networking · difficulty ◆◆
ss — inspect sockets and network connections
See every socket on the box — who is listening, who is connected, and what is stuck.
netstat is gone from most modern distros, and the tool that replaced it is faster, more precise, and prints the same information in a fraction of the time. If you still reach for netstat out of habit, you are using a museum piece.
AI-generated draft · reviewed and editedThis text was generated with AI as a first draft, then reviewed and edited by the author, who holds editorial responsibility for it.$ ssWhat it does
ss (socket statistics) dumps the kernel's socket table: every TCP and UDP socket, whether it is listening, established, or stuck in a weird state, plus the process that owns it. It asks the kernel for that table over a netlink socket-diag connection instead of reading and parsing /proc/net the way net-tools did, and that is the difference that shows up once a box has thousands of sockets rather than a couple of hundred: it is a query, not a file to read and format.
Why it matters
When a port won't bind, a service won't start, or a connection hangs, ss is the first tool to reach for. It answers the three questions that matter: what is listening on this port, who is connected to me, and is anything stuck in a half-open state? It is the diagnostic backbone of every sysadmin and the reason netstat is now a legacy command on most systems.
The state in the first column is usually the whole answer
ss prints the socket state first, and the state is usually the diagnosis. LISTEN means a process is bound and waiting — the only state where nothing is wrong yet. ESTAB means both ends are still talking. TIME_WAIT is a socket whose local side closed first and which is sitting out twice the maximum segment lifetime before the kernel hands the port back; on Linux that is `tcp_fin_timeout` (60 seconds on this box), so a few dozen is normal, and a count that only ever climbs means something is opening far more short-lived connections than it should. CLOSE_WAIT is the one that means your code is at fault: the peer closed, your process has not, and the socket will sit there until it does. SYN_SENT means you are knocking and nothing is answering, which is a firewall or a dead host rather than an application bug. Add `-o` and every row carries its kernel timer as well, which is how you tell a healthy idle connection from a leak that never closes.
Example
$ ss -tlnpState Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
LISTEN 0 4096 0.0.0.0:4455 0.0.0.0:*
LISTEN 0 2048 127.0.0.1:43753 0.0.0.0:* users:(("hermes",pid=2090958,fd=8))
LISTEN 0 4096 0.0.0.0:5000 0.0.0.0:*
LISTEN 0 511 0.0.0.0:5500 0.0.0.0:* users:(("MainThread",pid=408243,fd=24))
LISTEN 0 4096 0.0.0.0:5678 0.0.0.0:*-t TCP, -l listening only, -n numeric ports (no DNS), -p show the owning process. This is the 'what is bound where' command — the first thing you run when a port won't start.
$ ss -tn state established '( dport = :443 or sport = :443 )'Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
0 0 192.168.1.57:52090 149.154.166.110:443
0 0 192.168.1.57:60148 149.154.166.110:443
0 0 192.168.1.57:55436 44.215.116.52:443
0 0 192.168.1.57:50570 44.215.117.206:443ss accepts a filter expression after the flags. This shows only established TCP connections to or from port 443 — every active HTTPS connection on the box, with the remote IP.
$ ss -sTotal: 1346
TCP: 452 (estab 221, closed 169, orphaned 1, timewait 52)
Transport Total IP IPv6
RAW 1 0 1
UDP 20 19 1
TCP 283 262 21
INET 304 281 23
FRAG 0 0 0-s prints a summary of all sockets by transport and state. The 'estab 221' line tells you how many live connections exist right now — a quick health read for a busy server.
$ ss -tan state time-wait | head -5Recv-Q Send-Q Local Address:Port Peer Address:Port Process
0 0 172.17.0.1:9119 172.30.0.4:47558
0 0 127.0.0.1:32958 127.0.0.1:34517
0 0 127.0.0.1:34517 127.0.0.1:34082
0 0 127.0.0.1:34112 127.0.0.1:34517The state filter is the useful half of ss. TIME_WAIT sockets are closed connections whose local side hung up first; they hold the port for tcp_fin_timeout seconds (60 on this box) and then disappear, so a few dozen is normal traffic and a count that only climbs is a churn problem. This box had 67 of them when I first counted and 155 a few minutes later — both fine, both moving. `ss -s` gives the same information as a one-line total when you do not want to count rows.
$ ss -tnp -o | head -6State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
ESTAB 0 0 127.0.0.1:9119 127.0.0.1:56008 users:(("hermes",pid=2921493,fd=51))
ESTAB 0 0 127.0.0.1:34332 127.0.0.1:34517 timer:(keepalive,11sec,0)
ESTAB 0 0 192.168.1.57:34982 104.16.102.112:443 users:(("python3",pid=1664797,fd=37))
ESTAB 0 0 192.168.1.57:40476 104.16.102.112:443 users:(("python3",pid=1478166,fd=15))
CLOSE-WAIT 0 0 127.0.0.1:54038 127.0.0.1:3499`-o` adds the kernel's timer for each socket and `-p` names the process, so a connection stops being anonymous. `timer:(keepalive,11sec,0)` is an idle connection about to send a keepalive — normal. The CLOSE-WAIT row is the interesting one: a peer closed that connection and the program on this side has not, which is the signature of a leak rather than a load problem. Pids, port numbers and timer values change with every run; the states and the users:(...) syntax are what you are reading.
Common flags
- -t
- show TCP sockets only
- -u
- show UDP sockets only
- -l
- listening sockets only (what is bound and waiting)
- -a
- -a all sockets, listening and non-listening — for TCP that means established connections plus everything sitting in TIME_WAIT, CLOSE_WAIT and the rest. Without -a, ss shows only the non-listening set.
- -n
- numeric — show ports and IPs without DNS or service-name lookups
- -p
- show the process (and PID) that owns each socket
- -s
- summary of socket counts by transport and state
- -o
- show timer info — useful for spotting stuck or half-open connections
History
Origin
ss was written by Alexey Kuznetsov, the kernel developer credited with the redesigned network subsystem the 2.2 kernels shipped, and it shipped as part of the iproute2 suite. It was designed from the start to ask the kernel for the socket table rather than read and parse a file, which is what shows up as a difference once a box has thousands of sockets. His name is in the source of ss itself, in the header of the ip command, and in the kernel's own ip-sysctl documentation.
Why netstat faded
netstat came from the net-tools package, which stopped being maintained as the kernel's networking internals evolved. Modern distros dropped it from default installs, and the iproute2 tools — ip, ss, and their siblings — became the standard. net-tools is still an installable package rather than a deleted one, so an older box — or this one, which still answers to `netstat` — can have both. ss is not just a faster netstat; its filter expressions let you query sockets the way you query a database.
Fun facts
Pros & cons
pros
- + Instant even on busy servers with thousands of sockets — netstat crawls by comparison
- + Powerful filter expressions let you query by port, state, or address
- + Shows the owning process with -p, so you can trace a port to a PID in one command
- + Ships by default on every modern Linux via iproute2
cons
- − Syntax is terse and the filter expressions take practice to read
- − No built-in continuous refresh like top — you re-run it or wrap it in watch
- − Not present on older systems that still rely on net-tools
Takeaways
- 1Start every port problem with `ss -tlnp` — it shows what is listening and which PID owns it
- 2Add -n to skip slow DNS lookups and get raw ports and IPs
- 3Use filter expressions like `ss -tn state established '( dport = :443 )'` to isolate one service's traffic
- 4Check `ss -s` for a one-line health summary of all connections
- 5Run it with sudo if you need to see processes you do not own
