TCP State Machine
Overview
Section titled “Overview”The TCP connection state machine (defined in RFC 793, with updates in RFC 1122) is one of the most Precisely specified protocol behaviors in all of networking. Every TCP endpoint transitions through Defined states as connections are established, used, and torn down. Understanding these states is Essential for troubleshooting connection issues, writing robust network software, and operating High-performance servers.
This document maps every state, every transition, every edge case (simultaneous open, simultaneous Close, half-open connections), and provides practical guidance for diagnosing TCP state problems in Production.
The 11 TCP States
Section titled “The 11 TCP States”TCP defines 11 states. An endpoint is always in exactly one of these states:
| State | Description |
|---|---|
| CLOSED | No connection. Initial state. |
| LISTEN | Waiting for connection requests. |
| SYN_SENT | Sent SYN, waiting for response. |
| SYN_RCVD | Received SYN, sent SYN+ACK, waiting for ACK. |
| ESTABLISHED | Connection open. Data transfer in progress. |
| FIN_WAIT_1 | Initiated close, sent FIN, waiting for ACK or FIN. |
| FIN_WAIT_2 | Received ACK for our FIN, waiting for peer”s FIN. |
| CLOSE_WAIT | Received peer’s FIN, waiting for local close. |
| LAST_ACK | Sent our FIN after peer closed, waiting for ACK. |
| TIME_WAIT | Waited for peer’s ACK after our close. 2*MSL timer. |
| CLOSING | Both sides sent FIN simultaneously, waiting for ACK. |
State Diagram
Section titled “State Diagram”stateDiagram-v2
[*] --> CLOSED
CLOSED --> LISTEN : passive_open
CLOSED --> SYN_SENT : send_SYN
LISTEN --> SYN_RCVD : recv_SYN, send_SYN+ACK
SYN_SENT --> ESTABLISHED : recv_SYN+ACK, send_ACK
SYN_SENT --> SYN_RCVD : recv_SYN, send_SYN+ACK
SYN_RCVD --> ESTABLISHED : recv_ACK
LISTEN --> SYN_SENT : send_SYN
ESTABLISHED --> FIN_WAIT_1 : close, send_FIN
ESTABLISHED --> CLOSE_WAIT : recv_FIN, send_ACK
FIN_WAIT_1 --> FIN_WAIT_2 : recv_ACK
FIN_WAIT_1 --> CLOSING : recv_FIN, send_ACK
FIN_WAIT_1 --> TIME_WAIT : recv_FIN+ACK, send_ACK
FIN_WAIT_2 --> TIME_WAIT : recv_FIN, send_ACK
CLOSE_WAIT --> LAST_ACK : close, send_FIN
LAST_ACK --> CLOSED : recv_ACK
CLOSING --> TIME_WAIT : recv_ACK
TIME_WAIT --> CLOSED : 2*MSL timeoutNormal Open: The Three-Way Handshake
Section titled “Normal Open: The Three-Way Handshake”The three-way handshake establishes both ends of the connection and synchronizes sequence numbers. Both sides must agree on initial sequence numbers (ISNs) before any data can be exchanged.
State Trace: Client Side
Section titled “State Trace: Client Side”State: CLOSED | |--- SYN (seq=1000) ---------------->| State: SYN_SENT | |<-- SYN+ACK (seq=2000, ack=1001) ---| State: ESTABLISHED (after sending ACK) | |--- ACK (ack=2001) ---------------->| State: ESTABLISHEDState Trace: Server Side
Section titled “State Trace: Server Side”State: LISTEN | |<-- SYN (seq=1000) -----------------| State: SYN_RCVD | |--- SYN+ACK (seq=2000, ack=1001) -->| | |<-- ACK (ack=2001) -----------------| State: ESTABLISHEDWhy Three Messages, Not Two?
Section titled “Why Three Messages, Not Two?”A two-way handshake would be vulnerable to stale duplicate SYNs from old connections. Consider:
- Client sends SYN (seq=1000). Network delay causes it to arrive late.
- Client times out, sends new SYN (seq=3000). Server responds, connection established.
- Data exchanged, connection closed.
- The stale SYN (seq=1000) finally arrives. Server creates a half-open connection.
- Client receives SYN+ACK for a connection it never intended, sends RST.
The three-way handshake prevents this because the client must acknowledge the server’s ISN. The Stale SYN would trigger a SYN+ACK from the server, but the client’s ACK would not match, and the Client would send RST.
ISN Generation
Section titled “ISN Generation”ISNs must be unpredictable to prevent TCP sequence prediction attacks (Blind Spoofing, RFC 6528). Modern implementations use a cryptographically strong PRNG seeded with a combination of:
- Source and destination IP addresses and ports
- A secret key (per-boot random)
- A high-resolution timestamp
Linux uses a 64-bit counter that increments by 1 for each microsecond and by 64,000 for each new Connection, making ISN prediction computationally infeasible.
# View Linux ISN generation parameterssysctl net.ipv4.tcp_timestampssysctl net.ipv4.tcp_syncookiesNormal Close: The Four-Way Teardown
Section titled “Normal Close: The Four-Way Teardown”TCP uses a four-way (full-duplex) close because each direction of the connection must be closed Independently. This is called a “half-close” — one side can stop sending while still receiving.
State Trace: Active Closer (Initiator)
Section titled “State Trace: Active Closer (Initiator)”State: ESTABLISHED | |--- FIN (seq=5000) ---------------->| State: FIN_WAIT_1 | |<-- ACK (ack=5001) -----------------| State: FIN_WAIT_2 | (waiting for peer to close its side) | |<-- FIN (seq=3000) -----------------| State: TIME_WAIT (after sending ACK) | |--- ACK (ack=3001) ---------------->| State: TIME_WAIT (wait 2*MSL)State Trace: Passive Closer
Section titled “State Trace: Passive Closer”State: ESTABLISHED | |<-- FIN (seq=5000) -----------------| State: CLOSE_WAIT | (application can still send data) | |--- ACK (ack=5001) ---------------->| State: CLOSE_WAIT (still, waiting for app to close) | |--- FIN (seq=3000) ---------------->| State: LAST_ACK | |<-- ACK (ack=3001) -----------------| State: CLOSEDFIN_WAIT_2 and Application Hang
Section titled “FIN_WAIT_2 and Application Hang”FIN_WAIT_2 is the state where the active closer has finished sending but is waiting for the peer to Finish. If the peer never sends FIN (application crash, bug, network partition), the endpoint stays In FIN_WAIT_2 indefinitely.
Linux protects against this with tcp_fin_timeout (default 60 seconds). After this timeout, the Kernel forcibly closes the connection and frees the resources.
# View and adjust FIN_WAIT_2 timeoutsysctl net.ipv4.tcp_fin_timeout
# Count connections in FIN_WAIT_2 statess -tan state fin-wait-2 | wc -l