netstat and ss — Diagnosing Network Connections on Linux
When something isn’t listening on the port you expect, or a service won’t start because “address already in use,” netstat and ss are the first tools to reach for. They show you what sockets are open, which processes own them, and the state of every connection on the machine. ss is the modern replacement for netstat — faster and more detailed — but netstat is still installed on most systems, so it’s worth knowing both.
Installing the Tools
On modern Debian/Ubuntu systems, netstat has been removed from the default install:
$ sudo apt install net-tools # provides netstat
$ sudo apt install iproute2 # provides ss (usually pre-installed)
On macOS, netstat is available by default. ss is a Linux-only tool.
netstat Basics
netstat with no arguments shows all active connections and Unix domain sockets — usually more noise than you want. These are the flags you’ll actually use:
| Flag | Meaning |
|---|---|
-t |
TCP sockets |
-u |
UDP sockets |
-l |
Listening sockets only |
-a |
All sockets (listening + connected) |
-n |
Numeric output (don’t resolve hostnames or port names) |
-p |
Show the PID/process name owning the socket |
-r |
Show the routing table |
-s |
Summary statistics per protocol |
Most common command: what’s listening
$ sudo netstat -tlnp
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 1234/sshd
tcp 0 0 127.0.0.1:5432 0.0.0.0:* LISTEN 5678/postgres
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 9012/nginx
tcp6 0 0 :::443 :::* LISTEN 9012/nginx
Breaking down the flags: -t (TCP), -l (listening), -n (numeric), -p (process).
Check UDP as well:
$ sudo netstat -ulnp
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
udp 0 0 0.0.0.0:53 0.0.0.0:* 1111/systemd-resolve
Show all active TCP connections
$ netstat -tn
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 192.168.1.10:56234 93.184.216.34:443 ESTABLISHED
tcp 0 0 192.168.1.10:54321 140.82.113.4:443 ESTABLISHED
Find what’s using a specific port
$ sudo netstat -tlnp | grep :8080
tcp 0 0 0.0.0.0:8080 0.0.0.0:* LISTEN 3456/node
Routing table
$ netstat -rn
Kernel IP routing table
Destination Gateway Genmask Flags MSS Window irtt Iface
0.0.0.0 192.168.1.1 0.0.0.0 UG 0 0 0 eth0
192.168.1.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0
Protocol statistics
$ netstat -s | head -20
Ip:
123456 total packets received
0 forwarded
0 incoming packets discarded
Tcp:
5678 active connection openings
1234 passive connection openings
12 failed connection attempts
ss — The Modern Replacement
ss queries socket information directly from the kernel and is significantly faster than netstat on busy systems. The flag interface is similar but not identical.
| Flag | Meaning |
|---|---|
-t |
TCP |
-u |
UDP |
-l |
Listening |
-a |
All |
-n |
Numeric |
-p |
Process info |
-s |
Summary |
-e |
Extended socket info |
-o |
Timer information |
-r |
Try to resolve hostnames |
What’s listening (the ss equivalent of netstat -tlnp)
$ sudo ss -tlnp
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=1234,fd=3))
LISTEN 0 5 127.0.0.1:5432 0.0.0.0:* users:(("postgres",pid=5678,fd=5))
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=9012,fd=6))
Filter by state
ss supports rich state filtering:
$ ss -tn state established
Recv-Q Send-Q Local Address:Port Peer Address:Port
0 0 192.168.1.10:56234 93.184.216.34:443
Available states: established, syn-sent, syn-recv, fin-wait-1, fin-wait-2, time-wait, closed, close-wait, last-ack, listening, closing.
Filter for TIME_WAIT sockets (a common sign of connection churn):
$ ss -tn state time-wait | wc -l
247
Filter by address or port
$ ss -tnp 'dport = :443' # connections to port 443
$ ss -tnp 'sport = :8080' # connections from local port 8080
$ ss -tnp 'dst 10.0.0.1' # connections to a specific IP
Show socket memory usage
$ ss -tm
Summary
$ ss -s
Total: 245
TCP: 18 (estab 5, closed 1, orphaned 0, timewait 1)
Transport Total IP IPv6
RAW 0 0 0
UDP 6 4 2
TCP 17 9 8
Practical Diagnostic Scenarios
“Port already in use” on startup
$ sudo ss -tlnp | grep :3000
LISTEN 0 511 0.0.0.0:3000 0.0.0.0:* users:(("node",pid=7890,fd=22))
$ kill 7890
Check if a remote port is reachable
netstat and ss only show local sockets. For remote connectivity, use:
$ nc -zv db.internal 5432
Connection to db.internal (10.0.0.5) 5432 port [tcp/postgresql] succeeded!
Or with timeout:
$ timeout 3 bash -c 'cat < /dev/null > /dev/tcp/db.internal/5432' && echo "open" || echo "closed"
open
Monitor connections to a service in real time
$ watch -n 1 'ss -tn state established | grep :443 | wc -l'
Find the process behind an established connection
$ sudo ss -tnp state established
Recv-Q Send-Q Local Address:Port Peer Address:Port Process
0 0 10.0.0.5:41234 34.107.221.82:443 users:(("curl",pid=12345,fd=5))
netstat vs ss — Quick Comparison
netstat |
ss |
|
|---|---|---|
| Speed | Slower (reads /proc) |
Faster (kernel netlink) |
| Available | Linux, macOS, BSD | Linux only |
| State filtering | Limited | Rich |
| Default install | No (removed from many distros) | Yes |
| Flags | -tlnp style |
Same, plus filter expressions |
On Linux, prefer ss. On macOS, netstat is your only option (or install lsof).
Conclusion
The two commands worth memorizing are sudo ss -tlnp (listening TCP sockets with process info) and sudo ss -tn state established (active connections). Everything else is layered on top. When a port isn’t responding as expected, these tools give you the ground truth in seconds — what’s bound, what’s connected, and which process owns it.