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
| Flag | Meaning |
|---|---|
-t | TCP sockets only |
-n | No name resolution |
-i | Show 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
| Field | Meaning |
|---|---|
ESTAB | TCP 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:port | Local IP and port |
peer:port | Remote IP and port |
TCP internals line
Congestion & flow control
| Field | Meaning |
|---|---|
cubic | Congestion control algorithm (cubic, bbr, reno, etc.) |
cwnd:10 | Current congestion window — segments in-flight sender can have without waiting for ACK (in MSS units) |
ssthresh:8 | Slow-start threshold — above this, switch from slow-start to congestion avoidance |
Sizing
| Field | Meaning |
|---|---|
wscale:7,7 | TCP window scale — snd_wscale,rcv_wscale. Actual window = advertised × 2^scale |
mss:1448 | Max segment size in use (bytes) |
pmtu:1500 | Path MTU — smallest MTU discovered on the path |
rcvmss:1448 | MSS of the last received segment (inferred from incoming data) |
advmss:1448 | MSS this socket advertised to the peer in the SYN |
Timing
| Field | Meaning |
|---|---|
rto:204 | Retransmission timeout (ms) |
rtt:0.5/0.25 | Smoothed RTT / mean deviation (ms) |
ato:40 | Delayed ACK timeout (ms) |
backoff | Exponential backoff multiplier — actual RTO = rto << backoff; non-zero means in retransmit backoff |
Byte/segment counters
| Field | Meaning |
|---|---|
bytes_sent | Total bytes sent |
bytes_retrans | Bytes retransmitted — non-zero indicates packet loss |
bytes_acked | Bytes confirmed received by peer |
bytes_received | Bytes received from peer |
segs_out / segs_in | Segments 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:
| Phase | cwnd behavior |
|---|---|
| Connection start | Initialized to Initial Window (IW) — Linux default: 10 MSS (RFC 6928) |
| Slow start | Doubles each RTT |
| Congestion avoidance | Grows ~1 MSS per RTT |
| Loss detected | Cut sharply (CUBIC: multiplicative decrease) |
| Idle connection | Reset 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 KBCheck / 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 10initcwnd (route attribute) is what a new connection starts at. cwnd in ss is where it is right now.
Diagnostic quick-reference
| Symptom | ss indicator |
|---|---|
| Throughput plateau | cwnd stuck low with no loss |
| Packet loss | bytes_retrans > 0, backoff > 0 |
| RTT inflation | rtt higher than expected for path |
| Receiver-limited | Send-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