Control panels

Using netstat and ss for detailed network connection analysis on Linux

Learn how to use netstat and ss to list listening ports, inspect established connections and trace which process owns a socket on a Linux VPS.

IntermediateLinux server7 min read

The netstat and ss commands show which ports your Linux server is listening on and which connections are currently open. Run ss -tulpn first: it lists every TCP and UDP socket, the port it uses and the process that owns it, which answers most "what is on port 8080?" questions in one line.

ss is the modern tool and ships with every current distribution. netstat comes from the older net-tools package, is often missing on fresh installs, and prints similar output with different flags. You will use both here, because plenty of tutorials and runbooks still quote netstat commands.

Before you start

You need:

  • A Linux VPS running Ubuntu 22.04/24.04, Debian 12, or AlmaLinux/Rocky 8–9.
  • SSH access with a sudo-capable user. If you are not connected yet, see connect to your Linux VPS with SSH.
  • Root or sudo rights, because seeing the process name behind a socket owned by another user requires elevated privileges.

Check whether netstat exists:

Bash
command -v netstat || echo "netstat not installed"

If it is missing, install the package:

Bash
# Ubuntu / Debian
sudo apt update && sudo apt install -y net-tools

# AlmaLinux / Rocky
sudo dnf install -y net-tools

Tip: ss is part of iproute2, which is installed by default. On a minimal image, add it with sudo apt install -y iproute2 or sudo dnf install -y iproute.

List all listening ports with ss and netstat

Start with the listening sockets. These are the entry points into your server: SSH, a web server, a database, or something you did not expect.

Bash
sudo ss -tulpn

Flag by flag: -t TCP, -u UDP, -l listening only, -p show the process, -n numeric ports and addresses instead of names. A typical line looks like this:

LISTEN 0  128  0.0.0.0:22  0.0.0.0:*  users:(("sshd",pid=741,fd=3))
LISTEN 0  511        *:80        *:*  users:(("nginx",pid=1204,fd=6))
LISTEN 0  128     [::]:22     [::]:*  users:(("sshd",pid=741,fd=4))

Read it as: protocol, receive queue, send queue, local address and port, peer address, then the process. 0.0.0.0:22 means listening on every IPv4 interface; 127.0.0.1:5432 means only reachable from the machine itself, which is what you want for a database.

The netstat equivalent:

Bash
sudo netstat -tulpn

Same columns, different order — local address comes before the peer. If you only need TCP, drop -u. If you want the interface names too, add -i to netstat or use ss -tulpn -o for timer state.

To see just one port:

Bash
sudo ss -tlnp 'sport = :22'
sudo netstat -tlnp | grep ':22'

Warning: A service listening on 0.0.0.0 is reachable from the internet unless a firewall blocks it. Confirm what is exposed before assuming the firewall covers it, and compare with your rules in UFW vs firewalld vs nftables vs iptables.

Inspect active and established connections

Listening ports only show the doors. Established connections show who is currently inside.

Bash
sudo ss -tan

That prints every TCP socket including ESTABLISHED, TIME-WAIT and CLOSE-WAIT. To narrow to live sessions:

Bash
sudo ss -tnp state established
sudo netstat -tnp | grep ESTABLISHED

You can filter by remote host, which is useful when one address is hammering your service:

Bash
sudo ss -tn state established '( dport = :80 or sport = :80 )'
sudo ss -tn dst 203.0.113.10

Count connections per remote IP to spot a flood:

Bash
sudo ss -tn state established | awk 'NR>1 {print $5}' | cut -d: -f1 | sort | uniq -c | sort -rn | head

For a one-screen summary of totals by protocol, use ss -s (netstat users can run netstat -s for the kernel counters, which is much longer output):

Bash
ss -s

Find which process owns a port

When a port is busy and a service refuses to start, you need the PID and the binary behind it.

Bash
sudo ss -tulpn | grep ':8080'
sudo lsof -i :8080

ss gives you users:(("java",pid=9821,fd=88)). Check what that process actually is before you touch it:

Bash
ps -fp 9821
sudo ls -l /proc/9821/exe

For a listening socket that belongs to a container, the process may appear as docker-proxy or live in a different network namespace. In that case inspect the container directly with docker ps and docker port <container>; see Docker persistent storage for non-root users if you are sharing volumes between containers and the host.

If the port is genuinely held by a stale process you own, stop it through its service manager first:

Bash
sudo systemctl stop myapp
sudo systemctl status myapp

Only kill by PID when the process has no unit:

Bash
sudo kill 9821
sudo kill -9 9821

Warning: kill -9 skips cleanup. For databases and mail servers it can leave files in an inconsistent state. Try systemctl stop, then plain kill, before -9.

Watch connections live

For intermittent problems — a health check that fails every few minutes, a client that reconnects in a loop — sampling is better than a one-off snapshot.

Bash
watch -n 2 'ss -tn state established | tail -n +2 | wc -l'

For a scrolling stream of new sockets as they appear:

Bash
sudo ss -tnp

Rerunning the command in a loop with watch is the safest option on a production server. You can also confirm a specific port answers from outside using plain bash, without installing anything:

Bash
timeout 5 bash -c '</dev/tcp/203.0.113.10/80' && echo "port open" || echo "port closed"

Check it works

Work through these in order and you will have a full picture of the server's network surface:

  1. sudo ss -tulpn lists every listening socket with a process name. No ? in the process column means you had enough privileges.
  2. sudo ss -tnp state established shows current sessions with both endpoints and the owning process.
  3. ss -s reports totals that roughly match what you saw in the detailed listings.
  4. Each port you expect (SSH, HTTP/HTTPS, your application) appears with the correct bind address, and nothing you cannot identify is listening on 0.0.0.0.
  5. A connection test from your workstation (nc -zv 203.0.113.10 22 or the bash /dev/tcp check above) succeeds for ports you intend to expose and fails for the rest.

If a service is bound to 127.0.0.1 but you expected it to be public, that is a service configuration issue, not a firewall one. Change the listen directive in the service config, restart it, and recheck with ss.

Troubleshooting

  • ss: command not found or netstat: command not found → the package is missing on a minimal image. Install iproute2 for ss or net-tools for netstat with your distribution's package manager, then rerun the command.
  • Process column shows a dash or ? → you ran the command without sudo. Ordinary users only see their own sockets, so always use sudo ss -tulpn.
  • Address already in use when starting a service → another process holds the port. Find it with sudo ss -tlnp 'sport =:8080', stop the owning unit, then start yours.
  • Port is listening locally but unreachable from outside → either the service binds only to 127.0.0.1, or the firewall drops the traffic. Check the bind address in ss output first, then review your firewall rules.
  • ss output is very long or hard to read → filter by state or port instead of scrolling: sudo ss -tn state established, sudo ss -tlnp 'sport =:443', or pipe through grep. Use -n so ports stay numeric.
  • A connection sits in CLOSE-WAIT for a long time → the remote end closed and your application has not released the socket. That is an application bug, not a kernel setting; restart the service and check its logs.

Tip: Do not reach for ss -K (killing sockets) as a first response. It drops connections without telling the application, which often makes the problem harder to diagnose. Stop or restart the service instead.

Next steps

Was this guide helpful?

Still stuck?

Ask MdmHost AI for a quick answer, or open a ticket and our team will take a look. We are here 24/7.