Server

The TCP Three-Way Handshake, Explained

By Prabath Thalangama· October 24, 2026· 3 min read
#tcp#networking#troubleshooting

Introduction

Every TCP connection — an HTTPS request, an SSH session, a database query — starts with the same three packets. Understanding them turns a lot of vague "the connection hangs" problems into specific ones: is the SYN even arriving? Is something dropping the SYN-ACK? Is a firewall silently discarding packets instead of rejecting them?

This post shows what the three packets carry and how to watch a handshake happen on your own machine.

Concepts

TCP is connection-oriented: before any data moves, both sides agree on a starting sequence number and exchange options like the maximum segment size (MSS) and window scaling. That agreement is the handshake.

Client                                  Server
  |  SYN  seq=x                            |
  | ------------------------------------>  |
  |                  SYN, ACK  seq=y ack=x+1
  | <-----------------------------------   |
  |  ACK  seq=x+1 ack=y+1                  |
  | ------------------------------------>  |
  |            (connection ESTABLISHED)    |
  1. SYN — the client picks a random initial sequence number x and sets the SYN flag. It also advertises its MSS, window size, and whether it supports options like SACK and timestamps.
  2. SYN-ACK — the server picks its own random sequence number y, sets SYN and ACK, and acknowledges the client's sequence with ack = x + 1.
  3. ACK — the client acknowledges the server's sequence with ack = y + 1. The connection is now ESTABLISHED on both ends and data can flow.

The random initial sequence numbers matter for security — a predictable ISN lets an off-path attacker inject data. Modern stacks randomize them per RFC 6528.

Hands-on

Watch a handshake

In one terminal, capture just the handshake packets to example.com:443:

sudo tcpdump -ni any 'tcp port 443 and (tcp[tcpflags] & (tcp-syn|tcp-ack) != 0)'

In another, make a connection:

curl -sS -o /dev/null https://example.com

You'll see three lines like:

IP 10.0.0.5.51820 > 93.184.216.34.443: Flags [S],  seq 1..., win 64240, options [mss 1460,sackOK,TS ...,nop,wscale 7]
IP 93.184.216.34.443 > 10.0.0.5.51820: Flags [S.], seq 2..., ack 1..., win 65535, options [mss 1440,sackOK,TS ...,nop,wscale 10]
IP 10.0.0.5.51820 > 93.184.216.34.443: Flags [.],  ack 1...

[S] is SYN, [S.] is SYN-ACK, [.] is a bare ACK. curl adds --connect-timeout 5 if you want to bound how long it waits.

The connection lifecycle

# Every socket and its TCP state.
ss -tan

# Watch states change while you connect.
watch -n0.5 "ss -tanp | grep :443"

States you'll see: SYN-SENT (client sent SYN, waiting), SYN-RECV (server got SYN, sent SYN-ACK), ESTABLISHED, then TIME-WAIT on the side that closed first.

Verification and troubleshooting

Map the symptom to the missing packet:

  • curl hangs, then Connection timed out — the SYN left but nothing came back. tcpdump on the client shows repeated [S] retransmits (roughly at 1s, 2s, 4s, ...). Cause: a firewall or security group that drops (not rejects) the packet, a routing black hole, or the server isn't listening. Check the path with traceroute -T -p 443 example.com.
  • Connection refused immediately — the SYN arrived and the host sent a RST because nothing is bound to that port. The service is down or bound to 127.0.0.1 only. ss -tlnp on the server.
  • Handshake completes, then the connection stalls on the first data packet — almost always an MTU / MSS mismatch. A tunnel (VPN, PPPoE, GRE) lowers the path MTU, the large packet gets dropped, and ICMP "fragmentation needed" is filtered so PMTUD can't recover. Clamp the MSS on the router (iptables ... TCPMSS --clamp-mss-to-pmtu) or lower the interface MTU.
  • Intermittent SYN-RECV pileup on the server — a SYN flood or a slow client. ss -tan state syn-recv | wc -l; enable net.ipv4.tcp_syncookies.
  • curl connects but TLS fails — the TCP handshake is fine; the problem is the TLS layer. Check with the SSL/TLS checker.
PrabathStuck on something this site can't fix?Reach out to Prabath directly on LinkedIn.