Frontend Labs
⋯

26. The TCP finite state machine

26.1 The states

State Meaning
CLOSED No connection.
LISTEN Server waiting for SYNs.
SYN_SENT Client sent SYN, waiting for SYN-ACK.
SYN_RECV Server got SYN, sent SYN-ACK, waiting for final ACK (this is the SYN-queue state).
ESTABLISHED Data transfer.
FIN_WAIT_1 We sent FIN, waiting for its ACK (or peer's FIN).
FIN_WAIT_2 Our FIN ACKed; waiting for peer's FIN. (tcp_fin_timeout bounds this ~60 s to avoid leaks against a peer that never closes.)
CLOSING Both sent FIN ~simultaneously; waiting for ACK of ours.
TIME_WAIT We sent the last ACK; wait 2×MSL.
CLOSE_WAIT Peer sent FIN, we ACKed it; waiting for our app to call close().
LAST_ACK We (after CLOSE_WAIT) sent our FIN; waiting for its ACK.

26.2 The diagram (active vs passive paths)

                              CLOSED
                    active open |        \ passive open
                   send SYN     |         \  (listen())
                                v          v
        rcv SYN,ACK          SYN_SENT     LISTEN
        send ACK   +-----------|              | rcv SYN
                   |           | (simultaneous| send SYN,ACK
                   |            open path)    v
                   |                       SYN_RECV
                   |          rcv ACK  /       |
                   v                  /        |
             ESTABLISHED <-----------+         |
        ______/   |    \______                 
close()/send FIN  | rcv FIN / send ACK          
       v          |         v                   
   FIN_WAIT_1     |     CLOSE_WAIT               
   |    | rcv ACK |         | close()/send FIN   
   |    v         |         v                    
   | FIN_WAIT_2   |     LAST_ACK                 
   |    | rcv FIN |         | rcv ACK            
rcv FIN | send ACK|         v                    
send ACK|         |      CLOSED                  
   v    v                                        
  CLOSING -> (rcv ACK) -> TIME_WAIT -> (2MSL) -> CLOSED

26.3 How to read states operationally

  • Many SYN_RECV on a server = SYN flood or a client population that sends SYN but not the final ACK (broken NAT, PMTU black hole on the SYN-ACK).
  • Many SYN_SENT on a client = destination unreachable / firewalled / overloaded.
  • FIN_WAIT_2 piling up = the peer isn't closing its side (peer app bug or peer overload). tcp_fin_timeout will eventually reap them.
  • CLOSE_WAIT piling up = local app bug (didn't close()), §25.5.
  • LAST_ACK stuck = the final ACK from the peer isn't arriving (loss, peer gone).
  • TIME_WAIT large but stable = normal for a busy connection initiator; only act if you're near port exhaustion.

26.4 The kernel timers attached to a connection

Timer Fires when Effect
Retransmission (RTO) an unACKed segment's timer expires resend oldest unacked segment, back off RTO, cut congestion window (Ch. 28, 31)
Persist receiver advertised a zero window send tiny "window probes" so a lost window-update doesn't deadlock the connection (Ch. 30)
Keepalive connection idle for tcp_keepalive_time (default 7200 s) send a probe; after tcp_keepalive_probes failures, kill the connection (Ch. 34)
TIME_WAIT (2MSL) last ACK sent reap the TCB
FIN_WAIT_2 our FIN ACKed but no peer FIN reap after tcp_fin_timeout
Delayed ACK data received, nothing to piggyback on send a bare ACK after up to ~40–200 ms (Ch. 33)

26.5 War story

A batch job's worker connections sat in ESTABLISHED forever after the remote database VM was hard-killed (no FIN, no RST). The app blocked on read() indefinitely; the job hung for hours. Default keepalive is 2 hours idle + 9 probes × 75 s ≈ 2h11m before the kernel gives up — far too slow. Setting TCP_KEEPIDLE=30, TCP_KEEPINTVL=10, TCP_KEEPCNT=3 on the socket (or an application-level read deadline) made it fail in ~1 minute and retry. Lesson: the OS will not promptly tell you a peer vanished; set per-socket keepalive or app-level timeouts for anything long-lived.

Try it yourself

ss -tan | awk '{print $1}' | sort | uniq -c | sort -rn   # histogram of TCP states
watch -n1 'ss -tan state syn-recv | wc -l'
sysctl net.ipv4.tcp_keepalive_time net.ipv4.tcp_keepalive_intvl net.ipv4.tcp_keepalive_probes
ss -tie                                   # per-socket: state, timers, retrans count

Generated from real-life-example.md · 8 Oct 2026 16:36