Why ss is Faster Than netstat
`netstat` reads `/proc/net/tcp`, `/proc/net/tcp6`, `/proc/net/udp`, and related files. Each of those files is generated on-demand by the kernel, formatted as text, then parsed by `netstat` in userspace. The more connections you have, the slower this gets - linearly.
`ss` uses the `NETLINK_INET_DIAG` socket family (kernel 2.6.14+) to query the kernel directly. The kernel returns structured binary data, so no text parsing happens. On our test server running 40,000 TCP connections under load, `ss -tan` returned in 87ms vs `netstat -tan` at 4.2 seconds.
`ss` is part of the `iproute2` package. Check your version with `ss -v`. As of iproute2 6.x (shipping with RHEL 9, Ubuntu 24.04, Debian 12), filter expressions support full boolean logic. Earlier versions have limited filter support, which affects the advanced examples later in this guide.
ss -v
# Example output: ss utility, iproute2-6.7.0
which ss
# /usr/sbin/ss
rpm -q iproute # RHEL/CentOS
dpkg -l iproute2 # Debian/Ubuntu
Core Flags You Need to Know
The flag structure follows a pattern: protocol selector + state filter + output modifier. You combine them freely.
`-t` selects TCP sockets. `-u` selects UDP. `-x` selects Unix domain sockets. `-l` limits output to listening sockets. `-a` shows all sockets (listening + established). `-n` disables name resolution - always use this on servers unless you need hostnames, because DNS lookups will slow `ss` down significantly. `-p` shows the process (PID and name) owning the socket. `-e` shows extended socket information including UID and inode. `-m` shows memory usage per socket. `-i` shows internal TCP information (RTT, congestion window, retransmit count).
The most common combination for a quick TCP overview is `ss -tanp`. For listening services only: `ss -tlnp`. These two commands cover 80% of daily use.
# All TCP sockets, no DNS, with process info
ss -tanp
# Only listening TCP sockets
ss -tlnp
# Listening UDP sockets
ss -ulnp
# Unix domain sockets
ss -xlnp
# All protocols, all states
ss -anp
Reading ss Output: Field by Field
The default columns are: Netid, State, Recv-Q, Send-Q, Local Address:Port, Peer Address:Port, and (with -p) Process.
`Netid` is the socket type: tcp, udp, u_str (Unix stream), u_dgr (Unix datagram), nl (Netlink), p_raw (raw packet).
`Recv-Q` for a listening socket shows the current backlog - the number of completed connections waiting to be `accept()`ed by the application. If this is consistently above zero and approaching your `net.core.somaxconn` limit, your application is not calling `accept()` fast enough. For established TCP sockets, `Recv-Q` shows bytes received but not yet read by the application.
`Send-Q` on a listening socket shows the maximum backlog configured (the value passed to `listen()`). On established sockets it shows bytes sent but not yet acknowledged by the peer.
A nonzero `Send-Q` on an established connection means either the peer is slow, the network has packet loss, or the peer's receive buffer is full. Sustained nonzero `Send-Q` across many connections to the same destination is a network problem, not an application problem.
# Output example from ss -tn:
# Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port
# tcp ESTAB 0 0 10.0.0.5:22 10.0.0.1:54321
# tcp LISTEN 0 128 0.0.0.0:80 0.0.0.0:*
# Check current somaxconn
sysctl net.core.somaxconn
Filtering by State
TCP state filtering is where `ss` significantly outpaces `netstat`. You can filter by any valid TCP state using the `state` keyword followed by the state name. Valid states include: `established`, `syn-sent`, `syn-recv`, `fin-wait-1`, `fin-wait-2`, `time-wait`, `closed`, `close-wait`, `last-ack`, `listening`, `closing`.
There are also state groups: `all` (all states), `connected` (all except `listening` and `closed`), `synchronized` (all connected except `syn-sent`), `bucket` (states maintained as minisockets: `syn-recv` and `time-wait`), `big` (opposite of bucket).
`TIME-WAIT` accumulation is a common problem on high-throughput HTTP servers. When you need to count these quickly during an incident, the filter + `wc -l` pattern is faster than grepping `netstat` output.
# Show only ESTABLISHED connections
ss -tn state established
# Count TIME-WAIT sockets
ss -tn state time-wait | wc -l
# Show SYN-RECV - useful for detecting SYN flood
ss -tn state syn-recv
# Show everything except ESTABLISHED (find anomalies)
ss -tn exclude established
# Multiple states: CLOSE-WAIT and FIN-WAIT-2 (app not closing sockets)
ss -tn state close-wait or state fin-wait-2
Filtering by Address and Port
Address and port filters use the `dst`, `src`, `dport`, and `sport` keywords. Ports use the format `eq :80` or `gt :1024` with standard comparison operators: `eq`, `ne`, `gt`, `ge`, `lt`, `le`.
You can combine conditions with `and` and `or`. Parentheses group conditions but must be escaped in the shell. In practice, use single quotes around complex filter expressions to avoid shell interpretation.
For CIDR range filtering, `ss` supports prefix notation directly. This is useful when you need to isolate traffic from a specific subnet - for example, checking all established connections from your internal monitoring network during a debug session.
# All connections to port 443
ss -tn dst :443
# All connections from port 80 (outbound web traffic)
ss -tn src :80
# Connections to a specific host
ss -tn dst 192.168.1.100
# Connections to host on specific port
ss -tn dst 192.168.1.100:443
# High-numbered source ports (ephemeral)
ss -tn 'sport gt :1024'
# Destination port range equivalent (combine with or)
ss -tn 'dport eq :80 or dport eq :443'
# All connections from a subnet
ss -tn src 10.0.0.0/8
# Connections NOT to loopback
ss -tn 'dst != 127.0.0.1'
Finding Which Process Owns a Socket
The `-p` flag requires root or the process must be owned by your user. Without root, `ss -p` shows `(("name",pid=N,fd=N))` only for your own processes. Run as root (or with `sudo`) to see all processes.
The output format changed between iproute2 versions. In iproute2 4.x, process info looks like `users:(("nginx",pid=1234,fd=6))`. In iproute2 6.x it is the same format but may include multiple entries when a socket is shared between processes (common with `SO_REUSEPORT` workers).
When you find a mystery socket and need the process, combine `-p` with `-e` to get the inode number, then cross-reference with `/proc` if the process name is not shown. This happens occasionally with short-lived processes that close before you can inspect them.
# Find what is listening on port 8080
ss -tlnp sport eq :8080
# Find process using a specific socket (run as root)
sudo ss -tlnp | grep :8080
# Get inode for further investigation
sudo ss -tlnpe | grep :8080
# Cross-reference inode to process
ls -la /proc/*/fd/* 2>/dev/null | grep socket:\[INODE\]
# Faster: use lsof as fallback
lsof -i :8080
Inspecting TCP Internals with -i
The `-i` flag exposes per-connection TCP internals that have no equivalent in `netstat`. This is the flag you reach for when debugging throughput problems, retransmit storms, or latency anomalies.
Key fields in `-i` output: `rto` is the retransmit timeout in milliseconds. `rtt` is the smoothed round-trip time and variance (`rtt/rttvar`). `ato` is the ACK timeout. `mss` is the maximum segment size negotiated. `cwnd` is the current congestion window in segments. `ssthresh` is the slow-start threshold. `send` shows the calculated send rate. `retrans` shows `current/total` retransmit counts. `rcv_space` is the receive buffer size.
A connection with high `retrans` and low `cwnd` is being throttled by packet loss. A connection with high `rtt/rttvar` ratio has jittery latency. These numbers let you distinguish network problems from application problems without needing a packet capture.
# TCP internals for all established connections
ss -tni state established
# Example -i output fields:
# cubic wscale:7,7 rto:204 rtt:1.5/0.75 ato:40 mss:1448
# rcvmss:1448 advmss:1448 cwnd:10 ssthresh:7 bytes_sent:45678
# bytes_retrans:0 bytes_acked:45678 bytes_received:12345
# segs_out:89 segs_in:67 send 77.2Mbps lastsnd:10 lastrcv:8 lastack:8
# pacing_rate 92.6Mbps delivery_rate 77.2Mbps delivered:88
# app_limited busy:15ms retrans:0/0 rcv_space:29200 rcv_ssthresh:29200
# Check retransmit counts across all connections
ss -tni | grep retrans | grep -v 'retrans:0/0'
Socket Memory Usage with -m
The `-m` flag shows the memory allocated to each socket's send and receive buffers. Format is `skmem:(r
`rmem_alloc` is memory currently allocated to the receive queue. `rcv_buf` is the total receive buffer size. `wmem_alloc` is memory allocated to the send queue. `snd_buf` is the total send buffer size. `sock_drop` is the number of packets dropped because the socket buffer was full - a nonzero value here means you need to increase `net.core.rmem_max` or `net.core.wmem_max`.
When debugging "connection drops" that application logs report but `ss -tn state time-wait` does not explain, check `sock_drop`. We found a case where a UDP service was silently dropping 2% of packets because `rcv_buf` was at default (212992 bytes) and the application processed messages slower than they arrived.
# Show memory per socket
ss -tnm state established
# Find sockets with drops (nonzero d= field)
ss -tnm | grep -v ',d:0)'
# Check current buffer limits
sysctl net.core.rmem_max net.core.wmem_max net.core.rmem_default net.core.wmem_default
# Increase receive buffer max (temporary)
sysctl -w net.core.rmem_max=16777216
# Persistent (add to /etc/sysctl.d/99-network.conf)
# net.core.rmem_max = 16777216
# net.core.wmem_max = 16777216
Unix Domain Sockets and Other Socket Types
Unix domain sockets are used heavily by databases, init systems, and inter-process communication. `ss -xl` shows listening Unix sockets. `ss -xa` shows all Unix sockets including connected pairs. The `Peer` column shows the inode of the connected socket, not a path.
With `-p`, you can identify which processes are communicating over Unix sockets. This is useful for mapping service dependencies: which processes talk to PostgreSQL's Unix socket, for example.
Netlink sockets (`ss -f netlink`) show kernel-userspace communication channels. Raw packet sockets (`ss -f packet`) show low-level packet captures in progress - useful for finding rogue `tcpdump` or custom monitoring agents that are capturing traffic you did not authorize.
For DevOps teams automating infrastructure audits, scripting `ss` output into a structured format that feeds into alerting pipelines is straightforward. Tools at taskbotshub.ai can help wire these socket audit scripts into broader automation workflows if you need to trigger alerts or remediation actions based on socket state thresholds.
# Listening Unix sockets with process info
sudo ss -xlnp
# Find what is connected to PostgreSQL socket
sudo ss -xp | grep postgres
# List all Netlink sockets
ss -f netlink -a
# Check for active packet captures (raw sockets)
ss -f packet -a
# Unix socket pair mapping (Peer column = connected inode)
ss -xa | awk '{print $1, $2, $5, $6}'
Practical Troubleshooting Workflows
Three common incidents and the exact `ss` commands to diagnose them:
**SYN flood detection:** Legitimate traffic produces SYN-RECV sockets briefly. A flood produces thousands. `ss -tn state syn-recv | wc -l` gives the count. `ss -tn state syn-recv | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -rn | head -20` shows the top source IPs. If you see hundreds of unique IPs, it is distributed. If you see one IP dominating, block it with `iptables -A INPUT -s
**Application not accepting connections:** If `ss -tln sport eq :PORT` shows `Recv-Q` steadily increasing toward the Listen backlog limit, the application is not calling `accept()` fast enough. Check application thread count, worker processes, and event loop health. Temporarily raise `net.core.somaxconn` to prevent client-visible errors while you fix the root cause.
**CLOSE-WAIT accumulation:** CLOSE-WAIT means the remote peer closed the connection but your application has not called `close()`. `ss -tn state close-wait | wc -l` shows scope. `ss -tnp state close-wait | awk '{print $7}' | sort | uniq -c | sort -rn` identifies which process is accumulating them. This is always a bug in the application - it receives EOF on a socket and does not close it. Common in Python and Java services with connection pool mismanagement.
# SYN flood: count and top sources
ss -tn state syn-recv | wc -l
ss -tn state syn-recv | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -rn | head -20
# Accept backlog pressure
watch -n1 'ss -tln'
# CLOSE-WAIT by process
sudo ss -tnp state close-wait | awk '{print $7}' | sort | uniq -c | sort -rn
# Connection count per remote IP (find dominant clients)
ss -tn state established | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -rn | head -20
# Ports with most connections
ss -tn state established | awk '{print $4}' | cut -d: -f2 | sort | uniq -c | sort -rn
Scripting and Output Formats
`ss` does not natively output JSON or CSV, but the output is consistent enough to parse with `awk`. For scripting, always use `-n` to avoid DNS delays and prefer explicit state filters to limit output volume.
For monitoring integration, the pattern is: run `ss` with a filter, count output lines (subtract 1 for the header), and feed the number into your metrics system. We use this to track `TIME-WAIT` count, `ESTABLISHED` count per service port, and `CLOSE-WAIT` accumulation as metrics exported to Prometheus via a shell exporter.
If you are building out automated remediation - for example, alerting when `CLOSE-WAIT` count for a specific service exceeds a threshold and then triggering a process restart - the scripting layer around `ss` is straightforward. Integrating that into a broader DevOps automation platform like taskbotshub.ai lets you build decision trees around socket health without maintaining custom alerting infrastructure.
For one-shot JSON output in a pipeline, `ss -tnp` output can be parsed reliably because column positions are fixed when using `-n` (no variable-length hostnames).
#!/bin/bash
# Socket health metrics for Prometheus node exporter textfile collector
OUTFILE=/var/lib/node_exporter/textfile_collector/sockets.prom
TW=$(ss -tn state time-wait 2>/dev/null | tail -n +2 | wc -l)
CW=$(ss -tn state close-wait 2>/dev/null | tail -n +2 | wc -l)
ES=$(ss -tn state established 2>/dev/null | tail -n +2 | wc -l)
SR=$(ss -tn state syn-recv 2>/dev/null | tail -n +2 | wc -l)
cat > "$OUTFILE" << EOF
# HELP socket_timewait_total Current TIME-WAIT socket count
# TYPE socket_timewait_total gauge
socket_timewait_total $TW
# HELP socket_closewait_total Current CLOSE-WAIT socket count
# TYPE socket_closewait_total gauge
socket_closewait_total $CW
# HELP socket_established_total Current ESTABLISHED socket count
# TYPE socket_established_total gauge
socket_established_total $ES
# HELP socket_synrecv_total Current SYN-RECV socket count
# TYPE socket_synrecv_total gauge
socket_synrecv_total $SR
EOF
ss vs lsof vs netstat: When to Use Each
`ss` is the right tool for: counting connections at scale, filtering by TCP state, checking buffer fill levels, inspecting TCP internals (RTT, cwnd, retransmits), and scripting socket metrics.
`lsof` is better when you need to: find all file descriptors for a process (not just sockets), trace file and socket activity together, or identify which process opened a specific file path. `lsof -i :PORT` is slower than `ss` but handles edge cases better when sockets are in unusual states.
`netstat` has no advantages over `ss` in 2026. The only reason to use it is compatibility with very old systems (pre-2.6.14 kernels, which you should not be running). On systems where `ss` is not installed, install `iproute2` rather than using `netstat`.
`/proc/net/tcp` direct parsing is occasionally useful in constrained environments (containers without `ss` or `netstat`, minimal images). The format is documented in `Documentation/networking/proc_net_tcp.rst` in the kernel source. Local addresses and ports are in hex, little-endian.
# Decode /proc/net/tcp hex address manually
# Local address field: 0100007F:0035 = 127.0.0.1:53
echo $((16#0035)) # = 53
# Hex IP bytes are reversed: 0100007F -> 7F.00.00.01 -> 127.0.0.1
# Quick install on Debian/Ubuntu if ss is missing
apt-get install -y iproute2
# Quick install on RHEL/CentOS
dnf install -y iproute