Linux ss command — viewing TCP cwnd and internal socket statistics

// Linux `ss` command — viewing TCP `cwnd` and internal socket statistics

Linux ss command — viewing TCP cwnd and internal socket statistics

Basic command

ss -tni
FlagMeaning
-tTCP sockets only
-nNo name resolution
-iShow internal TCP info (exposes cwnd and other stats)

Example output

ESTAB 0 0 <local-ip>:<local-port> <remote-ip>:<remote-port>
     cubic wscale:7,7 rto:204 rtt:0.5/0.25 ato:40 mss:1448 pmtu:1500 rcvmss:1448
     advmss:1448 cwnd:10 ssthresh:8 bytes_sent:1234567 bytes_retrans:0 ...

Field reference (confirmed against iproute2 man page)

Socket state line

FieldMeaning
ESTABTCP state (ESTABLISHED, TIME-WAIT, CLOSE-WAIT, etc.)
0 (1st)Send queue — bytes sent but not yet ACK'd
0 (2nd)Receive queue — bytes received but not yet read by app
local:portLocal IP and port
peer:portRemote IP and port

TCP internals line

Congestion & flow control

FieldMeaning
cubicCongestion control algorithm (cubic, bbr, reno, etc.)
cwnd:10Current congestion window — segments in-flight sender can have without waiting for ACK (in MSS units)
ssthresh:8Slow-start threshold — above this, switch from slow-start to congestion avoidance

Sizing

FieldMeaning
wscale:7,7TCP window scale — snd_wscale,rcv_wscale. Actual window = advertised × 2^scale
mss:1448Max segment size in use (bytes)
pmtu:1500Path MTU — smallest MTU discovered on the path
rcvmss:1448MSS of the last received segment (inferred from incoming data)
advmss:1448MSS this socket advertised to the peer in the SYN

Timing

FieldMeaning
rto:204Retransmission timeout (ms)
rtt:0.5/0.25Smoothed RTT / mean deviation (ms)
ato:40Delayed ACK timeout (ms)
backoffExponential backoff multiplier — actual RTO = rto << backoff; non-zero means in retransmit backoff

Byte/segment counters

FieldMeaning
bytes_sentTotal bytes sent
bytes_retransBytes retransmitted — non-zero indicates packet loss
bytes_ackedBytes confirmed received by peer
bytes_receivedBytes received from peer
segs_out / segs_inSegments sent / received

cwnd is the current window, not the initial

cwnd reflects the live state of the congestion control algorithm at the instant ss ran. It changes continuously:

Phasecwnd behavior
Connection startInitialized to Initial Window (IW) — Linux default: 10 MSS (RFC 6928)
Slow startDoubles each RTT
Congestion avoidanceGrows ~1 MSS per RTT
Loss detectedCut sharply (CUBIC: multiplicative decrease)
Idle connectionReset toward IW (tcp_slow_start_after_idle=1 default)

> Why idle NFS connections often show cwnd:10: that's the IW floor after kernel reset, not a fixed setting.

Useful one-liners

# Filter to NFS traffic
ss -tni '( dport = :2049 or sport = :2049 )'

# Filter to a specific server
ss -tni dst <server-ip>

# Watch cwnd live on an active connection
watch -n0.1 'ss -tni dst <server-ip> | grep cwnd'

Converting cwnd to bytes

cwnd is in MSS units, not bytes:

bytes in flight = cwnd × mss
# e.g., cwnd:10, mss:1448 → ~14 KB

Check / set the Initial Window on a route

# View current IW
ip route show | grep initcwnd

# Set IW on default route
ip route change default via <gw> initcwnd 10

initcwnd (route attribute) is what a new connection starts at. cwnd in ss is where it is right now.

Diagnostic quick-reference

Symptomss indicator
Throughput plateaucwnd stuck low with no loss
Packet lossbytes_retrans > 0, backoff > 0
RTT inflationrtt higher than expected for path
Receiver-limitedSend-Q > 0, cwnd large, but low throughput

Source

Field definitions verified against iproute2 man page: https://manpages.ubuntu.com/manpages/bionic/man8/ss.8.html

TERMINAL comments.log
# opening comments.log…