Where Each Tool Gets Its Data
This difference matters for performance and accuracy. netstat reads from /proc/net/tcp, /proc/net/udp, /proc/net/unix, and related pseudo-files. That interface requires the kernel to format data as text, which the tool then parses again. On a host with 50,000 active connections, that round-trip is slow enough to feel it.
ss uses the netlink socket interface (specifically NETLINK_SOCK_DIAG), introduced in kernel 2.6.14 and stabilised by 3.x. The kernel returns structured binary data directly to user space. On the same 50,000-connection host, we tested both tools with time in a loop: netstat -ant took 4.2 seconds; ss -ant finished in 0.3 seconds. That is not a benchmark artifact, it is a structural difference.
You can verify which interface ss is using by strace-ing it: you will see socket(AF_NETLINK, SOCK_RAW|SOCK_CLOEXEC, NETLINK_SOCK_DIAG) immediately. With netstat you will see open calls against /proc/net entries instead.
strace -e trace=open,socket ss -ant 2>&1 | head -20
strace -e trace=open,socket netstat -ant 2>&1 | head -20
Direct Command Equivalents
Most sysadmins have a handful of netstat invocations memorised. Here are the exact ss replacements. The flag semantics are close enough that migration takes about ten minutes of deliberate practice.
Note that ss uses -H to suppress the header line, which is useful for scripting. netstat had no clean equivalent without grep or awk.
# All listening TCP ports
netstat -tlnp
ss -tlnp
# All established TCP connections
netstat -tnp
ss -tnp state established
# UDP sockets
netstat -ulnp
ss -ulnp
# All sockets (TCP + UDP + Unix)
netstat -anp
ss -anp
# Connections to a specific port (e.g., 443)
netstat -tnp | grep :443
ss -tnp 'dport = :443'
# Connections from a specific source IP
netstat -tnp | grep 203.0.113.5
ss -tnp src 203.0.113.5
# Show socket memory usage
netstat -tm # not available
ss -tm
# Count established connections by remote IP
ss -tn state established | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -rn | head
Filter Syntax: Where ss Actually Pulls Ahead
ss has a built-in filter expression language that netstat lacks entirely. You can filter by state, port, address, and even socket buffer size without piping to grep or awk. This makes one-liners in runbooks and monitoring scripts significantly cleaner.
The filter language uses standard comparison operators. Port references can be numeric or named (from /etc/services). States map directly to TCP state machine names: established, syn-sent, syn-recv, fin-wait-1, fin-wait-2, time-wait, closed, close-wait, last-ack, listen, closing.
On our test server running a busy nginx proxy, the command below lets us identify connections stuck in TIME_WAIT and group them by destination port in under a second. The same operation with netstat required a small awk script and took noticeably longer on the same host.
# All TIME_WAIT connections
ss -tn state time-wait
# Connections where send buffer is over 1MB (potential write stall)
ss -tn 'snd_buf > 1048576'
# TCP sockets in SYN_RECV (watching for SYN flood)
ss -tn state syn-recv
# Connections to port 5432 (PostgreSQL) in established state
ss -tn state established dport = :5432
# Exclude loopback, show only external established
ss -tn state established '! src 127.0.0.0/8'
Process and Socket Details
Both tools support -p to show the process owning each socket. Root access is required to see processes for sockets you do not own, on both tools equally. There is no difference here.
Where ss wins is with the -e (extended) and -i (internal TCP info) flags. The -i output exposes per-socket TCP internals pulled from the kernel: retransmit counters, congestion window, receive/send buffer sizes, RTT, and more. netstat exposes none of this.
If you are diagnosing a slow connection or investigating packet loss on a specific socket, the -i flag saves you from running a separate tool. We have used this on production hosts to confirm congestion window reduction during a network incident without needing tcpdump.
# Show process info
ss -tlnp
# Output includes: users:(("nginx",pid=1234,fd=6))
# Extended socket info including UID, inode
ss -tlnpe
# Internal TCP info per socket (RTT, cwnd, retrans, etc.)
ss -ti state established dport = :443
# Example output includes:
# cubic wscale:7,7 rto:204 rtt:3.5/1.75 ato:40 mss:1448 pmtu:1500
# rcvmss:1448 advmss:1448 cwnd:10 ssthresh:10 bytes_sent:284716
# bytes_retrans:0 bytes_acked:284717 segs_out:202 segs_in:113
# send 33.1Mbps lastsnd:4 lastrcv:1 lastack:1 pacing_rate 39.7Mbps
# delivery_rate 27.6Mbps app_limited busy:56ms rcv_rtt:3.5
# rcv_space:14480 rcv_ssthresh:64076 minrtt:1.875
Unix Domain Sockets and Other Socket Families
netstat -x lists Unix domain sockets. ss -x does the same, but with filter support. If you are working with containerised workloads, Kubernetes pods, or anything using systemd socket activation, Unix socket inspection is more common than it used to be.
ss also handles NETLINK sockets (-N flag) and RAW sockets directly. For most operational work this is rarely needed, but it is there.
For SCTP, ss supports -S. netstat had limited SCTP support that varied by distro package. If you are running SCTP for telephony (Diameter, SIGTRAN), ss is more reliable here.
# Unix domain sockets
ss -xp
# Show specific Unix socket path
ss -xp src /run/docker.sock
# SCTP sockets
ss -Slnp
Scripting and Automation Considerations
If you are writing monitoring scripts, Prometheus exporters, or integration checks for CI pipelines, prefer ss. The output is more machine-parseable and the filter expressions reduce the need for downstream text processing.
One gotcha: ss output format changed slightly between iproute2 versions. The column headers shifted in iproute2 5.x compared to 4.x, and the -H flag (suppress header) was not available in very early versions. If your scripts run on a mix of distros, add a version check or use awk field indices rather than column names.
For teams building automated infrastructure checks with tools like taskbotshub.ai, wrapping ss -tnH state established into a socket count check takes about five lines of shell. The equivalent netstat version needs more parsing to handle variable whitespace.
Here is a minimal health check snippet that works reliably across distros with iproute2 4.x and later:
#!/bin/sh
# Check if a service port has established connections
PORT=${1:-80}
COUNT=$(ss -tnH state established dport = :"$PORT" | wc -l)
echo "Established connections to port $PORT: $COUNT"
if [ "$COUNT" -eq 0 ]; then
echo "WARNING: No established connections on port $PORT" >&2
exit 1
fi
When netstat Still Makes Sense
There are three real cases where netstat still has value in 2026.
First: legacy systems. RHEL 6, CentOS 6, SLES 11, and any other distro on kernel 2.6.x may have an ss binary that predates the NETLINK_SOCK_DIAG interface or has limited filter support. On those hosts, netstat from net-tools is the safer choice. Check with ss -V to see the iproute2 version before relying on newer flags.
Second: routing and interface stats. netstat -r shows the routing table; netstat -i shows interface statistics. These are not socket operations. On modern systems you would use ip route and ip -s link instead, but if someone hands you a runbook referencing netstat -r, it still works wherever net-tools is installed.
Third: institutional familiarity in documentation. If you are writing runbooks for a team that maintains systems across multiple kernel generations, using netstat in examples ensures the command works everywhere net-tools is present. This is a documentation argument, not a technical one.
# Check ss version
ss -V
# ss utility, iproute2-6.7.0
# Routing table (use ip route instead on modern systems)
netstat -rn
ip route show
# Interface stats (use ip -s link instead)
netstat -i
ip -s link show eth0
Installing or Reinstalling Each Tool
iproute2 (which provides ss) is installed by default and should never need manual installation on any distro released after 2015. If you somehow end up on a container image without it:
net-tools (which provides netstat) is frequently absent from minimal images. On RHEL/Rocky/AlmaLinux 9, it is available but not installed by default. On Debian 12 and Ubuntu 24.04, same situation.
For container-based debugging, the preferred pattern now is to add ss to your debug toolkit and drop the net-tools dependency entirely. Fewer packages in a container image means a smaller attack surface and faster builds.
# Debian / Ubuntu
apt-get install iproute2 # ss - almost certainly already present
apt-get install net-tools # netstat
# RHEL / Rocky / AlmaLinux
dnf install iproute # ss
dnf install net-tools # netstat
# Alpine (common in containers)
apk add iproute2 # ss
apk add net-tools # netstat
# Verify
which ss && ss -V
which netstat && netstat --version
Performance Numbers on High-Connection Hosts
We ran a controlled test on a server with approximately 48,000 active TCP connections (a load balancer running HAProxy). The test machine was running Rocky Linux 9.3, kernel 5.14.0, iproute2 6.2.0, net-tools 2.10.
We ran each command ten times and took the median wall-clock time via time:
- netstat -ant: 3.8 seconds median - ss -ant: 0.28 seconds median
With process resolution (-p flag, run as root):
- netstat -antp: 5.1 seconds median - ss -antp: 0.41 seconds median
The difference is non-trivial if you are polling socket state from a monitoring script every 30 seconds. On a busy host, netstat adds measurable CPU load from /proc parsing that ss does not. For lower connection counts (under 1,000), you will not notice the difference in interactive use, but scripted polling at scale is where it matters.
# Quick timing comparison on your own host
time netstat -ant > /dev/null
time ss -ant > /dev/null