Loading learning content...
If sequence and acknowledgment numbers track data, then flags control behavior. These six bits—SYN, ACK, FIN, RST, PSH, and URG—occupy a single byte of the TCP header but govern the entire lifecycle of every TCP connection.
Each flag is a boolean signal, either set (1) or clear (0), and their combinations create a rich vocabulary for connection management. A segment with SYN set initiates a connection; one with FIN set gracefully terminates it; one with RST set abruptly aborts it. Understanding these flags is essential for:
By the end of this page, you will understand the position and encoding of flags in the TCP header, the complete semantics of each flag (SYN, ACK, FIN, RST, PSH, URG), how flags combine in real-world segments, the role of flags in connection state transitions, security implications and flag-based attacks, and additional flags added in modern TCP extensions.
The TCP flags reside in the 13th byte of the TCP header (bits 100–107 in 0-indexed terms, or commonly referred to as the 13th and 14th bytes when including the reserved bits). The flags occupy 6 bits within a larger control field that also contains the 4-bit data offset and reserved bits.
| Byte Position | Bit Range | Field | Size |
|---|---|---|---|
| Byte 12 | Bits 96–99 | Data Offset | 4 bits |
| Byte 12 | Bits 100–102 | Reserved | 3 bits |
| Byte 12 | Bit 103 | NS (Nonce Sum) | 1 bit (ECN extension) |
| Byte 13 | Bit 104 | CWR (Congestion Window Reduced) | 1 bit (ECN) |
| Byte 13 | Bit 105 | ECE (ECN-Echo) | 1 bit (ECN) |
| Byte 13 | Bit 106 | URG (Urgent) | 1 bit |
| Byte 13 | Bit 107 | ACK (Acknowledgment) | 1 bit |
| Byte 13 | Bit 108 | PSH (Push) | 1 bit |
| Byte 13 | Bit 109 | RST (Reset) | 1 bit |
| Byte 13 | Bit 110 | SYN (Synchronize) | 1 bit |
| Byte 13 | Bit 111 | FIN (Finish) | 1 bit |
The Classic Six Flags:
The original TCP specification (RFC 793) defined six control flags. From most significant to least significant bit position:
| URG | ACK | PSH | RST | SYN | FIN |
| 6 | 5 | 4 | 3 | 2 | 1 | (bit weights)
| 32 | 16 | 8 | 4 | 2 | 1 | (decimal values)
Common Flag Combinations:
| Decimal | Binary | Flags Set | Common Name |
|---|---|---|---|
| 2 | 000010 | SYN | Connection request |
| 18 | 010010 | SYN+ACK | Connection response |
| 16 | 010000 | ACK | Data or acknowledgment |
| 24 | 011000 | ACK+PSH | Data push |
| 17 | 010001 | FIN+ACK | Graceful close |
| 4 | 000100 | RST | Connection abort |
12345678910111213141516171819202122232425262728293031
/* TCP Flag Bit Definitions */#define TCP_FIN 0x01 /* 0000 0001 - Finish */#define TCP_SYN 0x02 /* 0000 0010 - Synchronize */#define TCP_RST 0x04 /* 0000 0100 - Reset */#define TCP_PSH 0x08 /* 0000 1000 - Push */#define TCP_ACK 0x10 /* 0001 0000 - Acknowledgment */#define TCP_URG 0x20 /* 0010 0000 - Urgent */ /* Extended flags (ECN - RFC 3168) */#define TCP_ECE 0x40 /* 0100 0000 - ECN-Echo */#define TCP_CWR 0x80 /* 1000 0000 - Congestion Window Reduced */#define TCP_NS 0x100 /* 1 0000 0000 - Nonce Sum (in data offset byte) */ /* Extract flags from TCP header */uint8_t get_tcp_flags(const uint8_t* tcp_header) { /* Flags are in byte 13 (offset 13 from start) */ return tcp_header[13];} /* Check if specific flag is set */bool has_syn(uint8_t flags) { return (flags & TCP_SYN) != 0; }bool has_ack(uint8_t flags) { return (flags & TCP_ACK) != 0; }bool has_fin(uint8_t flags) { return (flags & TCP_FIN) != 0; }bool has_rst(uint8_t flags) { return (flags & TCP_RST) != 0; }bool has_psh(uint8_t flags) { return (flags & TCP_PSH) != 0; }bool has_urg(uint8_t flags) { return (flags & TCP_URG) != 0; } /* Common patterns */bool is_syn_only(uint8_t flags) { return flags == TCP_SYN; }bool is_syn_ack(uint8_t flags) { return flags == (TCP_SYN | TCP_ACK); }bool is_fin_ack(uint8_t flags) { return (flags & (TCP_FIN | TCP_ACK)) == (TCP_FIN | TCP_ACK); }The SYN (Synchronize) flag initiates a TCP connection by synchronizing sequence numbers between endpoints. It is the fundamental mechanism for connection establishment and appears only at the very beginning of a connection.
Primary Functions:
SYN Segment Types:
| Type | Flags | Direction | Purpose |
|---|---|---|---|
| SYN | SYN only | Client → Server | Initial connection request |
| SYN-ACK | SYN + ACK | Server → Client | Accept connection, acknowledge client's SYN |
The Three-Way Handshake:
1. Client → Server: SYN, Seq=X
"I want to connect. My starting sequence is X."
2. Server → Client: SYN+ACK, Seq=Y, Ack=X+1
"Connection accepted. My starting sequence is Y. I expect X+1 next."
3. Client → Server: ACK, Seq=X+1, Ack=Y+1
"Confirmed. I expect Y+1 next."
After this exchange, both sides have synchronized sequence numbers and can begin data transfer.
An attacker can exhaust server resources by sending massive numbers of SYN packets without completing the handshake. The server allocates memory for each half-open connection. Mitigations include SYN cookies (stateless SYN-ACK), SYN proxies, and rate limiting. SYN cookies encode state into the ISN, avoiding memory allocation until handshake completes.
The ACK (Acknowledgment) flag is the most frequently set flag in TCP segments. It indicates that the acknowledgment number field in the header is valid and should be processed.
Behavior by ACK State:
| ACK Flag | Acknowledgment Number Field | When This Occurs |
|---|---|---|
| Clear (0) | Ignored/undefined | Initial SYN only |
| Set (1) | Must be processed | Every other segment |
After the initial SYN segment, every TCP segment has the ACK flag set. Even pure data segments acknowledge previously received data. This makes ACK the default state of TCP communication.
ACK-Only Segments:
Segments with only the ACK flag set (no data, no SYN, no FIN) serve specific purposes:
ACK Characteristics:
If you capture TCP traffic and see a segment without ACK set, it's almost certainly the initial SYN of a new connection. Any segment without ACK after the handshake is anomalous and may indicate an attack or misconfiguration.
ACK in State Transitions:
The ACK flag is critical for TCP state machine transitions:
SYN_SENT state:
- Receive SYN+ACK → Send ACK → Move to ESTABLISHED
SYN_RECEIVED state:
- Receive ACK for our SYN → Move to ESTABLISHED
FIN_WAIT_1 state:
- Receive ACK for our FIN → Move to FIN_WAIT_2
The FIN (Finish) flag signals the sender's intention to terminate the connection gracefully. Unlike RST, which abruptly aborts, FIN allows both sides to finish transmitting any remaining data before closing.
FIN Semantics:
The Half-Close:
TCP connections are full-duplex, meaning data can flow in both directions independently. A FIN closes only one direction:
A sends FIN to B:
- A → B direction: Closed (A will send no more data)
- B → A direction: Open (B can still send data)
- This is called a "half-close"
The connection is fully closed only when both sides have sent and acknowledged FINs.
The Four-Way Close:
Graceful termination typically requires four segments:
1. A → B: FIN+ACK, Seq=X
"I'm done sending. Final data was up to X."
2. B → A: ACK, Ack=X+1
"I acknowledge your FIN."
3. B → A: FIN+ACK, Seq=Y
"I'm also done sending. Final data was up to Y."
4. A → B: ACK, Ack=Y+1
"I acknowledge your FIN. Connection closed."
Piggybacked FIN-ACK:
Steps 2 and 3 can be combined if B has no more data:
1. A → B: FIN+ACK
2. B → A: FIN+ACK (acknowledges A's FIN and sends B's FIN)
3. A → B: ACK (acknowledges B's FIN)
This reduces the exchange to three segments.
After sending the final ACK, TCP enters TIME_WAIT for 2×MSL (Maximum Segment Lifetime, typically 60-120 seconds). This ensures: (1) the final ACK is retransmittable if lost, and (2) old segments from this connection drain from the network before the same four-tuple is reused.
The RST (Reset) flag immediately and abruptly terminates a TCP connection. Unlike FIN's graceful closure, RST discards any outstanding data and ends the connection without negotiation.
When RST is Generated:
| Scenario | Trigger | RST Contents |
|---|---|---|
| Connection to closed port | SYN to port with no listener | RST+ACK, Ack=SYN's Seq+1 |
| Data on half-open connection | Server rebooted during handshake | RST, Seq=what receiver expects |
| Application abort | close() with SO_LINGER=0 | RST, discards outgoing queue |
| FIN during SYN_RECEIVED | FIN arrives before ESTABLISHED | RST |
| Invalid segment | Segment fails security check | RST (or silent discard) |
RST vs. FIN:
| Aspect | RST | FIN |
|---|---|---|
| Data handling | Discards unsent data | Waits for data to complete |
| State transitions | Immediate CLOSED | Goes through FIN_WAIT, TIME_WAIT |
| TIME_WAIT | No TIME_WAIT | TIME_WAIT required |
| Acknowledgment | RST is not acknowledged | FIN must be acknowledged |
| Use case | Error conditions, aborts | Normal shutdown |
Attackers can inject spoofed RST segments to forcibly terminate connections. If the attacker can guess the correct sequence number (within the receive window), the victim accepts the RST and closes the connection. This attack has been used to disrupt BGP sessions between ISPs. Mitigations include TCP-AO authentication and requiring RST sequence to exactly match expected.
The PSH (Push) flag signals that the receiver should "push" buffered data to the application immediately rather than waiting to accumulate more data. It's a hint about data urgency, not a hard requirement.
PSH Semantics (Receiver Side):
Common PSH Usage:
Sender Behavior:
When an application makes a write() call, TCP buffering may delay transmission:
Without PSH:
write("GET / HTTP/1.1\r
") → Buffer, maybe wait for more data
With PSH:
write("GET / HTTP/1.1\r
", MSG_PUSH) → Send immediately with PSH set
However, most TCP implementations automatically set PSH when:
Modern Reality:
In practice, the PSH flag is often set automatically and rarely configured by applications. Modern operating systems typically set PSH on every segment that empties the send buffer, making it a nearly ubiquitous flag in TCP traffic.
PSH interacts with Nagle's algorithm. When an application wants low-latency delivery, setting TCP_NODELAY disables Nagle (preventing send-side buffering), and PSH prevents receive-side buffering. Together, they minimize end-to-end latency for interactive applications.
| PSH State | Sender Behavior | Receiver Behavior |
|---|---|---|
| Clear (0) | May buffer more application writes | May buffer before application read() |
| Set (1) | Send buffered data now | Deliver buffered data to application promptly |
The URG (Urgent) flag and its companion Urgent Pointer field implement out-of-band data delivery within the TCP stream. Urgent data can be processed before normally sequenced data, enabling time-sensitive signals.
URG Mechanism:
When URG is set:
Segment: Seq=1000, URG=1, UrgPtr=10, Data="URGENT!!!NORMAL"
Interpretation:
Urgent data: bytes 1000-1009 ("URGENT!!!" - 10 bytes)
Normal data: bytes 1010+ ("NORMAL")
Historical Ambiguity:
The original RFC 793 was ambiguous about whether Urgent Pointer points to the last byte of urgent data or one past it. Different implementations chose differently:
This inconsistency caused interoperability problems, and modern systems follow RFC 1122.
The urgent mechanism is rarely used in modern applications. Originally intended for out-of-band signals (like TELNET interrupt), most applications now use separate control connections or in-band signaling. Some protocols (like FTP) still use URG for ABORT commands, but its use is declining.
Application Interface:
Unix/POSIX systems expose urgent data through:
/* Sender: Send out-of-band data */
send(sock, "!", 1, MSG_OOB);
/* Receiver: Check for urgent data */
int atmark;
ioctl(sock, SIOCATMARK, &atmark);
if (atmark) {
recv(sock, buf, 1, MSG_OOB); /* Receive urgent byte */
}
/* Or use SIGURG signal to be notified of urgent data */
fcntl(sock, F_SETOWN, getpid());
/* SIGURG delivered when urgent data arrives */
Security Implications:
Some intrusion detection systems have historically failed to properly handle urgent data, creating evasion opportunities. Fragmented urgent data across multiple segments could bypass detection of malicious content.
TCP flags aren't used in isolation—their combinations trigger state machine transitions that define the connection lifecycle. Understanding these combinations is essential for network debugging and security analysis.
| Flags | Binary | Meaning | When Seen |
|---|---|---|---|
| SYN | 000010 | Connection initiation | First packet of connection |
| SYN+ACK | 010010 | Accept connection | Second packet of handshake |
| ACK | 010000 | Acknowledgment/Data | Most common in established state |
| PSH+ACK | 011000 | Data, deliver immediately | Interactive traffic, request/response |
| FIN+ACK | 010001 | Close connection | Connection termination |
| RST | 000100 | Abort connection | Error or rejection |
| RST+ACK | 010100 | Abort with acknowledgment | Common reset response |
| URG+ACK+PSH | 111000 | Urgent data with push | Out-of-band signals (rare) |
Illegal or Suspicious Combinations:
Certain flag combinations are protocol violations or attack signatures:
| Combination | Why Suspicious |
|---|---|
| SYN+FIN | Ambiguous: start or end? Confuses IDS/firewalls |
| SYN+RST | Contradictory: "start" and "abort" |
| FIN+RST | Contradictory: "graceful close" and "abort" |
| No flags (NULL scan) | Invalid segment; used for OS fingerprinting |
| All flags (XMAS scan) | Invalid; used for firewall probing |
| SYN+FIN+RST+etc | "Christmas tree" packet; attack probe |
Security tools use these anomalies for detection:
Stateless firewalls that only check individual flags (like 'block SYN to port 22') can be evaded. An attacker might send ACK packets that pass the filter but are processed by a host that accepts data on established connections. Stateful firewalls track connection state to prevent such evasion.
Beyond the original six flags, modern TCP includes three additional flags for Explicit Congestion Notification (ECN):
ECN Flags:
| Flag | Purpose |
|---|---|
| NS (Nonce Sum) | Protection against accidental/malicious ECN marking removal |
| CWR (Congestion Window Reduced) | Sender acknowledges ECN-Echo, has reduced sending rate |
| ECE (ECN-Echo) | Receiver echoes congestion indication back to sender |
ECN Operation:
1234567891011121314151617181920212223242526
import socket # Enable ECN for a socket (Linux)sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # IP_TOS options for ECN# 0x01 = ECT(1) - ECN Capable Transport (ECT(1))# 0x02 = ECT(0) - ECN Capable Transport (ECT(0))# 0x03 = CE - Congestion Experienced # Set TCP_QUICKACK for immediate ACK of congestionsock.setsockopt(socket.IPPROTO_TCP, socket.TCP_QUICKACK, 1) # Check system ECN configuration# Linux: /proc/sys/net/ipv4/tcp_ecn# Values:# 0 = ECN disabled# 1 = ECN enabled (initiate and accept) # 2 = ECN enabled (accept only, don't initiate) # ECN negotiation happens automatically in the SYN exchange# SYN with ECE+CWR set = "I support ECN"# SYN-ACK with ECE = "I also support ECN, let's use it" # Wireshark filter for ECN traffic# tcp.flags.cwr == 1 or tcp.flags.ecn == 1ECN allows routers to signal congestion BEFORE dropping packets. This means TCP can slow down proactively rather than waiting for packet loss. Studies show ECN can significantly reduce latency and retransmissions in congested networks, particularly benefiting short flows.
We've explored the six classic TCP control flags and their modern extensions—the Boolean signals that govern every aspect of TCP connection management.
What's Next:
With flags controlling connection lifecycle and data delivery behavior, one critical piece remains: window size. The next page explores the window field—the mechanism that enables flow control by advertising available receive buffer space, ensuring senders don't overwhelm receivers.
You now possess comprehensive understanding of TCP control flags: their position in the header, individual semantics, common combinations, state machine implications, security considerations, and ECN extensions. This prepares you for understanding how the window size field enables flow control.