ss Command: Tutorial & Examples

The modern socket inspector — what's listening, what's connected.

What It Is

ss is the command that answers the two questions every server admin asks on day one: "what's listening on this box?" and "who's currently connected?" It stands for socket statistics, and it replaces the venerable netstat you'll still see in every old blog post. Same job, dramatically faster, with a tiny filter language bolted on. It ships in the iproute2 package on every modern Linux distro — same family as ip, same modern style.

If you've never touched a server before, this is the command you reach for the first time something on the network feels off — a port that should be open isn't, a port that shouldn't be open is, a service that should be reachable is bound to the wrong address. It's safe (ss only looks, never changes anything), it's instant, and once you can read its output you'll diagnose 80% of network mysteries from a single screen. Along the way we'll pick up TCP socket states, the difference between a socket and a port, and why ss runs circles around netstat on a busy machine.

Your First Look

The reflex command — the one you'll type on every new server before you trust it:

ss -ltnp
State   Recv-Q Send-Q   Local Address:Port   Peer Address:Port  Process
LISTEN  0      4096         127.0.0.1:631         0.0.0.0:*
LISTEN  0      70           127.0.0.1:3306        0.0.0.0:*      users:(("mysqld",pid=2323,fd=22))
LISTEN  0      128            0.0.0.0:22          0.0.0.0:*      users:(("sshd",pid=892,fd=3))
LISTEN  0      1000                 *:8080              *:*      users:(("java",pid=2397697,fd=87))
LISTEN  0      4096         127.0.0.1:8384        0.0.0.0:*      users:(("syncthing",pid=1525176,fd=63))

Four flags, complete answer: every TCP port that's listening, the process that owns it, its PID, and which interface it's bound to. The "first command on a new server" reflex. Memorize ss -ltnp before any other invocation — read it as listening, tcp, numeric, process. Drop the t for UDP too (-lunp), or use -ltunp for both.

The bare ss command — no flags — dumps every non-listening socket on the box, which on a busy server is hundreds of rows of established TCP connections plus a wall of Unix-domain sockets. You almost always want a filter: a state, a protocol, a port. That's the shape of every real session.

Pro Tip

ss -ltnp is the single most useful invocation in this whole command. Run it on a new server before anything else and you'll catch the classic first-timer mistakes: MySQL bound to 0.0.0.0:3306 instead of 127.0.0.1, a debug Java port wide open to the world, an sshd on an unexpected interface. One line, one audit.

How I Read It

When something on the network feels wrong, my eyes go in a fixed order — about four seconds total.

First I check what's listening. ss -ltnp. The local address column tells me at a glance whether each service is exposed: 127.0.0.1:3306 means "loopback only, the world can't reach it" — safe. 0.0.0.0:3306 or *:3306 means "bound to every interface" — reachable from the internet unless a firewall (iptables, nftables, firewalld, or a cloud security group) says otherwise. That distinction is the whole reason a MySQL bound to 0.0.0.0 with no firewall is the canonical first-timer mistake.

Second I check the state distribution — and this is the part veterans use as a diagnostic on its own. ss -s gives a one-line summary. If most TCP sockets are in ESTAB, the machine is doing normal work. If thousands are in TIME-WAIT, you've got short-lived connections piling up — usually a load balancer in front, or a client opening a fresh connection per request instead of reusing one. If they're in CLOSE-WAIT, that's a real bug in your app: it accepted a connection, the peer closed, but your code never called close(). The socket sits half-shut forever, eating a file descriptor each. A growing wall of CLOSE-WAIT is one of the most reliable "your code has a leak" signals on Linux.

Third, only now, I look at actual connections. ss -tnp state established shows me who's talking to whom right now. Pair the PID in the Process column with ps or top and you've connected "this socket" to "that program" to "that much CPU/memory." That triangulation is the daily move.

And the trick that took me years: ss has a tiny filter language — almost SQL-like — so you don't grep the output, you query it: ss -tn state established '( dport = :443 or dport = :80 )' shows every outbound HTTP/HTTPS connection in flight. Pre-filtered at the kernel, no awk, no grep. Worth knowing.

The Flags Explained

