Networking
TCP 3-Way Handshake
Three packets, one round trip and a half — every connection starts here
The TCP 3-way handshake is the SYN → SYN-ACK → ACK exchange that establishes a reliable connection between two computers. It synchronizes initial sequence numbers, negotiates options like maximum segment size, and confirms both sides can send and receive — all in 1.5 round-trips. Every TCP connection on the internet starts this way.
- Packets exchanged3 (SYN, SYN-ACK, ACK)
- Round trips1.5 — connection ready after 1 RTT
- Initial sequence numberRandom 32-bit per side (security)
- GoalSynchronize sequence numbers + confirm reachability
- Failure modeSYN flood (exhaust server half-open queue)
- Modern variantTCP Fast Open (TFO) — 0-RTT for repeat connections
Interactive visualization
Press play, or step through manually. The visualization is yours to drive — try it before reading on.
Watch the 60-second explainer
A condensed visual walkthrough — narrated, captioned, under a minute.
How the 3-way handshake works
Three packets, in this exact order:
- SYN — client → server. Client sends a SYN ("synchronize") packet with a random initial sequence number (ISN_c). Says "I want to connect; here's my starting sequence number."
- SYN-ACK — server → client. Server picks its own random ISN (ISN_s), sends SYN+ACK in one packet. Says "I got your SYN, my starting sequence number is ISN_s, I expect your next byte at ISN_c+1."
- ACK — client → server. Client acknowledges the server's SYN. Says "I got your SYN-ACK, expecting your next byte at ISN_s+1."
After the third packet, both sides know:
- The other can both send and receive (proven by all three packets being acknowledged).
- The other's starting sequence number (so they can detect lost or out-of-order packets).
- Negotiated options (MSS, window scale, selective acknowledgment).
The client can send data immediately with the third packet (often piggybacked on the ACK). The server has to wait one more round-trip before it can send data — that's the 1.5-RTT cost of the handshake.
State transitions
| Step | Client state | Server state |
|---|---|---|
| Initial | CLOSED | LISTEN |
| After client sends SYN | SYN_SENT | LISTEN |
| After server sends SYN-ACK | SYN_SENT | SYN_RCVD (half-open) |
| After client sends ACK | ESTABLISHED | SYN_RCVD (about to transition) |
| After server processes ACK | ESTABLISHED | ESTABLISHED |
The SYN_RCVD state is the half-open connection — the server has reserved resources but hasn't confirmed the client is real. SYN flood attacks exploit this state by creating thousands of half-open connections.
TCP vs UDP
| TCP | UDP | |
|---|---|---|
| Connection-oriented | Yes — handshake required | No — packets just sent |
| Reliability | Guaranteed delivery + order | Best effort — packets may be lost or reordered |
| Setup overhead | 1 RTT minimum | None |
| Header size | 20 bytes | 8 bytes |
| Flow control | Yes — receive window | None |
| Congestion control | Yes — Reno, Cubic, BBR | None |
| Used by | HTTP/1.1-2, email, SSH, file transfer | DNS, VoIP, video games, HTTP/3 (over QUIC) |
| Latency-sensitive? | No (retransmits add latency) | Yes — application handles loss |
Why exactly three?
Two packets aren't enough because of asymmetric information. After the client sends SYN and the server replies with SYN-ACK, only the client knows the SYN-ACK arrived — the server doesn't. The server is left in SYN_RCVD with no confirmation that the client is real or that the server's SYN-ACK was received.
The third ACK closes that knowledge gap. After three packets, both sides know that both sides know the connection is established. Two-packet handshakes (sometimes proposed for theoretical efficiency) leave the server vulnerable to half-open connection floods because there's no confirmation step.
Could you do four packets? You could, but it adds latency without benefit. Three is the minimum for mutual acknowledgment.
When this matters in practice
- Latency-sensitive applications. Each new TCP connection costs 1 RTT before data can flow. For a connection to a server 100ms away, that's 100ms of pure setup. HTTP/1.1 keep-alive, HTTP/2 multiplexing, and TCP Fast Open all exist to amortize this cost.
- Server scalability. Each half-open connection uses memory (~256 bytes typical). A server hit with 100k SYNs/sec is using 25 MB just for the queue. SYN cookies eliminate this state by encoding the SYN-RCVD info in the SYN-ACK sequence number itself.
- Firewall and load-balancer behavior. Stateful firewalls track handshakes to allow only legitimate traffic. Load balancers may need to "splice" two TCP connections (client ↔ LB and LB ↔ server) — the handshake mechanics determine how this works.
- Connection failure debugging. When you see "connection refused" — server sent RST instead of SYN-ACK because no service listening. "Connection timeout" — your SYN never reached the server (firewall) or its SYN-ACK never reached you. The handshake step that fails tells you where to look.
Pseudo-code (client side)
function tcpConnect(serverIP, serverPort):
isn_c = random32bit() # initial sequence number
sendPacket(SYN, seq=isn_c)
state = SYN_SENT
response = waitForPacket() # blocks until SYN-ACK or timeout
if response.flags == SYN+ACK:
isn_s = response.seq
sendPacket(ACK, seq=isn_c+1, ack=isn_s+1)
state = ESTABLISHED
return Connection(isn_c, isn_s)
else if response.flags == RST:
error("connection refused")
else timeout:
error("no response")
Famous TCP handshake issues
SYN flood DDoS
Attacker sends thousands of SYNs from spoofed IP addresses. Each consumes a slot in the server's half-open queue. With enough SYNs, the queue fills and legitimate connections fail.
Mitigation: SYN cookies. The server doesn't allocate state on receiving a SYN. Instead, it computes a cryptographic hash of (source IP, source port, destination IP, destination port, secret) and uses that as the sequence number in its SYN-ACK. When the client's ACK arrives with sequence number cookie+1, the server can verify the cookie and reconstruct the connection state on the fly. No state stored = no exhaustion.
TCP Fast Open (RFC 7413)
For repeat connections to the same server, TFO eliminates the round-trip delay. After the first handshake, the server gives the client a cookie. On the next connection, the client sends SYN + cookie + data in one packet; the server validates the cookie and processes the data immediately. 0-RTT for known clients. Used by Linux kernel, Chrome, modern Linux distros.
Common issues and edge cases
- Listening socket exhaustion. Each accept() pulls a socket from the kernel's accept queue. If your application is too slow to accept, the queue fills and new connections fail with backlog overflow. Tune
net.core.somaxconnand calllisten(fd, BACKLOG)with a sensible BACKLOG. - TIME_WAIT exhaustion on busy clients. After a connection closes, the side that initiated close enters TIME_WAIT for 60-120 seconds. A client opening many short connections can run out of ephemeral ports. Reuse connections (HTTP keep-alive) or use SO_REUSEADDR.
- Confusing SYN flood with normal traffic. Don't enable SYN cookies unconditionally — they have minor downsides (selective ACK and other options not preserved). Modern Linux enables them only when the SYN backlog overflows.
- NAT timeouts dropping idle connections. Many NAT devices time out TCP connections that go idle for 5+ minutes. TCP keepalive packets every few minutes prevent this. Default Linux keepalive is 2 hours — too long for many NATs.
- MTU mismatches breaking handshake completion. If a path has lower MTU than negotiated MSS suggests, large packets get dropped silently (Path MTU Discovery blackhole). Connection establishes but data transfer hangs. The fix is usually MSS clamping at the edge router.
Frequently asked questions
Why 3 packets and not 2?
Two packets can confirm one direction but not both. The client's SYN proves "I can send to you." The server's SYN-ACK proves "I received yours, and I can send to you." But the server doesn't yet know the client received the SYN-ACK. The third packet (client's ACK) closes that gap. After 3 packets, both sides know that both sides can communicate. With only 2, you'd have ambiguous states where the server doesn't know if the client is alive.
Why are sequence numbers random?
Security and reliability. If sequence numbers were predictable (like 0 each time), an attacker could inject forged segments into an existing connection — TCP would accept them as valid because they'd have plausible sequence numbers. Random initial sequence numbers (ISN) make injection attacks much harder. They also prevent old packets from a previous connection (same source/dest IP+port) from being accepted by a new one.
What's a SYN flood attack?
Send SYN packets to a server but never complete the handshake (don't send the final ACK). Each SYN consumes a slot in the server's half-open connection queue. With enough fake SYNs, the queue fills and legitimate connections can't be established. Mitigations include SYN cookies (server stores no state until the third packet) and per-source rate limiting.
How is TCP different from UDP?
TCP guarantees ordered, reliable, connection-oriented delivery via the handshake, sequence numbers, retransmission, and flow control. UDP is fire-and-forget — no handshake, no acknowledgments, no order. TCP is for HTTP, email, file transfer (anything where loss matters). UDP is for video calls, gaming, DNS (anything where retransmits would arrive too late to matter).
What is TCP Fast Open?
A TCP extension that lets the client send data in the SYN packet, eliminating the 1-RTT delay for repeat connections. The server stores a cookie tied to the client's IP after the first connection; on subsequent SYNs the client includes the cookie + data, and the server can act on it immediately. Saves significant latency for protocols like HTTP that establish many short-lived connections.
What does the MSS option do?
Maximum Segment Size — the largest TCP payload (excluding headers) that can fit in one packet without IP fragmentation. The handshake's SYN and SYN-ACK each advertise the sender's MSS; the connection uses the smaller of the two. Typical Ethernet: MSS = 1460 bytes (1500 MTU − 20 IP − 20 TCP). MSS too high causes fragmentation; too low wastes header overhead.
What's a half-open connection?
A state where one side has sent SYN-ACK but not received the final ACK. The server has allocated resources (sequence numbers, buffers) but the connection isn't fully established yet. Normally resolves in milliseconds. SYN flood attacks exploit this by creating thousands of half-open connections to exhaust server memory.