TCP requires a three-way handshake (SYN, SYN-ACK, ACK) because two messages are insufficient to safely establish a connection: with only two messages, the server cannot confirm the client received its response, and stale packets from previous connections could be misinterpreted as new connection attempts. The third message (ACK) confirms the client saw the server's random sequence number, preventing both the zombie-packet problem and ensuring both sides agree the connection exists before data transfer begins.
Deep Dive
Prerequisite Knowledge
- No data available.
Where to go next
- No data available.
Deep Dive
Why Does TCP Handshake Need 3 Messages? (Not 2, Not 1)
Added:Most TCP connections begin with a three message exchange before the server treats the connection as fully established. This one round trip before your HTTP request, database query, or SSH session can begin. But why three?
Why not just zero or maybe one? If both computers already know each other's IP address and port, why not start sending byes immediately? Let's build a TCP handshake from scratch and see what breaks at zero messages, then one, then two, and by the end, the three-way handshake should feel like the smallest design that actually works.
Before we ask how TCP starts a connection, we need to define what a connection is. At the layer below TCP, we have the internet protocol or IP. IP moves packets from one machine to another using their addresses. Every IP packet carries a source and destination addresses and TCP adds a source port and destination port so the operating system knows which program owns which packet.
A TCP conversation is labeled by a foruple. Source IP, source port, destination IP, and destination port.
And it allows two programs to exchange information over the internet.
These four values identify a connection.
But that's not what a connection is. A connection is the state both machines keep behind this identifier. Each side of the connection keeps send and receive states. For the bytes it sends, it tracks what has been sent and acknowledged. And for the bytes it receives, it tracks what arrived and what is missing. That state is what turns loose IP packets into a reliable stream of bytes. So the real question is how do two machines safely create that state? And the answer is TCP handshake.
Let's start with the most aggressive design without the handshake. We will assume the client knows the server's IP and port. So it starts sending data immediately. To keep the byte stream ordered, we assign the sequence number to every bite and we start counting at zero. This seems fine at first. If bite 200 arrives before the bite 100, the receiver waits. If bite 100 arrives twice, it drops the duplicate. But this setup hides two separate problems. The first problem is the client doesn't know if the server exists. And the second problem happens due to initial sequence numbers starting at zero for every connection. Let's imagine a client opens a connection. It sends some data and then it closes it. A few packets from that old connection can get delayed somewhere in the network and a few seconds later the client opens a new connection to the same server and reuses the same client port. The connection identifier is the same in this case. Now the old packet with the same identifier finally arrives and its sequence number fits what the new connection expects.
the server has no way to know it came from the previous connection and the old data can land inside a fresh stream. I call this the zombie packet problem. The solution is to avoid starting every connection at the same number. Instead, every connection has a fresh start, unpredictable 32-bit number.
I will refer to this number as random in this video, but modern TCP stack computes it from three things. the clock, a secret, and the connection details. The clock ensures that reopening the same connection later advances the starting number. The secret makes it hard to guess, and the connection details keep different conversations from all having the same sequence pattern. So, the old connection might start at around 240 million, for example, and the new one might start at around 3 1/2 billion. The receiver only accepts bytes whose sequence numbers fall inside its current receive window.
A fresh starting number makes stale packets less likely to fit the receive window. And TCP uses other protections too. We will come back to one of them later.
Okay. So fixed initial sequence numbers are not an option. But if the server doesn't know what number the client picked or vice versa, it can't understand the string. So we need at least one message to exchange the initial numbers.
Let's try sending one message where the client says I want to start a connection and my starting number is 3 and 1/2 billion. The server receives it and now it can understand the client stream.
However, the first problem still remains. The client doesn't know if the server exists and is ready to receive bytes from the client. The server has to send something back to show the client that it's actually there. So, one message can't finish the setup. We need a second message at least. So, let's try this. The client sends, I want to start a connection and my starting number is X. And the server replies, I got your number X and my starting number is Y.
Why does the server pick its own random starting number? We will come back to that later. These messages have names, by the way. When a message says I want to synchronize sequence numbers, we call it a scene. And when it acknowledges what the other side sent, we call it an act. So the client sends a scene and the server responds with scene act.
At first, this looks complete. The server knows the client's starting number. The client knows the server starting number. And the client knows the server is reachable because it got a reply. But now look at it from the server's point of view. The server received one scene and sent one scene act in a two message design. That would have to be enough. The server would now treat the connection as open. And that's the problem because the server doesn't know whether the client receives the synac. That packet could have been dropped on the way back. If that happens, the client never learns about why, but the server still thinks the connection is ready. So the two machines disagree. The server thinks there is a connection and the client does not.
There is another way why this can happen and this one gets to the heart of why TCP uses the third message. Imagine an old scene from a previous connection gets delayed somewhere in the network.
That connection closed a long time ago and the client moved on but later the old scene finally reaches the server. To the server it looks like a fresh request. In a two message design, the server would reply with seen act and then treats the connection as open. But the client is not opening that connection anymore. It has no reason to accept the server's reply. And again, the server has created a connection that only exists on one side. That's the problem two messages cannot solve. After two messages, the client knows that the server answered, but the server still doesn't know whether the client saw that answer or whether the original scene even belongs to a current connection attempt.
The missing piece is a confirmation from the client. The client needs one more message saying, I received your starting number Y and the next sequence number I expect from you is Y + 1. That's the third message, the final act, which confirms the client saw the server's seen act. Without that, the server would have to treat a connection as ready after hearing only one packet from the network. That packet might be stale, spoofed, or followed by a lost reply. So the third message is what lets the server safely move from the seen received state meaning someone asked to connect to established state meaning both sides have seen each other's starting numbers and both sides agree this connection exists.
Notice what happened conceptually here.
First the client sent X then the server acknowledged X and then the server sent Y and the client acknowledged Y. Those are four separate jobs. But the server can combine the middle two into one.
Scene act. So four logical steps become three messages. Scene scene act and act.
That's why three messages are enough.
And why two are not. An old scene cannot complete this exchange by itself. The server may respond to it because the server cannot immediately know that it's old, but the client is not waiting for that connection anymore. So it will not return the expected final act. It will normally answer with a reset and the server will discard the halfopen state.
And what if the final act itself gets dropped? The client may already consider the connection established but the server remains in the scene received state. In this case, the server retransmits its scene act and the client acknowledges it again and the two sides eventually recover. Any client data sent after the sync also carries an acknowledgement. So that can complete the handshake as well. Note that the final egg doesn't prove the client's identity. TCP is not an authentication protocol. It proves something narrower which is the sender knew the server's current sequence number. Why? Normally because it receives the scene act. Now we can come back to the question we skipped. Why does the server pick its own starting number? Why not just reuse the client's number X?
The first reason is that well there is no reason to couple the numbering in one direction to the numbering in the other.
Each sender controls the sequence numbers for the bites it sends. So each site chooses its own initial sequence number. The second reason is that Y should be hard to predict. Imagine an off path attacker sending a scene with a spoofed source address pretending to be a machine that server trusts. The server sends scene to that machine, not to the attacker. And because the attacker is off path, it can't see sequence number Y. To forge the final act, it has to guess Y + one. If the server's starting numbers are predictable, that guess may be possible. If the server simply reuses X, then it is trivial because it's the attacker that chooses X in the spoofed scene packet. With a correct guess, the attacker can forge the final act and make the server enter the established state. The attacker would still be communicating blindly and the trusted machine could disrupt the attempt by returning a reset. But predictable sequence numbers have historically made this kind of address spoofing attack possible. That is essentially how the famous attack associated with Kevin Mitnik work in 1994.
The trusted machine was first kept from interfering and predictable server sequence numbers were then used to forge a connection from its address.
Modern TCP makes initial sequence numbers difficult for an offpath attacker to predict. But this still isn't an authentication. Real authentication has to come from a cryptographic protocol such as TLS or SSH.
Okay, so the three-way handshake gives TCP the right correctness rule. The server does not treats the connection as fully established until the client acknowledges the server's sin. But there is still a cost. While the server waits for that final act, it normally remembers a halfopen connection. That state includes the endpoint address and ports, sequence numbers, timers, and enough information to recognize the final lock if it arrives. For one connection, that's tiny. But servers handle many connection attempts at once and attackers can exploit exactly that waiting period. An attacker can send a large number of scene packets often with fake source addresses. And this is called a scene flood attack. The server replies with scene acts and waits for final acts that never come. If enough halfopen connections pile up, they can fill the server's connection queue and new connections from real clients may time out or get rejected.
The third message doesn't make this resource problem disappear. It provides the correct rule for establishing a connection, but the server still has to survive while it waits. One important defense mechanism is scene cookies, which many TCP stacks can use when the connection Q is under pressure. With scene cookies, the server encodes the information it will need into its own starting sequence number. If a valid final act returns, the server can reconstruct the connection state instead of having stored the normal per attempt state while it waited.
That protects the connection queue from this type of attack, but it doesn't make the server immune to every high volume attack. The server still has to receive and process packets. So, sync cookies defend against the cost of waiting.
Now, let's look at the real packet capture. I ran TCP dump and made one HTTP request to example.com and opened the capture in Wireshark.
If your Wireshark shows sequence numbers as zero and one, that's because it uses relative sequence numbers by default. I turned on raw sequence numbers so we can see what TCP put on the wire. Pocket one is the scene. My laptop sent it from the port 57649 to the server's port 80. The raw sequence number is 42 million and something and that's the client's initial sequence number. Pocket two is the scene act. The server sends back its raw sequence number which is 4 billion and something and the act field is 240 million and something + one. The server is saying I got your scene and the next bite I expect is one number after that.
The scene itself consumes one sequence number even though it does not carry application data and packet 3 is the final act from the client. The client sequence number is now 240 million and something + one and the act field is 4 billion and something + one which is the server's starting number plus one. That is the proof of life step. The client is proving it saw the server's random number. After this the data can flow. The first HTTP bytes continue from the numbers established during the handshake. So the real packets have exactly the shape we designed. Two unpredictable starting numbers, two acknowledgements, and three messages in total.
For those of you that want to know more, there are extensions. On top of this, TCP fast open can put application data in the scene packet on repeat connections. But the ordinary handshake is still the clean base case. Scene senac and finally act.
Before we close, it's worth mentioning that fresh starting sequence numbers are not TCP's only defense against zombie packets. They make stale packets less likely to fit into a new stream's receive window. But TCP also tries to prevent all the new versions of the same connection from overlapping in time.
After a connection closes normally, the end point that actively closes it usually enters the time weight state.
Instead of forgetting the connection immediately, TCP keeps its foruple around for a while. This does two things. First, imagine the act that finished closing the connection gets dropped. The other side will send it fin again and the endpoint in time weight state can respond with another act. And second, waiting gives the old packets time to disappear before the same foruple is used for a new connection.
During time wait, TCP normally avoids immediately reusing that foruple.
Some implementations can reuse it earlier when sequence numbers or timestamps make it possible to distinguish old packets from the new ones safely. So fresh starting numbers make old packets less likely to fit while time weight reduces the chance that all the new versions of the same connection overlap in the first place.
If this kind of design decision case study is your thing, I write a newsletter on systems trade-offs and the engineering decisions behind grill software. It's free. Link in the description. Thanks for watching the whole video and I'll see you in the next one.
Related Videos