The flags worth knowing, in roughly the order you'll need them:

  • -l — only listening sockets (the "what's open" view).
  • -tTCP only.
  • -uUDP only. Combine: -tu for both.
  • -n — numeric: don't translate port 22 to ssh, don't reverse-DNS IPs. Always pass it. Faster, unambiguous, no hangs on stale DNS.
  • -p — show the process name and PID for each socket. Without sudo you'll only see processes you own (same /proc permission trap as lsof).
  • -a — all sockets (both listening and established). The default omits listeners.
  • -s — summary: counts per state, transport, IP family. The diagnostic glance.
  • -o — show timer info (keepalive, retransmit timers) — useful when a connection is hanging.
  • -e — extended info (UID, inode, socket cookie).
  • -m — show socket memory usage. Diagnostic for "why is this process eating buffer memory?"
  • -i — internal TCP info: rtt, cwnd, ssthresh — the kernel's view of how the connection is performing. The closest most admins get to seeing TCP congestion control in action.
  • -4 / -6 — restrict to IPv4 or IPv6.
  • -x — Unix-domain sockets only (the local IPC sockets — /var/run/... files).
  • -K — kill matching sockets (needs root; the nuclear option for a stuck connection).
  • state STATE — filter by socket state: established, listening, time-wait, close-wait, syn-sent, fin-wait-1, etc. Or by group: connected, synchronized, bucket, big.
  • sport / dport — filter by source / destination port, with operators: dport = :443, sport > :1024, dport != :22.
  • src / dst — filter by address: dst 10.0.0.0/8, src 192.168.1.5.

Reading It by Example

The handful of ss recipes that turn into muscle memory.

Everything listening, with owning process:

ss -ltnp

The new-server reflex. Add -u for UDP too: ss -ltunp.

One-line health summary:

ss -s
Total: 1383
TCP:   123 (estab 87, closed 6, orphaned 0, timewait 4)
Transport Total     IP        IPv6
RAW       1         0         1
UDP       22        12        10
TCP       117       37        80
INET      140       49        91

A normal box. Estab dominates, timewait small. If timewait were in the thousands you'd be looking at a short-lived-connection storm; if orphaned were nonzero you'd be looking at sockets the kernel is cleaning up after a crashed process.

Every established connection, with process:

ss -tnp state established

The "who's talking to my server right now" view. Pair with ps on the PIDs you don't recognize.

Connections in a worrying state:

ss -tn state close-wait
ss -tn state time-wait | wc -l

A nonzero (and growing) close-wait count is a leak in your application code. A huge time-wait count is normal under load but worth knowing about — it's the kernel waiting 60 seconds after a clean close to be sure no stray packets arrive.

Connections to a specific port:

ss -tnp '( sport = :443 or sport = :80 )'

Every inbound HTTP/HTTPS request currently in flight to your nginx. Swap sport for dport to see outbound.

Connections to a specific peer:

ss -tn dst 10.0.0.5

"Who on this box is talking to that database server?" Add -p and you have names too.

Kernel-level TCP info for hangs:

ss -tnpi state established '( dport = :443 )'

Adds -i for internal TCP info: round-trip time, congestion window, retransmits. When a connection is slow but not broken, this is where you see why.

Cheat Sheet

The invocations to keep in your head:

  • ss -ltnp — every listening TCP port, numeric, with process. The one to memorize.
  • ss -ltunp — same but TCP + UDP.
  • ss -s — summary: socket counts per state and transport.
  • ss -tnp — all established TCP, with process.
  • ss -tn state close-wait — find leaking sockets in your app.
  • ss -tn state time-wait | wc -l — count sockets in the post-close grace window.
  • ss -tnp '( dport = :443 )' — query language for fine-grained filters.
  • ss -x — Unix-domain sockets (/var/run IPC).
  • ss -tnpi state established — round-trip times, congestion window, retransmits.
  • ss -tnp dst 10.0.0.5 — every connection to one peer.
  • sudo ss -tnpK '( dport = :8080 )' — kill matching sockets (last resort).

How You'll Actually Use It

In real life ss lives in three moments. The new-server audit: SSH in, type ss -ltnp, scan for anything bound to 0.0.0.0 that shouldn't be. The "address already in use" detective: your service won't start, ss -ltnp | grep :8080 names the PID holding it. The state-distribution glance: something feels slow, ss -s in one second tells you whether the box is calm, swamped with timewaits, or leaking close-waits.

