The ss Command: Socket Statistics for Linux
If you still type netstat -tulpn out of muscle memory, this guide is for you. On a modern distribution that command increasingly returns command not found: netstat lives in the old net-tools package, which is no longer installed by default on most current releases. Its replacement is ss, part of iproute2 — the same package that gave us ip in place of ifconfig.
The switch is not just cosmetic. netstat works by parsing text files under /proc/net/, which means that on a busy host with tens of thousands of sockets it reads and re-parses a great deal of text. ss queries the kernel directly through the netlink interface (sock_diag), so it is dramatically faster on exactly the machines where you are most likely to be in a hurry. It also exposes internal TCP state — congestion window, round-trip time, retransmission counts — that netstat never showed you.
The one command to memorise
If you take a single thing away, take this one:
ss -tlnp
It answers the question you ask most often — what is listening on this box, and which process owns it? The flags are worth learning individually, because you will recombine them constantly:
-t: TCP sockets.-u: UDP sockets.-l: only listening sockets.-n: numeric — do not resolve service names or hostnames. Always use this; DNS resolution is the single biggest reason the command feels slow.-p: show the owning process (requires root for sockets you do not own).-a: all sockets, listening and connected.
So the direct translation of the old habit is:
# old: netstat -tulpn
ss -tulnp
Typical output looks like this:
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 4096 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=812,fd=3))
LISTEN 0 511 127.0.0.1:6379 0.0.0.0:* users:(("redis-server",pid=1043,fd=6))
LISTEN 0 4096 *:443 *:* users:(("nginx",pid=1370,fd=8))
A quick note on the address column, because it trips people up: 0.0.0.0:22 means "listening on all IPv4 addresses", 127.0.0.1:6379 means "loopback only — not reachable from another host", and *:443 is the all-addresses wildcard for a socket bound to both IPv4 and IPv6. When someone reports that a service is unreachable from outside, the difference between the first and second lines is very often the whole answer.
Reading Recv-Q and Send-Q correctly
These two columns are the most useful and most misread part of the output, because their meaning changes depending on the socket state.
For an established connection:
- Recv-Q — bytes received by the kernel but not yet read by the application. Persistently high means your process is not calling
read()fast enough: it is busy, blocked, or stalled. - Send-Q — bytes queued for sending but not yet acknowledged by the peer. Persistently high points at the network or a slow receiver, not at your application.
For a socket in LISTEN state, the same columns mean something completely different:
- Recv-Q — the number of connections that have completed the handshake and are waiting in the accept queue for the application to accept them.
- Send-Q — the maximum size of that accept queue (the effective backlog).
That second pair is a genuinely valuable production signal. In the sample above, nginx shows 0 4096: an empty queue with room for 4096. If you instead see a listening socket sitting at 4096 4096, the accept queue is full — the application is not accepting fast enough, and the kernel is dropping or refusing new connections. That presents to users as random connection timeouts while the service looks "up" in every dashboard. Confirm it with:
ss -tln
nstat -az TcpExtListenOverflows TcpExtListenDrops
A climbing ListenOverflows counter alongside a saturated Recv-Q is conclusive. The fix is application-side concurrency (more workers/threads) and, secondarily, raising the backlog — both the app's listen() backlog and net.core.somaxconn.
Filtering by state
ss takes a small filter language, and state filters are the most useful part of it:
ss -tn state established
ss -tn state time-wait
ss -tn state syn-sent
There are also handy aggregate keywords: connected (anything but listening/closed), synchronized (established-ish states, excluding syn-sent), and bucket (the minor states time-wait and syn-recv).
The gotcha worth knowing: when you supply an explicit state filter, ss drops the State column from the output — every row has the same state, so it stops printing it. This shifts every column one position to the left, which silently breaks any awk '{print $5}' you copied from a netstat-era snippet. With a state filter, the peer address is $4; without one, it is $5.
Filtering by address and port
Port and host filters use a small expression syntax. Note the colon before the port number — it is required:
# everything talking to or from port 443
ss -tn '( sport = :443 or dport = :443 )'
# who is connected to the local Postgres
ss -tn state established '( sport = :5432 )'
# all connections to one particular host
ss -tn dst 10.0.4.17
# a whole subnet, with port range
ss -tn 'dst 10.0.0.0/8 and dport >= :8000 and dport <= :8999'
The vocabulary is worth internalising: src/dst for addresses, sport/dport for ports, combined with and, or, not and the comparison operators =, !=, <, >, <=, >=. Quote any expression containing parentheses, spaces, or </> so the shell does not interpret them.
Summary and TCP internals
For a fast overall picture, -s prints totals per protocol and state:
ss -s
This is the quickest way to spot socket exhaustion or an unexpected mountain of time-wait entries. When you need to know why a specific connection is slow rather than merely that it exists, -i exposes the kernel's internal TCP information:
ss -tni state established '( dport = :443 )'
Among the fields it reports, a few earn their keep immediately:
rtt:— smoothed round-trip time and its variance, in milliseconds. Real latency to that peer, measured by the kernel rather than guessed by you.cwnd:— the congestion window. A window stuck at a very low value on a long-lived transfer means the kernel has detected loss and throttled you.retrans:— retransmissions, shown as current/total. Anything beyond a trickle is packet loss, and it is the fastest way to distinguish a network problem from an application problem.bbr/cubic— the congestion control algorithm in use.
Three more modifiers round out the set: -m shows per-socket memory usage (useful when chasing kernel memory pressure), -o shows active timers such as the timer:(keepalive,...) countdown, and -e shows extended details including the socket inode and owning UID.
Unix and other socket families
ss is not TCP-only. Unix domain sockets are frequently the missing piece when a local service "cannot connect" despite nothing being wrong with the network:
# listening Unix sockets, with owning process
ss -xlp
# does the PHP-FPM / Docker / Postgres socket actually exist?
ss -xlp | grep -E 'php|docker|postgres'
Restrict the address family when a host is dual-stack and you only care about one half: -4 for IPv4, -6 for IPv6. Beyond that, -w covers raw sockets and -x Unix sockets, matching the layout of netstat's equivalent options.
Recipes for real problems
What is using port 8080, and can I have its PID?
ss -tlnp sport = :8080
Which clients hold the most connections to this host? Remember the column shift from the state filter — peer is $4 here. Stripping only the final colon-separated field keeps this correct for IPv6 addresses too:
ss -tn state established |
awk 'NR>1 {print $4}' |
sed 's/:[^:]*$//' |
sort | uniq -c | sort -rn | head
Am I running out of ephemeral ports? Compare the number of outbound connections against the configured range:
ss -tn state established | wc -l
sysctl net.ipv4.ip_local_port_range
Is my accept queue overflowing? The single most valuable check in this guide:
ss -tln # compare Recv-Q against Send-Q on LISTEN rows
Why is this connection slow?
ss -tnie dst 10.0.4.17 # look at rtt, retrans, cwnd
One final option deserves a safety note: ss -K can forcibly close matching sockets, for example ss -K dst 10.0.4.17. It requires root and a kernel built with CONFIG_INET_DIAG_DESTROY, and it terminates live connections without the application's knowledge. It is a genuine emergency tool — useful for evicting a stuck client, dangerous as a habit.
netstat to ss translation
| Old netstat command | ss equivalent |
|---|---|
netstat -tulpn | ss -tulnp |
netstat -an | ss -an |
netstat -tn | ss -tn state connected |
netstat -s | ss -s (or nstat for counters) |
netstat -x | ss -x |
netstat -r | ip route |
netstat -i | ip -s link |
The last two are the ones people forget: routing tables and interface statistics were never really ss's job — they moved to ip.
Where it fits in a debugging session
ss answers "what sockets exist, in what state, owned by whom". That is usually where a network investigation should start, because it is instant and requires no capture. When the answer is "the connection exists but the conversation looks wrong", that is the moment to reach for a packet capture and watch the bytes on the wire with tcpdump. If the connection never appears at all, the problem is upstream of the socket layer — routing, firewall rules, or DNS — and it is worth walking through the fundamentals in basic network troubleshooting.
Keep ss -tlnp and ss -tln in your fingers. Between "who is listening" and "is the accept queue backing up", those two cover a surprising share of real production incidents.