Back to blog
Linux & SRE September 15, 2026

Linux Netstat vs SS: Diagnosing TCP Socket Buffer Saturation, Queue Backpressure, and Kernel Memory

Automate WhatsApp Alerts
Start Free ➔

During high-throughput traffic bursts or sudden database query latency spikes, application performance deteriorates without an obvious CPU or memory bottleneck on the host. API response times jump from 15 ms to 2,500 ms, clients begin experiencing connection timeouts, and your reverse proxies emit HTTP 502/504 errors.

When SREs inspect the host, the first instinct is often to run legacy tools like netstat -an or check bandwidth utilization. However, in modern high-concurrency Linux environments, the true bottleneck frequently hides inside the kernel TCP socket buffers and socket queues (Recv-Q / Send-Q).

Understanding whether backpressure originates from an application reader stall, a network congestion bottleneck, an unacknowledged transmit queue, or TCP window exhaustion requires moving past legacy /proc/net/tcp parsers to modern Netlink-based socket diagnostics (ss -tinm).

In this principal SRE guide, we break down Linux kernel socket memory architecture, compare netstat vs ss at the syscall layer, decode cryptic skmem output, calculate Bandwidth-Delay Product (BDP) buffer sizing, and provide a production triage runbook.


1. TCP Socket Buffers: The Kernel Data Path

To diagnose socket saturation, we must first trace how data traverses the Linux kernel network stack between user space and the physical network interface controller (NIC).

                 THE LINUX KERNEL TCP SOCKET DATA PATH

     ┌─────────────────────────────────────────────────────────────┐
     │                     User Space Application                  │
     └──────────────┬───────────────────────────────▲──────────────┘
      write()/send()│                               │read()/recv()
                    ▼                               │
     ┌──────────────────────────────┐┌─────────────────────────────┐
     │       TCP Send Buffer        ││      TCP Receive Buffer     │
     │        (SO_SNDBUF)           ││         (SO_RCVBUF)         │
     │  - Queued Payload (Send-Q)   ││  - Queued Payload (Recv-Q)  │
     │  - Out-of-Order / Unacked    ││  - Out-of-Order Queue (OFO) │
     │  - sk_buff Metadata Overhead ││  - sk_buff Metadata Overhead│
     └──────────────┬───────────────┘└──────────────▲──────────────┘
                    │                               │
       TCP Engine:  │ cwnd, rwnd, PACING            │ TCP Engine: Prune, ACK,
                    ▼                               │             Window Update
     ┌──────────────────────────────┐┌─────────────────────────────┐
     │       IP / Routing Layer     ││       IP / Routing Layer    │
     └──────────────┬───────────────┘└──────────────▲──────────────┘
                    ▼                               │
     ┌──────────────────────────────┐┌─────────────────────────────┐
     │   qdisc (e.g. fq, fq_codel)  ││       NIC RX Ring Buffer    │
     └──────────────┬───────────────┘└──────────────▲──────────────┘
                    ▼                               │
     ┌──────────────────────────────┐               │ (NAPI / SoftIRQ)
     │      NIC TX Ring Buffer      │               │
     └──────────────┬───────────────┘               │
                    ▼                               │
            Physical Network ───────────────────────┘

What Actually Saturates?

A socket does not simply hold raw bytes; it is backed by Linux kernel struct sock and chained struct sk_buff (socket buffer) descriptors. Sockets encounter five distinct pressure boundaries:

  1. Send Buffer Saturation (SO_SNDBUF / tcp_wmem):
    • The application writes data faster than the TCP stack can transmit and acknowledge over the network.
    • Constrained by: Peer advertised receive window (rwnd), local congestion window (cwnd), network packet loss/retransmissions, or transmission pacing.
  2. Receive Buffer Saturation (SO_RCVBUF / tcp_rmem):
    • Packets arrive from the network faster than the user space application invokes read(), recv(), or epoll_wait().
    • Result: The receive buffer fills up, the TCP stack shrinks its advertised receive window (rwnd = 0), and the remote sender is forced to freeze transmission.
  3. Global TCP Memory Pressure (net.ipv4.tcp_mem):
    • The aggregate memory consumed by all open TCP sockets exceeds the kernel's global allocation thresholds, forcing the kernel to prune out-of-order queues and drop incoming segments.
  4. NIC Ring Buffer & Queue Discipline (qdisc) Saturation:
    • Hardware-level descriptor rings or traffic-shaping queues back up before packets ever reach the TCP layer.