Outside those, it's a deeper tool: ss -tnpi when you're chasing why a connection is slow (the kernel's own RTT estimate is right there), ss -x when a local app can't reach its sibling over a Unix socket, the filter language when grep would be a hack. If you're scripting around it, ss -H suppresses the header for cleaner parsing.

Gotchas

  • You need sudo to see other users' processes. Without it, the Process column is empty for anything you don't own — same /proc permission rule that bites lsof. For a real audit: sudo ss -ltnp.
  • -l hides everything that isn't listening. A common surprise: you run ss -ltnp, see no row for sshd on a port you know is live, then realize you wanted ss -tnp (or -atnp) because that established session isn't a listening socket.
  • Default output drops the listening state. Without -l or -a, sockets in LISTEN are filtered out — ss shows you only the conversations in flight.
  • -n is not the default. Without it, ss translates port numbers to service names (:ssh, :http) and reverse-DNS-es every IP. Slow, noisy, and prone to hanging on a flaky resolver. Always -n.
  • The filter language is whitespace-sensitive. '( dport = :443 )' needs the spaces around the parens and the colon before the port. dport=:443 will not parse the way you expect.
  • A PID shown today may be a different program tomorrow. Same trap as ps, kill, lsof — re-check before you act on it.
  • CLOSE-WAIT is your app's fault, not the network's. It means your side received a FIN from the peer and never closed back. No amount of kernel tuning fixes a missing close() in your code.

History & Philosophy

ss arrived with the iproute2 toolkit in the mid-2000s, written to replace the net-tools family (netstat, ifconfig, route) that had become the maintenance burden of a previous era. The user-facing reason for the rewrite was speed — and the speed gap is bigger than people realize. netstat walks /proc/net/tcp, /proc/net/udp, and every /proc/<PID>/fd directory to match sockets back to processes. On a server with 10,000 open connections that's a lot of filesystem reads, and netstat can take many seconds to return. ss, by contrast, asks the kernel directly over a special socket called netlink — a structured query interface the kernel exposes for tools like ss and ip. One request, one response, no /proc walk. On that same box ss returns instantly.

That's the deeper magic: where top and lsof are pretty faces over text files in /proc, ss is a pretty face over a structured kernel API. The filter language (dport = :443, state close-wait) compiles down to a netlink filter the kernel evaluates on its side — you're not grepping output, you're querying the kernel's socket table directly. Once that lands, the gap between ss and netstat stops feeling like polish and starts feeling like the difference between SQL and a for loop in shell.

Note

if you still have netstat in your fingers, every guide you read will still mention it — but on a modern box, every netstat recipe has a faster ss equivalent. netstat -lntpss -lntp. netstat -tanss -tan. Same letters, same answer, ten times the speed. The old net-tools package isn't even installed by default on most distros anymore.

Reference

Socket states you'll see in the State column, and what each one tells you:

State Meaning What it signals
LISTEN Waiting for incoming connections A service is open on this port
ESTAB Active, open connection Normal traffic in flight
SYN-SENT Sent a SYN, awaiting reply This box is connecting out
SYN-RECV Got a SYN, sent SYN-ACK Handshake mid-way (inbound)
TIME-WAIT Recently closed, holding the grace window Normal in bulk on busy servers — short-lived connections cycling
CLOSE-WAIT Peer closed, local app hasn't called close() Often an app bug when it piles up — a socket/FD leak
FIN-WAIT-1 We sent a FIN, awaiting ACK Connection closing (local side initiated)
FIN-WAIT-2 Our FIN acked, awaiting peer's FIN Connection closing, half-shut
LAST-ACK Sent final FIN, awaiting last ACK Closing, almost gone
CLOSING Both sides sent FIN simultaneously Rare, closing

Column legend for the default ss -ltnp layout:

Column Meaning
Recv-Q Bytes received by the kernel but not yet read by the app (for LISTEN, the current backlog)
Send-Q Bytes sent but not yet ACKed by the peer (for LISTEN, the max backlog)
Local Address:Port Address and port on this machine — 127.0.0.1 = loopback only, 0.0.0.0/* = all interfaces
Peer Address:Port The other end; 0.0.0.0:* / *:* for a listener with no peer yet

See Also

  • netstat — the legacy tool ss replaced; same job, slower, still everywhere
  • lsof — the file-descriptor view; lsof -i :PORT is the older idiom ss -ltnp graduates you off
  • ps — what to run on the PID ss -p gave you
  • top / htop — pair a busy socket with the process eating CPU
  • tcpdump — when ss shows the connection but you need the packets
  • nmap — the external view: what your server looks like from the outside
  • iptables / nft — what controls whether a LISTENing port is actually reachable
  • ip — sibling tool in the same iproute2 family
  • nginx / ssh / mysql — the services whose ports you're auditing
  • tcp / udp / port / socket / protocol — the concepts behind every column
  • /proc — where netstat reads from, and what ss skips by going to netlink instead

Not sure what's listening on your servers right now — or which ones bound the database to 0.0.0.0 by accident?

CleverUptime runs ss -ltnp on every server, every minute, and tells you in plain language when a new port appears, when a critical one disappears, and when one is exposed to the world that shouldn't be.

Want to see your own server's health right now? One command, no signup, no install.

Check your server →