<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Nick.Hu</title>
    <description>The latest articles on DEV Community by Nick.Hu (@nickhu150).</description>
    <link>https://dev.to/nickhu150</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4235516%2F0f4f62c4-a145-41f7-8dc4-ad5c153e945d.png</url>
      <title>DEV Community: Nick.Hu</title>
      <link>https://dev.to/nickhu150</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nickhu150"/>
    <language>en</language>
    <item>
      <title>The TCP three-way handshake, packet by packet</title>
      <dc:creator>Nick.Hu</dc:creator>
      <pubDate>Mon, 09 Nov 2026 16:25:40 +0000</pubDate>
      <link>https://dev.to/nickhu150/the-tcp-three-way-handshake-packet-by-packet-3l5k</link>
      <guid>https://dev.to/nickhu150/the-tcp-three-way-handshake-packet-by-packet-3l5k</guid>
      <description>&lt;p&gt;Every networking course shows the same diagram: client, server, three arrows, "connection established". It is accurate and completely unhelpful the first time a handshake fails in production, because the diagram hides the two things you actually need — what each packet carries, and what happens when one of them never arrives.&lt;/p&gt;

&lt;p&gt;This is the handshake at packet level, with the fields that matter and the failure modes that show up in real captures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Packet 1 — SYN
&lt;/h2&gt;

&lt;p&gt;The client picks a random initial sequence number (ISN) and sends a segment with the SYN flag set and no payload.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Flags: SYN
Seq:   1043 (relative), random 32-bit absolute value
Window: 64240
Options: MSS=1460, SACK permitted, Timestamps, Window scale=7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three things here are easy to miss.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The ISN is not zero.&lt;/strong&gt; On the wire it is a random 32-bit number, chosen per RFC 6528 so that an attacker cannot guess it and inject segments. Capture tools show a &lt;em&gt;relative&lt;/em&gt; sequence number starting at 0 because absolute numbers are unreadable; the relative view is a display convenience, not the protocol.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SYN consumes a sequence number.&lt;/strong&gt; The first byte of actual data will be ISN+1. This matters later when you compute acknowledgement numbers by hand and are off by one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Most of the negotiation already happened.&lt;/strong&gt; MSS, window scaling, SACK and timestamps are all offered in this first packet. If the server disagrees about any of them, the disagreement is visible in packet 2, not later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Packet 2 — SYN-ACK
&lt;/h2&gt;

&lt;p&gt;The server answers with its own SYN and an acknowledgement of the client's.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Flags: SYN, ACK
Seq:   8830 (server ISN)
Ack:   1044 (client ISN + 1)
Options: MSS=1460, SACK permitted, Timestamps, Window scale=7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;Ack: 1044&lt;/code&gt; is the load-bearing detail. It says "I received your SYN, and the next byte I expect from you is 1044" — which is the client's ISN plus one, because the SYN itself occupied a sequence number.&lt;/p&gt;

&lt;p&gt;If the server's MSS comes back as something like 1360 instead of 1460, you have found a tunnel or a middlebox clamping MSS. That single number explains a whole class of "large responses hang, small ones work" bugs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Packet 3 — ACK
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Flags: ACK
Seq:   1044
Ack:   8831 (server ISN + 1)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No payload, usually. The connection is now ESTABLISHED on both sides. The third packet is cheap — which is exactly why the handshake costs one round trip and not two.&lt;/p&gt;

&lt;p&gt;There is a detail worth knowing: the client may put data in this third packet. That is not a separate behaviour, just allowed by the protocol, and it is why some captures show a POST sitting inside the final ACK.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the handshake actually buys
&lt;/h2&gt;

