Closing a TCP connection takes four messages, not three. Each side has to independently say “I’m done sending” and get an acknowledgement. Then there’s a weird state called TIME_WAIT that sysadmins love to complain about.
In simple language: TCP is full-duplex — both sides can send. So both sides have to close their end separately. That’s why we get four messages instead of three.
The Four Messages
- FIN (A → B) — A says “I have no more data to send.”
- ACK (B → A) — B acknowledges the FIN. (B can still send data to A.)
- FIN (B → A) — B says “I’m also done sending.”
- ACK (A → B) — A acknowledges. Connection fully closed.
The Termination Timeline
State Walkthrough
The side that calls close() first is the active closer. The other side is the passive closer.
Active closer: ESTABLISHED -> FIN_WAIT_1 -> FIN_WAIT_2 -> TIME_WAIT -> CLOSED
Passive closer: ESTABLISHED -> CLOSE_WAIT -> LAST_ACK -> CLOSED
The active closer is stuck in TIME_WAIT for a while. The passive closer goes straight to CLOSED after the final ACK.
What is TIME_WAIT?
After sending the final ACK, the active closer waits for 2 × MSL (Maximum Segment Lifetime). MSL is typically 60s by default, so TIME_WAIT lasts ~30–120s depending on the OS.
Why Wait?
Two reasons:
- The final ACK might be lost. If the other side never sees it, they’ll resend their FIN. We need to be around to ACK it again.
- Old duplicate packets must die. A delayed segment from the just-closed connection shouldn’t show up in a brand-new one with the same 5-tuple.
Why TIME_WAIT Causes Headaches
A busy server (think a load balancer making lots of short-lived outbound connections) can pile up tens of thousands of sockets in TIME_WAIT. Each consumes a 5-tuple. We can run out of ephemeral ports.
# Count TIME_WAIT sockets on Linux
ss -tan state time-wait | wc -l
# Useful kernel knobs (Linux)
sysctl net.ipv4.tcp_fin_timeout # how long FIN_WAIT_2 lingers
sysctl net.ipv4.tcp_tw_reuse # 1 = reuse TIME_WAIT sockets for outgoing connections
Tuning tcp_tw_reuse=1 is a common fix for proxy servers. Don’t enable it blindly though — read the docs.
Simultaneous Close (Rare)
If both sides send FIN at the same time, we end up in a state called CLOSING and the dance becomes:
A: FIN_WAIT_1 -> CLOSING -> TIME_WAIT
B: FIN_WAIT_1 -> CLOSING -> TIME_WAIT
Cool but rarely seen in practice.
Half-Close
TCP supports a half-close — A says FIN, but keeps reading. This is what shutdown(sock, SHUT_WR) does. Useful for protocols like HTTP/1.0 where the client sends a request, half-closes write, and reads the response.
Reset (RST) — The Hard Hangup
If something goes wrong (connection refused, port not listening, app crashes), the OS sends a RST instead of going through the polite 4-way termination. Both sides immediately tear down. No TIME_WAIT.
Common Gotcha
A common interview answer is “TCP closes with 3 messages, like the open.” Wrong — it’s 4 because each direction is closed independently. The trick is that two of those four can sometimes piggyback into a single segment if both sides are ready, but logically there are still four.
Interview Tip
If asked “why does TIME_WAIT exist?” — both reasons matter (lost ACK + ghost packets). Don’t just say “to be safe.” Mentioning that the active closer pays the cost is bonus.