Networking Fundamentals

Ch.17: TCP Connection Termination (4-Way Handshake)

By Ayush Arora13 min read

Inspired by: YouTube

In the previous post, we examined the TCP 3-Way Handshake (SYN, SYN-ACK, ACK), learning how client and server operating systems establish a stateful connection by synchronizing sequence numbers and receive window sizes. However, a TCP connection cannot remain open indefinitely.

Once client and server finish exchanging application data (such as HTTP requests and responses), host operating systems must cleanly teardown the connection. This process releases critical system resources, including kernel socket descriptors, memory buffers, and state table entries.

In this post, we will explore TCP Connection Termination, analyzing why connection teardown requires a 4-way handshake (FIN, ACK, FIN, ACK), how half-closed states work, why the TIME-WAIT state is essential, and how abrupt RST resets work.


Why Connection Teardown Requires 4 Steps (The Full-Duplex Principle)

In the 3-way handshake, the server merges its acknowledgment (ACK) and synchronization (SYN) into a single SYN-ACK segment. A common question arises: Why isn't connection termination also a 3-step process? Why are FIN and ACK sent separately by the server?

The answer lies in TCP's Full-Duplex Architecture:

TCP provides two independent, directional data channels:

  1. Client to Server Stream
  2. Server to Client Stream

When the client finishes sending its HTTP request payload and transmits a FIN (Finish) segment, it is only closing its sending direction (Client to Server). The server may still have pending response data in its kernel queue that needs to be transmitted to the client.

Therefore:

  1. The server immediately sends an ACK acknowledging the client's FIN segment.
  2. The connection enters a Half-Closed State. The client can no longer send data, but the server can continue streaming remaining response bytes to the client.
  3. Once the server finishes sending all remaining data, it transmits its own FIN segment to close the Server to Client direction.
  4. The client responds with a final ACK, fully terminating the connection.

Step-by-Step Mechanism of the 4-Way Teardown

Here is the complete exchange of segments during a standard 4-way connection termination:

Step 1: The First FIN (Client to Server)

When an application (such as a web browser or React app) completes its data transmission, it calls the close() system call on its socket descriptor.

Step 2: The First ACK (Server to Client)

The server receives the FIN segment from the client.

Step 3: The Second FIN (Server to Client)

While in CLOSE-WAIT, the server application finishes sending any remaining buffered responses. Once all response data is flushed and the server application calls close(), the server kernel initiates its own closure.

Step 4: The Second ACK (Client to Server)

The client receives the server's FIN segment.


Summary of 4-Way Teardown Segments and States

StepSegment NameSenderClient StateServer StateNote
1FINClientFIN-WAIT-1ESTABLISHEDClient closes sending direction
2ACKServerFIN-WAIT-2CLOSE-WAITServer acknowledges Client FIN (Half-Closed)
3FINServerFIN-WAIT-2LAST-ACKServer flushes data & closes sending direction
4ACKClientTIME-WAITCLOSEDClient acknowledges Server FIN

Connection Teardown State Machine


Deep Dive: The TIME-WAIT State

Why doesn't the active closer (client) transition directly to CLOSED after sending the final ACK in Step 4? Why must it wait in TIME-WAIT for a short timer period before fully closing?

There are two fundamental reasons:

1. Reliable Delivery of the Final ACK

Network IP packets can be lost. If the client's final ACK (Step 4) is dropped by a router:

2. Preventing Stale Packet Corruption (Draining Straggler Segments)

IP networks can delay packets due to routing loops. Imagine a scenario where a connection closes, and a new TCP connection is opened immediately using the exact same 4-tuple (Client IP, Server IP, Client Port, Server Port).

If an old delayed packet from the previous connection suddenly arrives, the new connection could mistake it for valid data, corrupting application state.

Holding the socket in TIME-WAIT for a timer period ensures that all old straggler packets belonging to that 4-tuple expire and drain from the network before the port can be reused.

Who Enters TIME-WAIT? (Can the Server Enter TIME-WAIT?)

A common point of confusion is whether TIME-WAIT is exclusive to clients.

The rule in TCP is simple: Whichever endpoint sends the first FIN segment (the Active Closer) is the host that enters TIME-WAIT.


Technical Edge Cases

1. Abrupt Termination: The RST (Reset) Segment

