Connection Tracking: The Hidden State Machine
In the previous chapter, we learned that netfilter is the framework responsible for packet filtering, NAT, and packet manipulation inside the Linux kernel.
One important feature of netfilter is that it is stateful. Instead of treating every packet as an independent event, the kernel remembers ongoing network conversations. This memory is provided by a subsystem called connection tracking, often abbreviated as conntrack.
Connection tracking is one of the most important—and least visible—parts of Linux networking. It enables stateful firewalls, makes NAT work correctly, and allows Linux to understand the relationship between packets.
In this chapter, we'll explore how connection tracking works, how to inspect it, and why understanding it is essential when troubleshooting Linux networks.
What Is Connection Tracking?
Despite its name, connection tracking does not only track TCP connections.
Instead, it tracks network flows.
A flow is identified by information such as:
- Source IP address
- Destination IP address
- Transport protocol (TCP, UDP, ICMP, ...)
- Source port (if applicable)
- Destination port (if applicable)
This combination is often called the 5-tuple.
For example:
| Source | Destination | Protocol |
|---|---|---|
| 192.168.1.10:53000 | 10.0.0.5:443 | TCP |
Once Linux sees the first packet belonging to this flow, it creates an entry in the connection tracking table.
Future packets matching the same flow can be recognized immediately.
Why Does Linux Need Connection Tracking?
Imagine a firewall without memory.
Every packet would have to be evaluated independently.
Consider a TCP connection:
Client Server
SYN -------------------->
<---------------- SYN-ACK
ACK -------------------->
HTTP Request ----------->
<---------------- HTTP Response
Without remembering previous packets, Linux cannot easily determine whether an incoming packet belongs to an existing connection or is an unexpected packet.
Connection tracking solves this problem.
It allows Linux to recognize that:
- this packet belongs to an already established flow,
- this packet is a reply,
- this packet is part of an existing NAT mapping,
- this packet is invalid.
The Hidden State Machine
Connection tracking internally maintains a state machine for every tracked flow.
The most commonly encountered states are:
NEWESTABLISHEDRELATEDINVALID
These are connection tracking states, not TCP states.
That distinction is extremely important.
NEW
NEW means the packet is attempting to start a new flow.
For TCP, this is typically the initial SYN packet.
Example:
Client ---- SYN ----> Server
The flow has never been seen before.
ESTABLISHED
Once traffic is flowing in both directions, the connection becomes established.
Client <=====> Server
Packets belonging to this conversation match the existing conntrack entry.
Most firewall rules simply allow:
- established
- related
traffic while applying stricter rules only to new connections.
RELATED
Some protocols create additional connections that logically belong to an existing one.
Examples include:
- FTP data channels
- Some ICMP error messages
Although these packets belong to a different flow, they are related to an existing tracked connection.
INVALID
Packets may be marked invalid for many reasons:
- malformed packets
- packets that cannot be associated with any flow
- corrupted headers
- unexpected sequence of packets
Many firewall configurations simply drop invalid packets.
Conntrack State vs TCP State
A very common misunderstanding is confusing conntrack states with TCP states.
TCP itself has many protocol states:
- LISTEN
- SYN_SENT
- SYN_RECEIVED
- ESTABLISHED
- FIN_WAIT
- TIME_WAIT
- CLOSE_WAIT
Connection tracking does not replace TCP.
Instead, it provides a higher-level view used by the networking stack.
Think of it like this:
Application
│
TCP State Machine
│
Connection Tracking
│
Netfilter
│
IP Layer
TCP handles reliable communication.
Connection tracking remembers flows for packet processing.
Following a New Connection
Let's follow an HTTPS connection.
Client Linux Server
SYN ------------------------>
conntrack creates entry
SYN-ACK <--------------------
ACK ------------------------->
HTTPS Request --------------->
HTTPS Response <--------------
After the first packet, Linux already knows:
- who initiated the flow,
- both endpoints,
- transport protocol,
- ports,
- current tracking state.
Every later packet can immediately match this entry.
Where Are Connections Stored?
The Linux kernel stores tracked flows inside the connection tracking table.
This table exists entirely inside the kernel.
Each entry contains information such as:
- source address
- destination address
- ports
- protocol
- timeout
- current state
- NAT information (if applicable)
The table is constantly updated as packets arrive.
When a flow becomes inactive, its entry eventually expires.
Inspecting the Connection Tracking Table
The easiest way to inspect tracked connections is with the conntrack utility.
On Debian-based systems:
sudo apt install conntrack
List current entries:
sudo conntrack -L
Example output:
tcp 6 431999 ESTABLISHED src=192.168.1.10 dst=10.0.0.5 sport=53000 dport=443 \
src=10.0.0.5 dst=192.168.1.10 sport=443 dport=53000
Although the output looks intimidating, notice that it simply describes:
- original direction
- reply direction
- protocol
- state
- timeout
Watch Connections Appear
Open one terminal:
sudo conntrack -E
This command listens for connection tracking events.
Open another terminal:
curl https://example.com
You should see events similar to:
NEW
UPDATE
DESTROY
This demonstrates that connection tracking is constantly updating its internal state.
Compare ss and conntrack
Run:
ss -tn
You may see:
ESTAB 192.168.1.10:53000 10.0.0.5:443
Now compare:
sudo conntrack -L
Although both commands show active communication, they answer different questions.
ss shows:
- sockets
- processes
- transport-layer information
conntrack shows:
- packet flows
- flow state
- NAT information
- kernel tracking entries
They complement each other.
Observe UDP Tracking
Many people think only TCP is tracked.
Let's prove otherwise.
Start a DNS lookup:
dig example.com
Immediately inspect:
sudo conntrack -L | grep udp
You should see a UDP flow.
Although UDP has no handshake, Linux still tracks the exchange for a limited amount of time.
Connection tracking is therefore about flows, not protocol reliability.
Connection Tracking and NAT
Connection tracking and NAT are tightly coupled.
Consider a home router.
Laptop
192.168.1.20
│
▼
Linux Router
Public IP
│
▼
Internet
When the laptop sends a packet:
192.168.1.20:53000
↓
203.0.113.15:41000
The router changes the source address.
But when the reply returns:
203.0.113.15:41000
How does Linux know which internal machine should receive it?
Connection tracking remembers the translation.
Without conntrack, NAT would fail after the first packet.
This is why NAT depends heavily on connection tracking.
We'll examine NAT in detail in the next chapter.
Timeouts
Entries cannot remain in memory forever.
Each protocol has its own timeout values.
Examples:
- TCP established connections
- UDP flows
- ICMP exchanges
Once the timeout expires, the kernel removes the entry.
You can inspect some timeout-related settings:
sysctl net.netfilter.nf_conntrack_tcp_timeout_established
Example:
net.netfilter.nf_conntrack_tcp_timeout_established = 432000
This value represents the timeout (in seconds) for established TCP connections.
Connection Tracking Capacity
Since every active flow consumes kernel memory, the tracking table has a maximum size.
Check the current limit:
cat /proc/sys/net/netfilter/nf_conntrack_max
Check current usage:
cat /proc/sys/net/netfilter/nf_conntrack_count
Example:
Current entries : 1854
Maximum entries : 262144
If the table becomes full, Linux cannot track additional flows.
This can cause:
- connection failures
- dropped packets
- NAT failures
- firewall issues
Monitoring conntrack usage is therefore an important operational task on busy systems.
When Connection Tracking Causes Problems
Although conntrack is extremely useful, it can also become a bottleneck.
Common issues include:
- exhausted conntrack table
- incorrect timeout values
- asymmetric routing
- hardware offloading interactions
- extremely high connection rates
Large Kubernetes clusters, load balancers, and busy NAT gateways often require conntrack tuning.
When Is Connection Tracking Not Used?
Not every packet must be tracked.
Examples include:
- some high-performance routers
- specialized packet forwarding
- certain DDoS mitigation scenarios
Skipping connection tracking reduces memory usage and CPU overhead, but you lose stateful firewalling and NAT support.
For most Linux servers, leaving connection tracking enabled is the correct choice.
Debugging Checklist
When packets do not behave as expected, ask:
- Does the flow appear in
conntrack -L? - Is the state what you expect?
- Is the conntrack table full?
- Are timeout values appropriate?
- Is NAT depending on this connection?
Answering these questions often narrows the problem quickly.
Summary
- Connection tracking is a kernel subsystem that remembers network flows.
- It is implemented as part of the netfilter framework.
- It tracks flows, not just TCP connections.
- Common conntrack states are
NEW,ESTABLISHED,RELATED, andINVALID. - Conntrack states are different from TCP protocol states.
- NAT relies heavily on connection tracking to map reply traffic back to the correct flow.
- The
conntrackutility allows you to inspect, monitor, and troubleshoot tracked flows. - Every tracked flow consumes kernel memory, making table sizing and monitoring important on busy systems.
In the next chapter, we'll build on this foundation by exploring Network Address Translation (NAT) and see how connection tracking enables address and port rewriting while preserving end-to-end communication.