&lt;p&gt;Three guarantees, in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The client can reach the server&lt;/strong&gt; (packet 1 arrived).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The server can reach the client&lt;/strong&gt; (packet 2 arrived — the server learned the client's address works in both directions, and the client learns the server is listening).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Both agree on sequence numbers&lt;/strong&gt;, so retransmission, ordering and duplicate detection all work.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Notice what it does &lt;em&gt;not&lt;/em&gt; buy: any authentication, any encryption, any proof that the server is the machine you meant. That is TLS's job, and it starts only after this finishes — which is why an HTTPS connection costs one RTT for TCP and one or two more for TLS.&lt;/p&gt;

&lt;h2&gt;
  
  
  When it goes wrong
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;SYN with no SYN-ACK.&lt;/strong&gt; Either the server is not listening (you get RST instead), a firewall is dropping silently, or the route is broken. A silent drop and a listen-less port look completely different: RST comes back in milliseconds, silence takes a retransmission timeout.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SYN-ACK with no ACK.&lt;/strong&gt; The server's kernel has a half-open connection sitting in its SYN queue, and it will retransmit the SYN-ACK on a timer (roughly 1s, 2s, 4s…) until it gives up. On the client side this usually means the final ACK was lost or the client vanished. On the server side, a pile of these is what a SYN flood looks like before SYN cookies are enabled.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Retransmitted SYN at 1s, 3s, 7s.&lt;/strong&gt; That spacing is not arbitrary — initial RTO of 1 second, doubled on each failure, per RFC 6298. If you see 1s, 2s, 4s instead, you are looking at a different stack or a middlebox generating its own retransmits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Simultaneous open.&lt;/strong&gt; Both sides send SYN at the same time, both send SYN-ACK, and the connection establishes anyway. Rare, legal, and confusing the first time you see it in a capture.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TCP Fast Open.&lt;/strong&gt; With a TFO cookie from a previous connection, the client can put data in the SYN itself, saving a round trip. It failed to deploy widely for reasons that are more about middleboxes than about the protocol.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading the timing
&lt;/h2&gt;

&lt;p&gt;The gap between packet 1 and packet 2 is the network RTT plus the server's SYN processing time. The gap between 2 and 3 is one-way latency plus client processing.&lt;/p&gt;

&lt;p&gt;If packet 1→2 is 200ms and 2→3 is 2ms, you have a distant server and a fast client. If 2→3 is also ~100ms, that is normal one-way delay, not a problem. What you are looking for is asymmetry: a 300ms gap on one leg and 5ms on the other means something in the middle is holding packets — a proxy, a load balancer mid-handshake, or a retransmission that your capture tool has labelled but you have not noticed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the diagram fails
&lt;/h2&gt;

&lt;p&gt;Three reasons, and they are all about scale.&lt;/p&gt;

&lt;p&gt;It draws two hosts. Real paths have a router, a NAT, sometimes a TLS-terminating proxy that answers the SYN itself and opens a &lt;em&gt;second&lt;/em&gt; handshake to the origin. That is why you sometimes see two handshakes in one capture and assume something is wrong.&lt;/p&gt;

&lt;p&gt;It draws uniform latency. Real RTTs drift, and the handshake is where you notice first, because three packets give you two intervals to compare.&lt;/p&gt;

&lt;p&gt;It draws the fields as labels. The fields are numbers with consequences — a window scale of 0 disables scaling entirely, an MSS of 536 means something ancient is in the path, a timestamp of 0 disables PAWS.&lt;/p&gt;

&lt;p&gt;The handshake is short enough to hold in your head. That is exactly why it is worth reading once at byte level rather than at arrow level: after that, every capture you look at has a story you can actually follow.&lt;/p&gt;




&lt;p&gt;If you want to step through it interactively — every field decoded, including the options block — I built a packet-by-packet visualiser for this exchange and 32 other protocols: &lt;a href="https://protocolexplorer.run/protocols/tcp/" rel="noopener noreferrer"&gt;Protocol Explorer, TCP handshake&lt;/a&gt;. The TCP and HTTP lessons are free.&lt;/p&gt;

</description>
      <category>networking</category>
      <category>tcp</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
