What this header is actually for
IP's job stops at "get this packet from one host toward another, best effort, no promises." It doesn't guarantee delivery, doesn't guarantee order, and a packet can even arrive duplicated. TCP sits directly on top of IP and makes up the difference. Every field below exists to support one of three jobs:
- Identifying which application conversation a segment belongs to.
- Tracking exactly which bytes have arrived.
- Controlling how much data is allowed to be in flight at once.
Picture a warehouse shipping cartons through a courier that makes no promises on its own: no guaranteed delivery, no guaranteed order, cartons can even get duplicated in transit. TCP's header is the packing slip stapled to every carton: which shipment this carton belongs to, which carton number it is within the batch, a receipt confirming which cartons already arrived, how much room the receiving dock currently has for more, a stamp for special handling, and a tamper seal. That's the whole header, mapped field by field below.
The header, field by field
| Field | Size | What it holds |
|---|---|---|
| Source Port | 16 bits | The sending application's port, identifying which local conversation this segment belongs to |
| Destination Port | 16 bits | The receiving application's port, e.g. 443 for HTTPS |
| Sequence Number | 32 bits | The byte offset of the first byte of data in this segment, relative to the connection's initial sequence number |
| Acknowledgment Number | 32 bits | The next byte the sender of this segment expects to receive, valid whenever ACK is set |
| Data Offset | 4 bits | Header length in 32-bit words, telling the receiver where the header ends and Data begins |
| Reserved | 6 bits | Set to 0, unused |
| Flags | 6 bits | URG, ACK, PSH, RST, SYN, FIN, one bit each; see below |
| Window Size | 16 bits | How many more bytes the sender of this segment can currently receive, the core of flow control |
| Checksum | 16 bits | Integrity check covering the TCP header and data |
| Urgent Pointer | 16 bits | Only meaningful when URG is set; rarely used in practice |
| Options | 0–40 bytes | Optional extensions, most commonly MSS (Maximum Segment Size) and window scaling |
| Data | Variable | The actual application payload: an HTTP request, part of a file transfer, anything |
The six control flags
| Flag | Means | Typically set when |
|---|---|---|
| SYN | Synchronize sequence numbers, start a new connection | Only on the first two segments of the three-way handshake |
| ACK | The acknowledgment number field is valid | On effectively every segment after the handshake's first one |
| FIN | This sender has no more data to send | Initiating a normal, graceful close |
| RST | Abort the connection immediately, no graceful close | The destination port is closed, or a hard error occurred |
| PSH | Push buffered data up to the application right away | Interactive traffic, like a single SSH (Secure Shell) keypress that shouldn't wait to be batched |
| URG | The Urgent Pointer field is significant | Rare in modern traffic |
The three-way handshake and connection termination, both built entirely from these flags, get their own full walkthrough in the next topic.
Sequence and acknowledgment numbers: TCP counts bytes, not packets
A sequence number doesn't count segments. It counts bytes. Each connection starts with a random initial sequence number (ISN), and every byte of data sent after that gets its own implicit position in the stream. A segment's Sequence Number field is just the position of its first byte; its Acknowledgment Number, sent by the other side, is the position of the next byte it's still waiting for.
Real initial sequence numbers are large and effectively random, chosen that way so an attacker can't easily guess them and inject forged segments into someone else's connection. Packet capture tools like Wireshark show "relative" sequence numbers instead, starting each connection at 0, purely to make captures easier to read. The actual numbers on the wire are the real, random ones.