The TCP handshake is a surprisingly complex dance that often trips people up when they first try to observe it.

Let’s see it in action. Imagine you’re trying to connect to a web server on 192.168.1.100 on port 80. You’d fire up tcpdump like this:

sudo tcpdump -i eth0 'tcp port 80 and host 192.168.1.100'

And then, from another terminal, try to curl the server:

curl http://192.168.1.100

Here’s what tcpdump might show you, broken down:

10:30:00.123456 IP 192.168.1.1.54321 > 192.168.1.100.80: Flags [S], seq 123456789, win 65535, options [mss 1460,nop,wscale 6,sackOK,TS val 12345678 ecr 0], length 0

This first packet is the SYN. The client (your machine, 192.168.1.1) is initiating the connection. The [S] flag indicates SYN. seq 123456789 is the initial sequence number the client chose. The win 65535 is the initial window size, and the options are important for tuning the connection (like Maximum Segment Size or MSS, window scaling, timestamps, and selective acknowledgments).

10:30:00.123500 IP 192.168.1.100.80 > 192.168.1.1.54321: Flags [S.], seq 987654321, ack 123456790, win 65535, options [mss 1460,nop,wscale 6,sackOK,TS val 98765432 ecr 12345678], length 0

This is the SYN-ACK. The server (192.168.1.100) is acknowledging the client’s SYN (ack 123456790 – it’s the client’s initial sequence number plus one) and sending its own SYN ([S.] flags). The server also picked its own initial sequence number (seq 987654321) and has its own set of options. Notice the TS val 98765432 ecr 12345678 – the server is echoing the client’s timestamp and providing its own.

10:30:00.123550 IP 192.168.1.1.54321 > 192.168.1.100.80: Flags [.], ack 987654322, win 65535, options [nop,nop,TS val 12345679 ecr 98765432], length 0

And finally, the ACK. The client acknowledges the server’s SYN-ACK (ack 987654322 – the server’s sequence number plus one). The [.] flag signifies an ACK. The connection is now established, and data can flow.

The core problem this handshake solves is establishing a reliable, ordered, and error-checked stream of data between two endpoints over an unreliable network like the internet. It ensures both sides agree on initial parameters like sequence numbers (so data can be put back in order), window sizes (for flow control), and various performance-tuning options.

The sequence numbers are crucial. Every byte of data sent has an associated sequence number. The ACK number signifies the next sequence number the sender expects to receive. This is how TCP guarantees that lost packets are retransmitted and that packets arriving out of order are reassembled correctly. The window size is a buffer on the receiver’s side; the sender can only send up to that many unacknowledged bytes. Window scaling allows for much larger windows, essential for high-bandwidth, high-latency links. Timestamps are used for measuring Round Trip Time (RTT) and for protecting against wrapped sequence numbers.

Many people think the handshake is just about saying "hello." It’s much more: it’s a negotiation of critical parameters that define the entire lifecycle of the connection, setting the stage for all subsequent data transfer. Without this negotiation, TCP wouldn’t be able to provide its reliable stream service.

If you’re seeing dropped SYN-ACKs or ACKs, it’s not just a "failed connection." It means a firewall is likely blocking the packet, the server process isn’t actually listening on that port, or there’s a routing issue preventing the response from getting back to the client. The handshake is the first indicator that the communication channel itself is being established, and its failure points to fundamental network or application configuration problems.

Once the handshake is complete, the next thing you’ll typically observe is the actual application data flowing, often preceded by a PUSH flag.

Want structured learning?

Take the full Tcpdump course →