TOP 15 Data compression Interview Questions and Answers 2019 Part-2 | Data compression | Wisdom jobs
wisdomjobs
281 views•2019-06-28

CTS 158: 802.11w Management Frame Protection
ClearToSend
4K views•2019-02-04

NDSS 2019 Send Hardest Problems My Way: Probabilistic Path Prioritization for Hybrid Fuzzing
NDSSSymposium
496 views•2019-04-02

How realistic is Cities: Skylines?
CityBeautiful
159K views•2019-02-14

GUIs & TUIs: Choosing a User Interface for Your Python Project | Real Python Podcast
realpython
2K views•2025-04-04

The OSI Model - Explained by Example
hnasr
225K views•2019-05-12

Cloud Computing - Introduction
elithecomputerguy
98K views•2019-10-07

From Traveler's Dilemma to Dynamic Routing | Demystifying Networking
IITBombayJuly
5K views•2019-08-04
Trending

WOW! Judge TURNS THE TABLES on Trump in His OWN $10B LAWSUIT!!!
MeidasTouch
197K views•2026-07-23

Playstation NO DISC/NO BUY Fight Is Over...
DavidJaffeGames
4K views•2026-07-23

Steam and Xbox Just Dropped The Hammer On PlayStation
OhNoItsAlexx
9K views•2026-07-23

Americans Confused in Australia for 17 Minutes Straight
IWrocker
17K views•2026-07-23