Loading learning content...
The three-way handshake culminates in the client's final ACK segment—a deceptively simple packet that carries enormous significance. This third message transforms a tentative, half-open connection into a fully ESTABLISHED bidirectional communication channel.
Until this ACK arrives, the server sits in a vulnerable SYN_RECEIVED state: resources allocated, sequence number proposed, but no confirmation that the client is still alive and ready to communicate. The final ACK delivers that confirmation, triggering the last state transition and enabling the flow of application data.
By the end of this page, you will understand the precise role of the final ACK in the handshake, the ESTABLISHED state transition on both client and server, how data can be piggybacked on the ACK, what happens when the ACK is lost, and the critical timing considerations for connection establishment.
The final ACK of the three-way handshake serves a single but critical purpose: confirming to the server that the client received the SYN-ACK. This confirmation is essential for several reasons:
1. Validating Bidirectional Communication:
After the SYN-ACK, we know:
The final ACK completes the validation loop, proving both directions are functional.
2. Acknowledging the Server's ISN:
The SYN-ACK contained the server's Initial Sequence Number. Without an acknowledgment, the server can't be sure the client knows where to expect the server's first data byte. The ACK's acknowledgment number (server's ISN + 1) confirms this.
3. Releasing the Server from SYN_RECEIVED:
While in SYN_RECEIVED, the server has committed resources but can't transfer data. The ACK transitions the server to ESTABLISHED, enabling data transfer and freeing it from the handshake's vulnerability window.
When the client receives the SYN-ACK, it knows the server is ready. Why must it send an explicit ACK before data? Because the server is stuck in SYN_RECEIVED until it receives something that acknowledges its ISN. The client could send data (with the ACK flag set), but it must include the acknowledgment. The 'pure ACK' is the minimal packet that completes the handshake; data can optionally accompany it.
The Acknowledgment Semantics:
In the final ACK:
Acknowledgment Number = Server's ISN + 1
Just as the server's SYN-ACK acknowledged the client's SYN (client_ISN + 1), the client's ACK acknowledges the server's SYN-ACK (server_ISN + 1). This symmetry is elegant and intentional:
| Segment | Acknowledges |
|---|---|
| SYN | Nothing (first message) |
| SYN-ACK | Client's SYN |
| ACK | Server's SYN-ACK |
The final ACK is typically the simplest packet in the handshake—often just a TCP header with no data and no options. Let's examine its structure:
| Field | Size | Value | Explanation |
|---|---|---|---|
| Source Port | 16 bits | Ephemeral port (e.g., 54321) | Client's port (same as in SYN) |
| Destination Port | 16 bits | Service port (e.g., 443) | Server's port (same as in SYN) |
| Sequence Number | 32 bits | Client's ISN + 1 | Next byte client would send |
| Acknowledgment Number | 32 bits | Server's ISN + 1 | Acknowledges server's SYN-ACK |
| Data Offset | 4 bits | 5 (20 bytes, no options) | Minimal header with no options |
| Reserved | 4 bits | 0 | Reserved for future use |
| Flags | 8 bits | 0x10 (ACK only) | Only ACK flag set |
| Window Size | 16 bits | Receive window | Current receive buffer availability |
| Checksum | 16 bits | Computed value | Integrity check |
| Urgent Pointer | 16 bits | 0 | Not valid when URG not set |
| Options | None | N/A | Options already negotiated |
| Data | 0 bytes (usually) | N/A | Can optionally carry first data |
Key Observations:
Flags = 0x10 (ACK only) — Unlike the SYN-ACK which had both flags set (0x12), the final ACK has only the ACK flag. The binary representation: 0001 0000 = 0x10.
Sequence Number = Client's ISN + 1 — The SYN consumed sequence number ISN. The next sequence number (for data or control) is ISN + 1. Even though this pure ACK carries no data, the sequence field indicates where data would start.
No Options (Typically) — Option negotiation is complete. The final ACK usually carries no options, resulting in a minimal 20-byte header. Some implementations may include Timestamps if negotiated.
Window Update — The window size reflects the client's current receive buffer availability. This is the first 'real' window advertisement since the handshake is completing.
A pure handshake ACK has zero data bytes. However, nothing prevents piggybacking application data on this segment. If the client has data ready to send (e.g., an HTTP GET request), it can include that data with the ACK, saving one round-trip. This is common and correct behavior.
The final ACK triggers the last state transition in connection establishment. However, the timing is subtly different for client and server:
Client's Perspective:
The client transitions to ESTABLISHED when it sends the final ACK, not when it's confirmed received. From the TCP state machine:
SYN_SENT → (receive SYN-ACK, send ACK) → ESTABLISHED
This means the client enters ESTABLISHED before the server does. The client can immediately start sending data (though it may be buffered until the three-way handshake completes).
Server's Perspective:
The server transitions to ESTABLISHED when it receives the final ACK:
SYN_RECEIVED → (receive ACK) → ESTABLISHED
Until this ACK arrives, the server remains in the vulnerable half-open state.
The Asymmetry Matters:
The client enters ESTABLISHED half an RTT before the server. This has practical implications:
Client can send data immediately — Data sent after the ACK may arrive at the server before the ACK does. The server will buffer this data until it enters ESTABLISHED.
Connection accept timing — The application's accept() call returns only when the connection reaches ESTABLISHED. Server-side timers measuring 'time to first byte' should account for this.
Error handling — If the ACK is lost and the server eventually times out, the client thinks the connection is open while the server has abandoned it. Keepalives or data transmission will eventually detect this.
The ESTABLISHED State:
Once ESTABLISHED, both sides can:
The ESTABLISHED state persists until one side initiates connection termination (FIN) or an error occurs (RST). A connection can remain ESTABLISHED for seconds, minutes, or months—TCP has no inherent connection timeout. This is why server-side idle timeouts and keepalives are important for resource management.
One of TCP's elegant optimizations is the ability to piggyback data on acknowledgment segments. The final handshake ACK is no exception—application data can (and often should) be included.
Why Piggyback Data?
Without piggybacking:
Client: SYN
Server: SYN-ACK
Client: ACK (empty) ← Packet #3
Client: DATA (GET /index.html) ← Packet #4
With piggybacking:
Client: SYN
Server: SYN-ACK
Client: ACK + DATA (GET /index.html) ← Packet #3 with data
Piggybacking saves one packet, reducing latency and network overhead.
| Aspect | Without Piggybacking | With Piggybacking |
|---|---|---|
| Packets for handshake + first request | 4 | 3 |
| Time to first request | 1.5 RTT + processing | 1.5 RTT |
| Header overhead | 80 bytes (2 headers) | 40 bytes (1 header) |
| Network utilization | Lower | Higher |
| Implementation complexity | Simpler | Slightly complex |
The Piggybacked ACK Structure:
When data is included, the segment looks like:
Flags: 0x18 (PSH + ACK)
Sequence Number: Client_ISN + 1
Acknowledgment: Server_ISN + 1
Data: Application payload (e.g., HTTP request)
Notice the PSH (Push) flag is typically set along with ACK, telling the receiver to deliver data to the application promptly rather than buffering.
Example: HTTP Request on Final ACK:
123456789101112
Transmission Control Protocol, Src Port: 54321, Dst Port: 80 Flags: 0x018 (PSH, ACK) .... ...1 .... = Acknowledgment: Set .... .... 1... = Push: Set Sequence Number: 1001 (relative: 1) Acknowledgment Number: 2001 (relative: 1) [TCP Segment Len: 85] HTTP Request (85 bytes): GET /index.html HTTP/1.1\r\n Host: www.example.com\r\n \r\nStandard TCP requires waiting for the SYN-ACK before sending data. TCP Fast Open (RFC 7413) allows data in the initial SYN itself for repeat connections, saving another RTT. The client caches a 'Fast Open Cookie' from the first connection; subsequent connections include data in the SYN, and the server can respond with data in the SYN-ACK.
The final ACK, like any packet, can be lost in transit. This creates an interesting asymmetry: the client believes the connection is ESTABLISHED while the server remains in SYN_RECEIVED.
The Lost ACK Scenario:
| Time | Client State | Server State | Event |
|---|---|---|---|
| T=0 | CLOSED | LISTEN | Client initiates connection |
| T=0 | SYN_SENT | LISTEN | Client sends SYN |
| T=50ms | SYN_SENT | SYN_RECEIVED | Server receives SYN, sends SYN-ACK |
| T=100ms | ESTABLISHED | SYN_RECEIVED | Client receives SYN-ACK, sends ACK (lost) |
| T=3s | ESTABLISHED | SYN_RECEIVED | Server's timer expires |
| T=3s | ESTABLISHED | SYN_RECEIVED | Server retransmits SYN-ACK |
| T=3.05s | ESTABLISHED | SYN_RECEIVED | Client receives duplicate SYN-ACK |
| T=3.05s | ESTABLISHED | SYN_RECEIVED | Client retransmits ACK |
| T=3.1s | ESTABLISHED | ESTABLISHED | Server receives ACK, connection complete |
Recovery Mechanism:
Server retransmits SYN-ACK — After a timeout (typically ~3 seconds initially), the server sends another SYN-ACK since it hasn't received the expected ACK.
Client responds to duplicate SYN-ACK — When the ESTABLISHED client receives a SYN-ACK, it knows the server hasn't received the ACK. It retransmits the ACK (this is idempotent and safe).
Recovery succeeds — The retransmitted ACK reaches the server, completing the handshake.
If Client Sends Data:
Interestingly, if the client sends data before the ACK recovery:
This is why the server's SYN-ACK retransmit timer is important—it provides a fallback when the client's first acknowledgment is lost but no data follows immediately.
If repeated SYN-ACK retransmissions fail (client is gone, network partitioned), the server eventually abandons the connection. Typical defaults: 5 retries with exponential backoff = ~30 seconds total. After the final retry, the server clears the TCB and returns to LISTEN for that port. The client, meanwhile, may still think it's connected until it tries to send and fails.
The server must carefully validate the incoming ACK to prevent attacks and ensure it corresponds to the correct half-open connection.
ACK Validation Checks:
Security Implications:
ACK Flooding Attack: An attacker sends many ACK packets with random acknowledgment numbers, hoping to match half-open connections and complete handshakes for spoofed IPs. Defenses:
RST Attack (Connection Reset Attack): An off-path attacker who guesses the sequence and acknowledgment numbers can send a RST to tear down connections. This is why cryptographic ISN matters—it makes sequence space unpredictable.
SYN Cookie ACK Validation: When SYN cookies are active, the ACK validation is different:
With SYN cookies, the ACK validation effectively proves the client received the SYN-ACK—because the correct acknowledgment can only be computed by someone who received the cookie value. This elegantly prevents off-path attackers from completing handshakes to spoofed addresses.
When the final ACK transitions a connection to ESTABLISHED, the operating system must notify the listening application. This involves the accept queue—a kernel data structure holding completed connections waiting for the application to accept them.
The Two Queues:
Modern TCP implementations maintain two queues for incoming connections:
Connection Lifecycle through Queues:
SYN arrives → SYN Queue entry created → SYN-ACK sent
↓
ACK arrives
↓
SYN Queue entry removed → Accept Queue entry created → ESTABLISHED
↓
Application calls accept()
↓
Connection socket returned
What Happens When Accept Queue Is Full:
If the accept queue is full when a connection completes:
| Kernel Behavior | Effect | Controlled By |
|---|---|---|
| Drop the connection | Client times out | Some systems |
| Send RST | Client sees 'Connection refused' | tcp_abort_on_overflow |
| Ignore the ACK | Server retransmits SYN-ACK | Default on Linux |
The default Linux behavior (ignoring the ACK) allows the connection to complete once the application catches up and frees accept queue space.
A slow application that doesn't call accept() fast enough can cause the accept queue to overflow. New connections are dropped even though the handshake completed. Monitor 'listen queue overflow' metrics (e.g., netstat -s | grep overflowed) and increase backlog size or improve application responsiveness if this occurs.
Let's examine an actual final ACK packet from a network capture, including one with piggybacked data:
12345678910111213141516171819202122232425262728
Transmission Control Protocol, Src Port: 54321, Dst Port: 443 Source Port: 54321 Destination Port: 443 [Stream index: 0] [TCP Segment Len: 0] Sequence Number: 1 (relative sequence number) Sequence Number (raw): 2812345679 [Next Sequence Number: 1] Acknowledgment Number: 1 (relative ack number) Acknowledgment number (raw): 1517890235 0101 .... = Header Length: 20 bytes (5) Flags: 0x010 (ACK) 000. .... .... = Reserved: Not set ...0 .... .... = Accurate ECN: Not set .... 0... .... = Congestion Window Reduced: Not set .... .0.. .... = ECN-Echo: Not set .... ..0. .... = Urgent: Not set .... ...1 .... = Acknowledgment: Set .... .... 0... = Push: Not set .... .... .0.. = Reset: Not set .... .... ..0. = Syn: Not set .... .... ...0 = Fin: Not set Window: 65535 Checksum: 0x4a5b [correct] Urgent Pointer: 0 [Timestamps] [Time since first frame in this TCP stream: 0.024691356 seconds] [Time since previous frame in this TCP stream: 0.012345678 seconds]Analysis of Pure ACK:
Final ACK with Piggybacked Data:
123456789101112131415161718192021
Transmission Control Protocol, Src Port: 54321, Dst Port: 80 Source Port: 54321 Destination Port: 80 [TCP Segment Len: 106] Sequence Number: 1 (relative) Acknowledgment Number: 1 (relative) 0101 .... = Header Length: 20 bytes (5) Flags: 0x018 (PSH, ACK) .... ...1 .... = Acknowledgment: Set .... .... 1... = Push: Set Window: 65535 Checksum: 0x5c6d [correct] [SEQ/ACK analysis] [This is a PDU containing application data] Hypertext Transfer Protocol GET /index.html HTTP/1.1\r\n Host: www.example.com\r\n User-Agent: curl/7.68.0\r\n Accept: */*\r\n \r\nTo view only TCP handshake packets in Wireshark, use: 'tcp.flags.syn==1 or (tcp.flags.ack==1 and tcp.seq==1)'. For the final ACK specifically: 'tcp.flags == 0x010 and tcp.seq == 1'. This helps isolate connection establishment from data transfer.
The three-way handshake imposes a fundamental latency cost on every TCP connection. Understanding this cost is critical for performance optimization, especially for short-lived connections.
The 1.5 RTT Cost:
From the client's perspective:
T=0: Send SYN
T=RTT/2: Receive SYN-ACK, send ACK, enter ESTABLISHED
T=RTT: ACK reaches server, server enters ESTABLISHED
The client can begin sending data at T=RTT/2 (1.5 RTT after the very first packet if we count the server receiving and processing). For the first useful response, we need:
Time to first byte (client sends request):
= 1 RTT (handshake SYN → client established) + 0 (piggybacked request)
= 1 RTT
Time to first response byte:
= 1 RTT (request sent) + RTT/2 (server processes) + RTT/2 (response arrives)
= 2 RTT minimum
| RTT | Handshake Time | First Byte Latency | Impact |
|---|---|---|---|
| 1ms (local) | 1.5ms | 2ms | Negligible |
| 20ms (regional) | 30ms | 40ms | Noticeable |
| 100ms (continental) | 150ms | 200ms | Significant |
| 250ms (intercontinental) | 375ms | 500ms | Half second delay |
| 600ms (satellite) | 900ms | 1200ms | Very poor UX |
Implications for Modern Web:
A typical web page loads dozens of resources. If each requires a new TCP connection:
Optimizations:
| Technique | Mechanism | RTT Savings |
|---|---|---|
| Connection Reuse | HTTP Keep-Alive | Eliminates handshake for subsequent requests |
| HTTP/2 Multiplexing | Multiple streams on one connection | Single handshake for all resources |
| TCP Fast Open | Data in SYN for repeat connections | Saves 1 RTT on handshake |
| 0-RTT (TLS 1.3/QUIC) | Crypto and data in first packet | Near-zero handshake |
| Connection Pooling | Pre-established connections | Warm connections ready to use |
For HTTPS, the TCP handshake is just the beginning. TLS 1.2 adds another 2 RTT; TLS 1.3 improved this to 1 RTT (or 0-RTT for resumed sessions). QUIC combines connection and encryption setup, reducing fresh connection latency significantly compared to TCP + TLS.
We've comprehensively examined the final ACK—the segment that completes TCP's three-way handshake and enables reliable communication. Let's consolidate the essential knowledge:
What's Next:
With the three-way handshake complete—SYN, SYN-ACK, ACK—the connection is ESTABLISHED. But this is just the mechanism. In the next page, we'll step back and examine connection establishment as a holistic process: the complete state machine, simultaneous open scenarios, connection timeouts, and how the handshake interacts with the operating system's socket API.
You now understand the final ACK segment comprehensively—its purpose, structure, state transitions, loss handling, and performance implications. Combined with your knowledge of SYN and SYN-ACK, you have a complete picture of TCP connection establishment at the packet level.