Not all TCP connections close gracefully through a 4-way handshake. In certain operational scenarios, a connection must be terminated immediately. This is accomplished using an RST (Reset) segment.

Graceful (FIN) vs. Forceful (RST) Termination

The fundamental difference between a standard FIN segment and an RST segment comes down to courtesy versus emergency aborts:

How an RST Segment Works under the Hood

When a host transmits or receives an RST segment:

  1. Bypasses State Lifecycle: Both endpoints bypass intermediate handshake states (FIN-WAIT-1, FIN-WAIT-2, CLOSE-WAIT, LAST-ACK, and TIME-WAIT) and transition directly to CLOSED.

  2. No Acknowledgment Required (Preventing Infinite Reset Loops): The recipient of an RST segment does not send an ACK back. This design rule prevents infinite packet loops: if an ACK sent in response to an RST were dropped or misrouted, the other host (which already destroyed its connection state) would be forced to respond with another RST, creating an endless ping-pong loop of packets storming the network. Unlike FIN (which asks for confirmation), RST is a one-way command. The connection is considered dead the instant RST is processed.

  3. Buffer Erasure (Immediate Memory Purge): In a normal FIN teardown, TCP guarantees that all queued data is flushed across the wire before closing. In an RST scenario:

    • Outbound Transmission Queue: The OS kernel immediately wipes all unsent payload bytes sitting in its outbound socket buffer. They are deleted from RAM without ever touching the network.
    • Inbound Receive Queue: If the application has not yet read the data already sitting in its socket receive buffer, the OS kernel purges those unread bytes immediately. Any subsequent read() system call by the application returns an instant error (ECONNRESET / "Connection reset by peer") instead of returning the unread data.

Common Triggers for RST Segments

  1. Connection Refused (Closed Port): If a client sends a SYN request to a server port where no application is actively listening (e.g., attempting an HTTP request on port 8080 when the backend server is stopped), the host OS kernel immediately responds with an RST segment to inform the client that the port is closed.

  2. Process Crash or Sudden Host Reboot: If an application process (like Node.js, Python, or Nginx) crashes unexpectedly while holding active TCP connections, the OS kernel reclaims orphaned socket descriptors by sending RST segments to all connected peers.

  3. Out-of-State / Unsolicited Packets: If a host receives a TCP segment belonging to a connection that no longer exists in its active state table (for example, if a firewall or router previously cleared the connection state), the host replies with an RST segment to signal that the connection is invalid.

What Happens if an RST Segment is Lost in Transit?

Because an RST segment is not acknowledged (No ACK), IP network routers can occasionally drop an RST segment in transit. When this happens:

  1. Temporary Half-Open (Zombie) State:

    • Sender of RST (Host A): Host A immediately closes its socket and wipes its state table. It considers the connection dead.
    • Recipient (Host B): Because Host B never received the RST segment, Host B still believes the connection is active (ESTABLISHED) and keeps its kernel socket memory allocated.
  2. Self-Healing on Next Packet Transmission:

    • The moment Host B attempts to send new data bytes or a keep-alive probe to Host A, Host A receives a packet for a connection that no longer exists in its table.
    • Host A's kernel immediately responds by transmitting another RST segment.
    • Host B receives this new RST segment and finally tears down its socket, throwing an ECONNRESET error.
  3. Fallback to Keep-Alive Timers:

    • If Host B remains completely silent and never transmits data, Host B's TCP Keep-Alive timer (or application-level heartbeat) eventually triggers, sending probe packets. When Host A rejects the probe with an RST, Host B's connection is cleanly closed.

Comparison: FIN vs. RST

FeatureFIN Segment (Graceful Close)RST Segment (Abrupt Reset)
Operational GoalClean, negotiated connection shutdownImmediate, forced connection termination
Buffer HandlingFlushes and delivers all pending dataDiscards all queued and in-flight data
AcknowledgmentRequires explicit ACK segmentsNo ACK expected or transmitted
State LifecycleMoves through state transitions and TIME-WAITBypasses all states and instantly transitions to CLOSED

2. Simultaneous Close

If both endpoints send FIN segments simultaneously:

3. Combined FIN-ACK Optimization (3-Way Connection Termination)

In a standard 4-way teardown, the server sends a separate ACK followed by a separate FIN. This occurs when the server still has buffered response data to flush to the client.

However, if the server has no pending data and is ready to close immediately upon receiving the client's FIN:


Summary