2. netstat vs ss: Syscall & Architecture Comparison

For decades, netstat (from the net-tools package) was the default network diagnostic tool. In modern production environments, netstat is obsolete and dangerous to run under heavy load.

   LEGACY: netstat                      MODERN: ss (Socket Statistics)
┌──────────────────────┐             ┌──────────────────────────────────┐
│ netstat -ant         │             │ ss -tinm                         │
└──────────┬───────────┘             └────────────────┬─────────────────┘
           │                                          │
           ▼                                          ▼
   Reads /proc/net/tcp                        Sends NETLINK_INET Request
  (Parses ASCII Text File)                    (Binary Netlink Socket)
           │                                          │
           ▼                                          ▼
 Sequential linear scan                       Zero-copy kernel memory dump
 Lock contention on large tables              Fast kernel-space filtering
 O(N) memory allocation in user space         O(1) streaming response

The Flaws of /proc/net/tcp and netstat

  1. Host-Freezing Overhead: netstat reads /proc/net/tcp and /proc/net/tcp6. The kernel generates this file on-the-fly as single-line ASCII text. On a host with 100,000 active connections (e.g., an NGINX proxy or Envoy mesh gateway), generating and parsing this multi-megabyte string blocks kernel structures, spikes CPU, and causes measurable request latency.
  2. Missing TCP Internals: netstat cannot inspect real-time congestion parameters (cwnd, ssthresh, rtt, rto, pacing_rate, or window scale factors).
  3. Zero Socket Memory Visibility: netstat only reports high-level byte counts; it cannot report sk_buff metadata overhead, forward-allocated memory, or socket-level buffer limits.

Detailed Diagnostic Comparison

Feature / MetricLegacy netstatModern ss (iproute2)Operational Impact in SRE Incidents
Kernel Interface/proc/net/tcp (ASCII formatting)NETLINK_INET (Binary netlink socket)ss executes up to $20 imes$ faster on $>50 ext{k}$ socket hosts.
Queue InspectionBasic Recv-Q & Send-QDetailed Recv-Q & Send-QCritical for identifying consumer stalls vs network backpressure.
TCP Internals (-i)❌ Not availablecwnd, rwnd, rtt, retrans, rtoEnables instant differentiation between network drop vs app hang.
Socket Memory (-m)❌ Not availableskmem:(r,rb,t,tb,f,w,o,bl,d)Exposes real buffer limits vs kernel metadata fragmentation.
Advanced FilteringGrep-based string matchingNative BPF-like syntax (dport = :443)Filter hundreds of thousands of sockets in kernel space with zero CPU hit.
Process Resolution-p (Slow /proc/$PID/fd scan)-p (Optimized netlink + proc map)Identifies offending PID and container descriptor mapping.

3. Reading ss Output Like a Principal SRE

When diagnosing a live connection bottleneck, execute:

ss -tinmp '( sport = :443 or dport = :443 )'

Decoding the Critical Output Fields

ESTAB  0  262144  10.0.4.12:443  198.51.100.84:54210  users:(("nginx",pid=14022,fd=18))
  skmem:(r0,rb131072,t0,tb524288,f4096,w262144,o0,bl0,d0)
  cubic wscale:7,7 rto:200 rtt:14.2/0.8 ato:40 mss:1460 rcvmss:1460 advmss:1460
  cwnd:10 ssthresh:8 bytes_sent:1849200 bytes_retrans:43800 bytes_acked:1805400
  segs_out:1280 segs_in:940 data_segs_out:1260 send 8.2Mbps lastsnd:12 lastrcv:140
  pacing_rate 16.4Mbps delivery_rate 4.1Mbps app_limited retrans:1/30

