Skip to content
TCP Teardown — FIN & TIME_WAIT
step 1/5

Reading — step 1 of 5

Read

~2 min readProduction Concerns

TCP teardown — FIN and TIME_WAIT

TCP setup is famous (the 3-way handshake). Teardown gets less attention, but it has more states and is responsible for some of the most operationally annoying bugs in networking.

The closing state machine

One side calls close() first (the active closer) and the other follows (the passive closer). These are exactly the transitions your exercise drives, starting from ESTABLISHED:

Rendering diagram…

Active vs passive close

The side that calls close() first is the active closer. It walks ESTABLISHED -> FIN_WAIT_1 -> FIN_WAIT_2 -> TIME_WAIT -> CLOSED.

The other side is the passive closer: ESTABLISHED -> CLOSE_WAIT -> LAST_ACK -> CLOSED.

Simultaneous close

If both sides call close() at the same instant, each sends its FIN before seeing the other's. They land in CLOSING, then TIME_WAIT. Rare, but legal.

Why TIME_WAIT exists

After sending the final ACK, the active closer can't immediately reuse the (src ip, src port, dst ip, dst port) tuple. It must wait 2 × MSL — twice the Maximum Segment Lifetime, long enough for any straggler from this connection to drain from the network. RFC 793 sets MSL to 2 minutes (a 4-minute wait); real stacks shorten it, and Linux fixes TIME_WAIT at 60 seconds — which is why you'll see the figure quoted anywhere from 1 to 4 minutes. Two reasons for the wait:

  1. The final ACK might be lost. If a delayed retransmission of the peer's FIN then arrives at a freshly opened connection on the same 4-tuple, that new connection sees a stray FIN and tears down for no reason.
  2. Old packets from the prior connection might still be in flight and could land in a new instance, corrupting its byte stream (the "wandering segment" problem).

Operational pain

TIME_WAIT is why busy servers see thousands of sockets stuck in that state. Workarounds:

  • Make the client the active closer when possible (clients have many ephemeral ports; servers have only a few well-known ones).
  • SO_REUSEADDR / tcp_tw_reuse to allow reusing the tuple before the wait expires.
  • Don't just shorten the timer to hide the problem — you'll get phantom resets in production.

CLOSE_WAIT leaks

If your application receives a FIN but never calls close() on its socket, it sits in CLOSE_WAIT forever. The remote side's RST eventually frees it, but you've leaked a file descriptor. This is one of the most common bugs in long-running daemons — always close the half that recvs zero bytes.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…