TCP Connection Teardown
TCP connection teardown is shown here as the four-segment exchange — FIN, ACK, FIN, ACK — that gracefully closes an established TCP connection. Each direction of the connection closes independently, the active closer walks FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT → CLOSED, the passive closer walks CLOSE_WAIT → LAST_ACK → CLOSED, and the closer that sent the first FIN lingers in TIME_WAIT for twice the maximum segment lifetime.
Think of it as ending a phone call between two polite people. You say "I'm done talking." The other person says "got it" — but they're not done, so they keep talking for a while, then say "okay, I'm done too," and you say "got it" back. Four separate utterances, because being done talking doesn't make the other person done. And then — this is the part everyone forgets — you stay on the line a few extra seconds, in case your last "got it" never made it through. That extra wait on the line is TIME_WAIT, and it does real work.
How TCP Connection Teardown Works
Opening a connection takes three segments; closing it takes four. The asymmetry comes from what a TCP connection actually is: two independent one-way byte streams, Client→Server and Server→Client, sharing one socket pair. Each stream closes on its own schedule.
One peer is the active closer — its application calls close() first; here that's the Client. The other is the passive closer. The active closer sends a FIN ("my stream is finished") and the passive closer acknowledges it immediately. But the passive closer's own stream may still be mid-sentence — buffered data, a response still being generated — so its ACK and its own FIN are two separate segments, possibly far apart in time. That gap is the half-close: one pipe shut, the other still flowing. The handshake fuses SYN and ACK into one segment because a fresh connection has nothing in flight; the teardown's middle ACK and FIN cannot be fused in general, which is why four waves instead of three.
Each segment drives one state transition on one of two ladders. The active closer descends ESTABLISHED → FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT → CLOSED; the passive closer descends ESTABLISHED → CLOSE_WAIT → LAST_ACK → CLOSED. Read the two ladders side by side and you have read the whole protocol.
The lesson uses symbolic sequence numbers — the Client's FIN carries seq = u, the Server's carries seq = w — rather than real 32-bit byte counters, and a FIN consumes one sequence number even though it carries no data. That's why the acknowledgments are u+1 and w+1, not u and w.
TCP Connection Teardown Step by Step
The default scenario is the Normal close, and it runs in thirteen beats:
- Both peers sit in
ESTABLISHED. The Client's application callsclose()— Client becomes the active closer, Server the passive one. - The Client sends segment 1:
FIN, seq = u. Client:ESTABLISHED → FIN_WAIT_1. - The Server receives the
FINand now knows the Client will send no more data. Server:ESTABLISHED → CLOSE_WAIT. - The Server sends segment 2:
ACK, ack = u+1, acknowledging theFINright away. - The Client receives that
ACK. Client:FIN_WAIT_1 → FIN_WAIT_2. The Client→Server pipe is now closed. - Half-close. The Server may still have data to deliver — one person on the call is done talking but still listening. Its
ACK(already sent) and itsFIN(not yet sent) stay separate; this beat is why the close takes four segments. - The Server finishes sending and emits segment 3:
FIN, seq = w, ack = u+1. Server:CLOSE_WAIT → LAST_ACK. - The Client receives the Server's
FIN. Client:FIN_WAIT_2 → TIME_WAIT. - The Client sends segment 4, the final
ACK, ack = w+1. - The Server receives it. Server:
LAST_ACK → CLOSED. The Server is completely done — note that it gets there before the Client. - The Client lingers in
TIME_WAITfor2·MSL, ready to re-acknowledge a retransmittedFINand letting any stray duplicates of this connection's segments expire in the network. - The timer elapses. Client:
TIME_WAIT → CLOSED. - A closing beat holds both ladders at
CLOSED. Four segments plus one deliberate wait, and the connection no longer exists.
On the canvas, each segment is a chip crossing the channel between the two lifelines, and every state change drops a badge onto its peer's ladder the moment it happens — older rungs fade so the current state is always the loudest. The packet in flight and the TIME_WAIT countdown run at slow-motion beats so you can read the seq/ack arithmetic mid-crossing.
Why TIME_WAIT Exists
TIME_WAIT looks like dead air — both FINs sent, both acknowledged, why is the Client still holding the socket? Because a pure ACK is not itself acknowledged, including this final ACK. The Client cannot know it arrived.
Switch the scenario selector to Lost final ACK and the lesson plays the failure that justifies the wait. The Client sends ACK, ack = w+1 — and the network eats it. The Server, still in LAST_ACK, hears nothing; its retransmission timer fires and it re-sends FIN, seq = w. The Client is still on the line in TIME_WAIT, so it simply re-ACKs with ack = w+1 and restarts its 2·MSL timer. The Server receives the ACK this time and moves to CLOSED; the Client serves out its linger and closes too. Same ending, one extra round trip.
Now run the counterfactual. If the Client had closed immediately after beat 9, the retransmitted FIN would arrive at a socket that no longer exists, and the Client's stack would answer with a RST — a slammed receiver instead of a "got it." The Server would end on an error rather than a clean close. The 2·MSL figure (twice the maximum segment lifetime) is sized so a retransmitted FIN and its re-ACK can each cross the network once before the wait ends, and so every old segment from this connection has died before the same socket pair can be reused.
Edge Cases
- Lost final ACK — the selectable second scenario, traced above: Server retransmits its
FINfromLAST_ACK, Client re-ACKs fromTIME_WAITand restarts the timer. - Simultaneous close. Both applications call
close()at once; each side sends aFINbefore seeing the other's and goesFIN_WAIT_1 → CLOSING → TIME_WAIT. Not animated here — there is no passive closer to contrast against. FIN_WAIT_2starvation. If the passive side never sends itsFIN, the active closer would wait forever inFIN_WAIT_2; real stacks may apply cleanup timeouts, especially after the application has abandoned the connection; the exact policy depends on the implementation and socket state.- Abortive close. A
RSTskips the whole four-way exchange — no half-close, noTIME_WAIT, and any in-flight data is discarded.
Common Mistakes
- Expecting three segments, like the handshake. The middle
ACKand the secondFINcannot merge in general — the passive closer may still be sending. The half-close window is the load-bearing difference between opening and closing. - Believing both sides reach
CLOSEDtogether. The passive closer is done the instant the finalACKlands (beat 10); the active closer holdsTIME_WAITfor2·MSLfirst. In this normal-close scenario, the Server finishes before the Client. - Treating
TIME_WAITas waste. It is the only thing standing between a lost finalACKand aRST-terminated close — and it's why servers that actively close thousands of connections accumulate sockets inTIME_WAIT. - Putting
TIME_WAITon the wrong peer. In this normal-close scenario it belongs to the active closer, not the passive peer. Simultaneous close can put both endpoints in TIME_WAIT. - Mixing up the acknowledgment arithmetic. A
FINconsumes one sequence number despite carrying no data, so the ACKs areu+1andw+1, notuandw.
A Note on Simplification
The lesson idealizes the rest: symbolic u/w instead of real byte counters, no data segments interleaved, timers as single beats. For the full state machine, RFC 9293 is the current word.
Keep exploring
A few lessons that share the same technique or lead into the next idea.
Found a mistake or something unclear? Leave feedback.