Let us break down every parameter and what it reveals about the socket health:

1. Connection Header & Queues

  • ESTAB: Socket is in an active established TCP state.
  • Recv-Q (0): In established state, bytes received by the kernel but not yet read by the application via recv(). ($0 =$ Application is consuming data promptly).
  • Send-Q (262144): In established state, bytes queued in kernel transmit memory awaiting ACK from peer ($262 ext{ KB}$ buffered).

⚠️ CRITICAL DIFFERENCE: Listen vs Established State:

  • In LISTEN sockets: Recv-Q represents the current number of completed handshakes waiting in the accept queue (accept()), while Send-Q represents the maximum listen backlog limit (somaxconn).
  • In ESTABLISHED sockets: Recv-Q and Send-Q represent actual payload data bytes buffered in the kernel network queues.

2. Socket Memory Details (skmem)

The skmem structure exposes kernel memory allocation for this single socket:

  • r0: Receive buffer memory currently used ($0 ext{ bytes}$).
  • rb131072: Maximum receive buffer limit (SO_RCVBUF $= 128 ext{ KB}$).
  • t0: Transmit memory currently committed ($0 ext{ bytes}$ in pure payload buffers).
  • tb524288: Transmit buffer limit (SO_SNDBUF $= 512 ext{ KB}$).
  • f4096: Forward-allocated memory cache ($4 ext{ KB}$ preallocated from page pool).
  • w262144: Memory committed for write buffers ($256 ext{ KB}$).
  • o0: Option/timer memory overhead.
  • bl0: Backlog queue memory used during socket lock contention.
  • d0: Dropped packet counter at socket layer.

3. TCP Internals & Congestion State

  • cubic: Active TCP congestion control algorithm.
  • wscale:7,7: Window scale factor negotiation ($2^7 = 128$ multiplier for both send and receive windows).
  • rtt:14.2/0.8: Smoothed Round-Trip Time ($14.2 ext{ ms}$) and RTT variance ($0.8 ext{ ms}$).
  • cwnd:10: Congestion Window ($10 imes ext{MSS} = 14,600 ext{ bytes}$). A low cwnd on an established high-bandwidth link indicates recent packet loss or congestion backoff!
  • bytes_retrans:43800: Total retransmitted bytes ($43.8 ext{ KB}$).
  • retrans:1/30: 1 current segment unacknowledged out of 30 in flight.

4. The SRE Diagnostic Matrix: Root-Cause Classification

When you observe non-zero Recv-Q or Send-Q values, use this decision matrix to pinpoint the root cause immediately:

Signal / SymptomQueue StateUnderlying MechanismRoot Cause & Resolution
Application Consumer StallRecv-Q high & growing; Send-Q $= 0$Application event loop blocked; thread pool exhausted; synchronous DB lock.Application cannot call recv() fast enough. Profile application CPU/threads; do not touch network sysctls.
Downstream Peer / Client BottleneckSend-Q high; rwnd low/zero; cwnd normalRemote peer's receive buffer is full; peer advertised window shrinking (rwnd = 0).Client/downstream consumer is slow. Throttling is working as designed by TCP flow control.
Network Congestion / Packet LossSend-Q high; cwnd collapsed; retrans risingPackets dropped on network path; TCP retransmission timer (rto) firing; fast recovery active.Physical network loss, MTU mismatch, route flapping, or ISP drop. Check ethtool -S and traceroutes.
Listen Backlog OverflowRecv-Q $ge$ Send-Q on LISTEN portApplication cannot invoke accept() quickly enough to drain completed 3-way handshakes.Increase net.core.somaxconn and application listen backlog; scale worker processes.
Global TCP Memory Saturationdmesg emits "TCP: out of memory"; skmem d > 0Sockets consuming more memory than net.ipv4.tcp_mem hard threshold.Increase tcp_mem or reduce per-socket buffer maximums (tcp_rmem / tcp_wmem).

5. Linux Kernel TCP Buffer Memory Configuration

