3.1 TCP Connection Establishment, Termination, and TIME_WAIT
A socket lets a program contact another endpoint, and an ordinary handshake exchanges initial sequence numbers and establishes state before normal application-data delivery. One direction may close before the other. Diagnostic tools therefore expose states such as SYN-SENT, CLOSE-WAIT, and TIME-WAIT; memorizing a "four-way handshake" does not explain why a connection remains in one of them.
The intuitive idea of "a single connection pipe" remains useful, but it's not precise enough. TCP maintains separate sequence number spaces, receive and send windows, retransmission timers, acknowledged ranges, and closure progress at each endpoint. The three-way handshake synchronizes initial sequence numbers and connection states between both sides, while a FIN packet only closes one direction of transmission.
This lesson follows the timeline from connection establishment to release. The next lesson explains how sequence numbers, ACKs, and flow control reconstruct a reliable, ordered byte stream from lost or out-of-order IP packets.
1. What Identifies a Connection
A common TCP flow is typically identified by a four-tuple:
source IP, source port, destination IP, destination portThe protocol number (e.g., TCP) often completes the five-tuple used in network devices. Servers can allow many connections to share the same local port, since each connection has a unique remote address and port.
NAT, proxies, and load balancers may rewrite or terminate flows, so the four-tuple seen by clients, edge proxies, and backend services might differ. Application-level request IDs are more effective than socket four-tuples for tracing business requests across proxies.
2. Three-Way Handshake Exchanges Two Sequence Number Spaces
Client initiates the connection:
Client Server
| |
| SYN, seq = x |
|---------------------------------------->|
| |
| SYN + ACK, seq = y, ack = x + 1 |
|<----------------------------------------|
| |
| ACK, ack = y + 1 |
|---------------------------------------->|
| ESTABLISHED |Each direction independently selects an initial sequence number (ISN). The SYN packet consumes one sequence number, so the acknowledgment value is ISN + 1.
The third ACK confirms to the server that the client received its SYN and ISN. The endpoints enter ESTABLISHED at different times: the client can enter after a valid SYN-ACK, while the server normally waits for the final ACK. This exchange establishes sequence state and rejects some stale connection attempts. It neither guarantees future path availability nor authenticates identity.
The final ACK may carry application data. TCP Fast Open allows application data to be sent earlier under specific conditions, but introduces replay and middlebox compatibility boundaries. Applications cannot assume that the first data packet will be processed only once.
3. When connect and accept Return
Blocking connect typically waits until the handshake succeeds or fails; nonblocking connect usually returns immediately with an in-progress state, and the application must later check for writable or error readiness to determine the final outcome of SO_ERROR.
A successful client connect therefore does not prove that the server application has accepted, or even that the final ACK has arrived. The queue sequence below describes ordinary TCP; Fast Open and deferred-accept options can change application-visible timing.
On the server side:
- The listening endpoint receives a SYN packet;
- The kernel creates or encodes a half-open connection state and responds with a SYN-ACK;
- Upon receiving a valid ACK, the connection moves into a queue ready for
accept; - The application calls
acceptto obtain the connected socket.
The implementation may separately manage half-completed handshakes and fully completed connections awaiting accept, with the backlog constrained by kernel configuration. Receiving a SYN-ACK does not mean the application has already accepted the connection; seeing a completed handshake does not imply that the business thread has begun reading.
4. Handshake Failures in Packet Captures
SYN retransmission followed by timeout. Common when requests or responses are dropped, routing black holes occur, or firewalls silently drop packets. A client-side timeout alone cannot determine whether the packet was lost in the outbound or inbound direction.
Immediate RST. The destination may have no listener, or an intermediate device may reject the attempt. A reset alone does not prove the SYN reached the destination host. Applications typically see a "connection refused" error.
SYN-ACK received, but final ACK lost. The server retransmits the SYN-ACK; the client then re-acknowledges. The protocol relies on retransmission to tolerate such packet loss.
Incorrect address selection. DNS returns multiple addresses, and the first candidate is unreachable while the application fails to promptly try alternative address families. Connection strategies should be aligned with Happy Eyeballs and a global deadline.
Packet capture must be performed on both ends: a client's SYN does not guarantee that the server has received it, and a server's SYN-ACK does not confirm that the client or its kernel has accepted it.
5. Connection State Is Not a Simple Line
Common states:
| State | Meaning |
|---|---|
LISTEN | Waiting for an incoming connection |
SYN-SENT | Actively opened, waiting for SYN/ACK |
SYN-RECEIVED | Received SYN, sent SYN-ACK |
ESTABLISHED | Handshake complete; data transmission is allowed |
FIN-WAIT-1 / FIN-WAIT-2 | FIN sent; the first awaits its ACK, the second has that ACK and awaits peer FIN |
CLOSE-WAIT | Received peer's FIN, local application has not yet closed the send direction |
LAST-ACK | Passive close side has sent FIN, waiting for final ACK |
CLOSING | Peer FIN received before our own FIN was acknowledged |
TIME-WAIT | Active close path retained; absorbs old segments and handles final retransmissions |
State names belong to the TCP endpoint, not to the entire application. A single process can maintain thousands of connections in different states simultaneously.
6. FIN Closes One Direction
When a client sends a FIN, it signals that "no more bytes will be sent in this direction," but it does not mean the client refuses to receive:
Client Server
| FIN ----------------------->| client send direction ends
|<----------------------- ACK |
|<----------- response data |
|<----------------------- FIN | server send direction ends
| ACK ----------------------->|This is a half-close. The protocol allows a client to send its request and then shutdown(SHUT_WR), continuing to read the server's response.
The common notion of a "four-way handshake" is a typical timeline; it does not require four separate packets. ACKs can be combined with data or FINs, and both sides may close simultaneously.
A FIN occupies a sequence number, and the receiving end only signals EOF to the application after all prior bytes have arrived in order.
Apply sequence arithmetic to the 12-byte request and response from chapter 2. Let the client ISN be 1000 and the server ISN 7000. This Python 3 calculation tracks sequence numbers without sending packets:
MOD = 1 << 32
client_isn, server_isn = 1000, 7000
client_next = (client_isn + 1) % MOD
server_next = (server_isn + 1) % MOD
assert (client_next, server_next) == (1001, 7001)
client_next = (client_next + len(b"hello socket")) % MOD
client_fin = client_next
ack_client_fin = (client_fin + 1) % MOD
server_next = (server_next + len(b"HELLO SOCKET")) % MOD
server_fin = server_next
ack_server_fin = (server_fin + 1) % MOD
print(client_fin, ack_client_fin) # 1013 1014
print(server_fin, ack_server_fin) # 7013 7014An ACK-only segment consumes no sequence number. After acknowledging the request FIN, the server’s response still starts at 7001; when the client acknowledges the server FIN, its own sequence is 1014. Capture tools using relative numbers subtract a per-direction reference, so those values need not match these absolute numbers.
7. Persistent CLOSE_WAIT and Application Cleanup
Entering CLOSE-WAIT means:
- The remote end has terminated transmission;
- The local kernel has processed the peer FIN; the application sees EOF after draining buffered bytes;
- The local application has not yet closed its direction.
A small number of brief CLOSE_WAIT states is normal. A growing number typically indicates:
- Forgot to close after reaching EOF;
- The socket is not released on the exception/cancel path;
- Workers are stuck in downstream operations, delaying connection cleanup;
- fd is still referenced by another object or thread;
- The TLS/protocol state machine is waiting for an application event that will never come.
Adjusting TCP timeout can't replace an application's need to call close. You must trace back to the file descriptor ownership and call stack.
The local endpoint can still send a response in CLOSE_WAIT. A protocol may intentionally keep a direction open, so the state alone is not an error. Check counts, duration, and when the application was supposed to finish.
8. Why TIME_WAIT Retains State
The connection is closed, but the final acknowledgment may be lost and an old segment may arrive late. The active closer cannot discard state immediately; otherwise it cannot answer a retransmitted FIN, and a new connection with the same four-tuple might accept a stale segment.
For this reason, the side that sends the final ACK typically enters TIME_WAIT and retains a protocol state for approximately 2×MSL. Its primary purposes are:
- If the final ACK is lost, the remote endpoint will retransmit the FIN, and the local side can still respond with an ACK;
- Allow stale duplicate segments from the same four-tuple to expire in the network, preventing them from corrupting a later connection that reuses the same endpoint.
The exact duration is an implementation detail and should not be treated as a permanent TCP constant. TIME_WAIT does not mean the application process still holds a connected file descriptor, nor does it equate to "connection leakage."
With simultaneous close, both endpoints can enter TIME_WAIT; a reset can take a different release path. The 2×MSL rule refers to the protocol’s maximum-segment-lifetime model, not the measured RTT.
A high volume of short-lived connections can generate a large number of TIME_WAIT states, which (combined with ephemeral port exhaustion, NAT table limits, or load-balancer state capacity) can become a system bottleneck. Prioritize connection reuse, reasonable source address and port allocation, and well-designed proxy architectures. Avoid prematurely shortening the TIME_WAIT period using dangerous sysctl settings before you fully understand the protocol's protective requirements.
9. RST Aborts a Connection
An established connection aborts after accepting a valid RST, potentially failing pending I/O and leaving application messages incomplete. The reset must pass checks involving connection state and sequence numbers. Seeing an RST in a capture does not prove the kernel accepted it. Common sources include:
- Connecting to a port without a listener;
- The endpoint does not exist or is in an inconsistent state;
- The application performs an explicit
SO_LINGER-configured abortive close; - The process terminates while still holding unprocessed incoming data, and certain stack implementations choose to reset the connection;
- A middlebox generates or fakes a reset.
A successful send followed by local close does not confirm peer processing. A send can also appear to succeed before the local stack learns of peer closure. Reliable business operations need an application response or acknowledgment.
SO_LINGER has cross-platform semantics and is prone to causing blocking closes or RSTs; it should not be used as a general technique for rapidly releasing ports.
10. Keepalive and Application Heartbeats
TCP keepalive is a kernel-level mechanism for probing idle connections. Whether it's enabled, how long it waits before initiating a probe, and the retry interval and count are determined by system and socket configurations. It's effective at reclaiming long-idle, silent peers, but its time scale may not align with the actual business requirements for detecting failures.
Application-level heartbeats can carry protocol state and use application deadlines, but they introduce additional traffic and risk of false positives. If the event loop stalls, the network experiences brief fluctuations, or garbage collection pauses, a heartbeat timeout does not necessarily indicate that a node has permanently failed.
Neither keepalive nor application heartbeats can replace request-level timeouts. The fact that a connection is "still alive" does not mean that a specific request will complete within its expected time.
11. SYN Floods and State Resources
An attacker can send a large number of SYN packets without completing the handshake, consuming half-open connection state and response bandwidth. Mitigation strategies may include:
- SYN cookies or similar stateless encoding mechanisms;
- Reasonable queue limits and rate limiting;
- Edge scrubbing or load balancing;
- Source validation and network-side policies;
- Monitoring the ratio of SYN, SYN-ACK, completed handshakes, and accepted connections.
SYN cookies trade connection state management for limited connection options or constraints. The specific implementation evolves with the system. Do not treat "enabling cookies" as the complete solution for capacity planning or DDoS protection.
12. Align Diagnostic Evidence with Kernel State
On Linux, begin diagnosis with ss and packet capture. The former reads local socket state, while the latter records segments passing through a particular capture point. Each provides only part of the evidence, so their timelines must be aligned.
On Linux, inspect local sockets with:
ss -lnt
ss -ntipo
ss -sFields and permissions vary by kernel version. The key is aligning the following elements with the packet capture timeline:
- local and peer endpoints
- TCP state
- associated process and file descriptor
- queue state
- timers and retransmission activity
ss only reveals the local kernel state. After proxying, front-end and back-end connections become two distinct TCP flows and must be observed separately.
13. Common Misconceptions
"Three-way handshake verifies a username." It establishes TCP sequence numbers and connection state, not application-level identity authentication.
"Receiving an ACK means the application has processed the data." A TCP ACK indicates receipt of a byte range by the protocol stack, not that the application has acknowledged or processed the data.
"Close simultaneously shuts down bidirectional transmission." A standard close ultimately releases the local reference; at the protocol layer, the two directions can independently send FIN packets.
"TIME_WAIT occurs only on the server." Which side initiates the active close matters more; architecture and protocol design can shift where this state occurs.
"As long as keepalive is enabled, there are no zombie connections." Keepalive parameters, network conditions, and application deadlines all influence when detection occurs.
14. Summary
The TCP lifecycle is a bidirectional state machine:
- It exchanges and confirms two initial sequence numbers through a three-way handshake;
- The states of listen, half-open connection, waiting for accept, and application processing represent distinct phases;
- A FIN only terminates one direction of transmission; the four packet sequence is not a fixed formula;
- CLOSE_WAIT often indicates that the local application has not completed cleanup;
- TIME_WAIT allows another ACK for a retransmitted FIN and isolates old segments;
- RST signifies an abnormal termination and does not guarantee that application messages are fully delivered;
- keepalive, heartbeat, and request deadline mechanisms address problems at different layers of the protocol stack.
The connection has completed establishment, data exchange, and release. The next section, 3.2 Sequence Numbers, ACKs, and Flow Control, examines how an ESTABLISHED connection reconstructs lost or out-of-order IP packets into a reliable byte stream.