The TCP three-way handshake is less about establishing a connection and more about establishing trust between two machines before any actual data is sent.
Let’s watch it happen. Imagine two services, client and server, trying to talk.
# On the client, before the connection:
# No established TCP connections to the server's port 8080
ss -tunp | grep 8080
# On the server, before the connection:
ss -tunp | grep 8080
# Client initiates:
curl http://server:8080/
# Now, let's look at the network traffic with tcpdump on the client:
sudo tcpdump -i any -nn 'host server and port 8080'
The first thing you’ll see is client sending a SYN packet to server. This is client saying, "Hey, I’d like to open a connection, and my initial sequence number is 123456789." The S flag indicates SYN, and seq 123456789 is that initial sequence number.
server receives the SYN. If it’s willing to talk, it responds with a SYN-ACK. This packet does two things: it acknowledges client’s SYN (telling client that its SYN arrived by sending back ack 123456790 – one higher than the client’s sequence number), and it also proposes its own initial sequence number for the connection (let’s say seq 987654321). The S flag is still there, and now we have the A flag for ACK.
Finally, client receives the SYN-ACK. It needs to confirm that server got its SYN and is ready to go. So, client sends back an ACK. This ACK acknowledges server’s SYN (with ack 987654322 – one higher than the server’s sequence number) and the connection is now established. The handshake is complete. The client then sends its actual HTTP request.
The problem this solves is simple: how do two machines, potentially separated by many unreliable networks, agree that they are both ready to send and receive data, and that they have synchronized their starting points for tracking that data? Without this agreement, packets could be lost, duplicated, or arrive out of order, leading to corrupted data or endless retransmissions. TCP needs a way to ensure both sides are on the same page before the real conversation begins.
Internally, each side maintains state for the connection. Before the handshake, the client is in a CLOSED state, and the server is also CLOSED.
- Client sends SYN: Client transitions to
SYN-SENT. Server remainsCLOSED. - Server sends SYN-ACK: Server transitions to
SYN-RECEIVED. Client receives SYN-ACK and transitions toESTABLISHED. - Client sends ACK: Client is now
ESTABLISHED. Server receives ACK and transitions toESTABLISHED.
Once both are ESTABLISHED, the connection is live, and data can flow. The sequence and acknowledgment numbers are crucial. They allow TCP to reassemble packets in the correct order, detect missing packets, and discard duplicates. Each byte of data is assigned a sequence number, and ACKs confirm receipt up to a certain number.
The sequence and acknowledgment numbers are not arbitrary. They are derived from an initial sequence number (ISN) that is chosen using a pseudo-random number generator. This ISN is designed to be unpredictable to prevent certain types of network attacks where an attacker might try to guess the sequence numbers and inject forged packets into an existing connection. The ISN is refreshed periodically, and its generation is a critical part of TCP’s security.
What most people don’t realize is that the SYN-ACK packet from the server doesn’t just acknowledge the client’s SYN; it also contains the server’s own SYN, meaning the server is simultaneously initiating its side of the connection. This is why it’s a SYN-ACK, and why the client’s final ACK is acknowledging the server’s sequence number. It’s a dual initiation and confirmation.
After the handshake, the next thing you’ll encounter is the connection hanging or timing out during the actual data transfer phase.