Linux automatically tunes socket buffer sizes dynamically based on connection throughput and RTT. However, the limits within which the kernel operates are governed by five primary sysctl parameters.

                  LINUX TCP BUFFER HIERARCHY
┌─────────────────────────────────────────────────────────────┐
│ Global TCP Memory Budget: net.ipv4.tcp_mem (Units: PAGES!)  │
│  [ min (178,000)  │  pressure (238,000)  │  max (357,000) ] │
└──────────────────────────────┬──────────────────────────────┘
                               │
            Governs All Concurrent Sockets
                               │
            ┌──────────────────┴──────────────────┐
            ▼                                     ▼
┌──────────────────────────────┐┌─────────────────────────────┐
│  net.ipv4.tcp_rmem (BYTES)   ││  net.ipv4.tcp_wmem (BYTES)  │
│  [ min  │  default  │  max ] ││  [ min  │  default  │  max ]│
│  [4096  │   131072  │ 6291456]││  [4096  │   16384   │4194304]│
└──────────────┬───────────────┘└─────────────┬───────────────┘
               │                              │
               ▼                              ▼
      Clamped by OS Max             Clamped by OS Max
     net.core.rmem_max             net.core.wmem_max

Inspecting Current Kernel Buffer Configurations

# 1. Per-Socket Receive Buffer Autotuning (min, default, max in bytes)
sysctl net.ipv4.tcp_rmem
# Sample: net.ipv4.tcp_rmem = 4096 131072 6291456

# 2. Per-Socket Transmit Buffer Autotuning (min, default, max in bytes)
sysctl net.ipv4.tcp_wmem
# Sample: net.ipv4.tcp_wmem = 4096 16384 4194304

# 3. System-wide Global TCP Memory (min, pressure, max in 4096-BYTE PAGES!)
sysctl net.ipv4.tcp_mem
# Sample: net.ipv4.tcp_mem = 186198 248265 372396

# 4. OS-Level Absolute Maximum Socket Buffer Ceilings (in bytes)
sysctl net.core.rmem_max net.core.wmem_max

⚠️ THE tcp_mem PAGE TRAP: Notice that tcp_rmem and tcp_wmem are configured in bytes, but tcp_mem is strictly measured in kernel memory pages (typically 4096 bytes per page)! If tcp_mem shows 372396, the maximum memory allocated to TCP across the entire host is: $$372,396 ext{ pages} imes 4,096 ext{ bytes/page} = 1,525,334,016 ext{ bytes} approx mathbf{1.42 ext{ GB}}$$


6. Bandwidth-Delay Product (BDP) & Memory Cost Modeling

Setting TCP buffer limits is an engineering tradeoff between maximizing single-connection throughput and preventing host-wide Out-Of-Memory (OOM) crashes.

The Bandwidth-Delay Product (BDP) Formula

To fill a network pipe without stalling, the TCP socket buffer must be at least as large as the Bandwidth-Delay Product:

$$ ext{BDP (Bytes)} = rac{ ext{Bandwidth (Bits/sec)} imes ext{RTT (Seconds)}}{8}$$

Example: Transatlantic 10 Gbps Link with 80 ms RTT

$$ ext{BDP} = rac{10 imes 10^9 ext{ bps} imes 0.080 ext{ s}}{8} = mathbf{100,000,000 ext{ Bytes} approx 95.3 ext{ MB}}$$

If tcp_rmem or tcp_wmem maximums are set to the default $4 ext{ MB}$, a single TCP stream across this link will never exceed: $$ ext{Max Throughput} = rac{4 ext{ MB} imes 8}{0.080 ext{ s}} = mathbf{400 ext{ Mbps}}$$ (Leaving over $95%$ of your 10 Gbps pipe unutilized!)

The High-Concurrency RAM Multiplication Risk

While large buffers are essential for long-haul high-speed links, applying massive buffer limits to a public-facing API server handling 100,000 concurrent clients will crash your server:

Per-Socket Memory Footprint:
  M_socket = R_buffer + W_buffer + M_sk_buff_overhead (approx 2x payload buffer)

Total System Memory:
  M_total = N_connections × M_socket
Concurrent Sockets ($N$)Buffer Size (tcp_rmem max)Total Potential RAM ConsumedRisk Assessment
1,000$4 ext{ MB}$$4 ext{ GB}$Safe on standard 16 GB servers
50,000$4 ext{ MB}$$200 ext{ GB}$Catastrophic Host OOM unless memory limits clamp autotuning
50,000$128 ext{ KB}$ (tuned)$6.4 ext{ GB}$Production-stable for high-concurrency gateways
100,000$64 ext{ KB}$ (tuned)$6.4 ext{ GB}$High-density edge proxy configuration

7. Production Diagnostic Shell Script

Save this copy-pasteable triage script to quickly capture the full TCP state during a live production incident:

#!/usr/bin/env bash
# ==============================================================================
# Pingzoapp TCP Socket Health & Buffer Saturation Triage Script
# ==============================================================================
set -euo pipefail

echo "======================================================================"
echo " 1. SYSTEM TCP SUMMARY & SOCKET POPULATION"
echo "======================================================================"
ss -s

echo -e "
======================================================================"
echo " 2. TOP 10 SOCKETS WITH LARGEST RECEIVE QUEUES (Recv-Q)"
echo "    High Recv-Q = Application reader is slow or blocked"
echo "======================================================================"
ss -tanp state established | sort -k2 -rn | head -11

echo -e "
======================================================================"
echo " 3. TOP 10 SOCKETS WITH LARGEST SEND QUEUES (Send-Q)"
echo "    High Send-Q = Network loss, congestion, or slow remote peer"
echo "======================================================================"
ss -tanp state established | sort -k3 -rn | head -11

echo -e "
======================================================================"
echo " 4. LISTEN SOCKET BACKLOGS (Recv-Q >= Send-Q means connection drops!)"
echo "======================================================================"
ss -ltnp

echo -e "
======================================================================"
echo " 5. GLOBAL TCP MEMORY & KERNEL CONFIGURATION"
echo "======================================================================"
echo "tcp_rmem (min/default/max bytes): $(sysctl -n net.ipv4.tcp_rmem)"
echo "tcp_wmem (min/default/max bytes): $(sysctl -n net.ipv4.tcp_wmem)"
echo "tcp_mem  (min/pressure/max pages): $(sysctl -n net.ipv4.tcp_mem)"
echo "somaxconn (max listen queue):     $(sysctl -n net.core.somaxconn)"

echo -e "
======================================================================"
echo " 6. TCP KERNEL COUNTERS & RETRANSMISSIONS (nstat)"
echo "======================================================================"
nstat -az | grep -Ei 'TcpRetransSegs|TcpExtListenOverflows|TcpExtListenDrops|TcpInErrs|TcpOutRsts' || true

8. Packet-Level Verification with tcpdump

To verify whether a stalled Send-Q is caused by zero-window advertisements from a remote client or packet drops on the wire, capture live TCP window negotiations:

# Capture traffic on port 443 displaying Window Scale and TCP Flags
sudo tcpdump -i any -nn -tttt 'tcp port 443 and (tcp[tcpflags] & (tcp-syn|tcp-fin|tcp-rst) != 0 or tcp[14:2] = 0)'

What to Look For:

  1. win 0 (Zero-Window Probe):
    14:20:10.120 IP 198.51.100.84.54210 > 10.0.4.12.443: Flags [.], ack 1805400, win 0, length 0
    
    The client is explicitly advertising a receive window of $0$. The Linux kernel halts transmission immediately, causing your local Send-Q to back up.
  2. Duplicate ACKs / Fast Retransmissions: Multiple identical ACK packets indicate lost segments in flight between your host and the client.

9. Production Monitoring & PromQL Alerting Rules

Incorporate TCP socket metrics into your Prometheus / Grafana stack via node_exporter:

groups:
  - name: tcp_socket_alerts
    rules:
      # 1. Listen Queue Overflow (Connections dropped during handshake)
      - alert: HostTCPListenOverflows
        expr: rate(node_netstat_TcpExt_ListenOverflows[2m]) > 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "TCP Listen Queue Overflow on {{ $labels.instance }}"
          description: "Kernel is dropping new incoming TCP connections because the application accept backlog is full."

      # 2. Elevated TCP Retransmission Rate (> 3% of sent segments)
      - alert: HostHighTCPRetransmissionRate
        expr: |
          100 * (rate(node_netstat_Tcp_RetransSegs[5m]) / rate(node_netstat_Tcp_OutSegs[5m])) > 3
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Elevated TCP Retransmissions on {{ $labels.instance }}"
          description: "TCP retransmission rate is {{ $value | printf '%.2f' }}% (>3%), indicating network packet loss or congestion."

      # 3. TCP Memory Under Pressure
      - alert: HostTCPMemoryPressure
        expr: node_sockstat_TCP_mem * 4096 > (node_memory_MemTotal_bytes * 0.20)
        for: 3m
        labels:
          severity: warning
        annotations:
          summary: "TCP Socket Memory exceeds 20% of total host RAM on {{ $labels.instance }}"
          description: "High concurrency is consuming significant memory in TCP socket buffers."

10. Common Misdiagnoses & Anti-Patterns

Common AssumptionTechnical RealityCorrect Action
"Send-Q is full, so our network bandwidth is saturated."A full Send-Q frequently indicates a slow remote client advertising win 0, not local network bandwidth exhaustion.Inspect ss -tin to check the peer's advertised rwnd.
"Recv-Q is full, so we must increase tcp_rmem."A full Recv-Q means the local application is not calling recv(). Increasing buffer size merely delays the inevitable drop by a few milliseconds.Profile application CPU bottlenecks, thread pool starvation, or event loop blocking.
"Blindly increasing tcp_rmem and tcp_wmem to 64MB fixes slow APIs."Multiplying buffer ceilings across 50,000 connections can instantly trigger kernel OOM panics.Size buffers according to actual BDP: $ ext{Bandwidth} imes ext{RTT}$.
*"Running `netstat -angrep :80` is harmless in production."*Parsing /proc/net/tcp on a host with 100k+ sockets locks kernel data structures and spikes latency.

Conclusion & Actionable SRE Checklist

Diagnosing TCP socket buffer saturation is about understanding which side of the pipe is exerting backpressure.

The 5-Step Incident Checklist:

  1. Run ss -s: Check active connection counts and TCP memory state.
  2. Scan Queues with ss -tan: Determine if backpressure is on Recv-Q (application stall) or Send-Q (network/client stall).
  3. Inspect Internals with ss -tinm: Examine cwnd, rtt, retrans, and skmem limits.
  4. Correlate with Application Metrics: Verify worker thread utilization and event loop delay.
  5. Tune Sockets Safely: Calculate BDP before adjusting sysctl parameters, ensuring total RAM footprint remains within host limits.

Monitor End-to-End Latency & TCP Socket Health with Pingzoapp

When network degradation, packet loss, or kernel buffer saturation impacts your users, synthetic uptime checks are your first line of defense.

With Pingzoapp, you get:

  • Sub-Minute Multi-Region Probing: Monitor TCP connect times, TLS handshake durations, and HTTP response latencies from global edge locations.
  • Instant WhatsApp & Multi-Channel Escalations: Get notified on WhatsApp, Telegram, Slack, and Discord the instant edge connections begin dropping.
  • Public & Private Status Pages: Maintain user trust with automated, transparent incident communication.

👉 Start Monitoring Free with Pingzoapp and catch socket bottlenecks before they impact your customers.

Zero-Code Uptime Alerts

Stop Finding Out About Outages from Angry Users

Get instant WhatsApp & Discord alerts the second your API, website, or server goes down. Setup in 30 seconds with 60-second checks.

WhatsApp & Discord 60-Second Checks Free Forever Plan
Try Pingzo Free

Know before your users do

Connect official WhatsApp notification channels, Discord webhooks, Telegram bots, and public status pages. Start in 30 seconds.

Create Free Monitor