Firewalls — Stateless vs Stateful
A firewall decides which packets pass and which it discards, based on a policy you write. The split that matters most is how much the firewall remembers. A stateless filter judges each packet in isolation against its rule set; a stateful firewall tracks live connections, so once it sees you open an outbound flow it lets the replies back in automatically. That single difference is why, on a stateful device, you write one rule to allow inbound HTTPS and never think about the response packets — and why, on a stateless one, you must permit the return direction by hand or watch every connection hang half-open.
Statefulness is not free. Tracking connections means holding a row per flow in a table with a finite size, and that table is the resource an attacker aims at — fill it and the firewall starts dropping new legitimate connections. The stateless model has no such table and scales to line rate trivially, which is exactly why router ACLs are stateless and host or edge firewalls are stateful. Knowing which kind you are configuring tells you whether return traffic is your problem or the firewall's.
Packet Filtering on the 5-Tuple
Every firewall rule matches on some subset of the 5-tuple: source IP, destination IP, protocol, source port, destination port. A rule says "for packets matching this tuple, take this action," and the three actions that matter are allow (forward it), drop (discard it silently), and reject (discard it but send back an ICMP or TCP RST so the sender fails fast). Drop versus reject is a real choice: drop makes a port scanner wait for a timeout and hides that a host exists; reject is faster for legitimate clients but confirms the host is there.
A stateless filter does nothing but this matching, packet after packet, with no memory between them. That is enough to block obviously unwanted traffic — a rule denying all inbound to port 22 from outside your range is stateless and correct. It only becomes awkward when the legitimate traffic is bidirectional, because the filter has no idea that an inbound reply belongs to an outbound request you allowed a moment ago.
Stateless Rules versus Connection Tracking
A stateful firewall adds a connection table on top of the matching. When a packet starts a new flow and the policy allows it, the firewall records the flow — its 5-tuple and protocol state — and from then on judges subsequent packets by whether they match an existing tracked connection, not by re-running the full rule set. The practical payoff is that you express policy in terms of who may initiate, and the return path is handled for you.
On a stateless device you have to encode the reply direction explicitly. Allowing a host to reach an external web server means one rule for the outbound request and a second rule permitting inbound packets from port 443 with the ACK flag set — and that second rule is broader and more error-prone than a stateful firewall's automatic return handling. Most real outages on stateless ACLs trace to a missing or too-wide return rule.
The Connection Table and conntrack
On Linux the connection table is conntrack, maintained by netfilter. Each tracked flow occupies an entry holding the tuple, the protocol state (for TCP: SYN_SENT, ESTABLISHED, TIME_WAIT), and a timeout. The table has a hard ceiling — nf_conntrack_max, often defaulting around 65,536 or 262,144 entries depending on RAM — and when it fills, the kernel logs nf_conntrack: table full, dropping packet and refuses new connections while existing ones keep working.
This is the failure mode a SYN flood or a connection-rate DDoS is built to trigger: not to saturate your bandwidth, but to exhaust the conntrack table so legitimate flows can't get an entry. Sizing nf_conntrack_max for your peak flow count, and tightening timeouts so dead half-open entries expire fast, is the defense.
# list live tracked connections and the table's current size conntrack -L # tcp 6 431999 ESTABLISHED src=10.0.1.7 dst=93.184.216.34 \ # sport=52344 dport=443 ... [ASSURED] mark=0 use=1 sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_max # net.netfilter.nf_conntrack_count = 18244 # net.netfilter.nf_conntrack_max = 262144
Default-Deny versus Default-Allow
The most consequential line in any firewall policy is the implicit one at the end: what happens to a packet that matched no rule. Default-deny drops anything not explicitly allowed; default-allow passes anything not explicitly denied. Default-deny is the only safe posture, because it fails closed — a service you forgot to open simply doesn't work, which you notice immediately, instead of a service you forgot to close staying silently exposed.
The rule applies to egress as much as ingress, and egress is where teams get lazy. A default-allow outbound policy is a ready-made exfiltration channel: a compromised host can ship data to any destination on any port because nothing said it couldn't. Locking egress to the specific destinations a workload legitimately needs turns "attacker has shell" into "attacker has shell and can't phone home," which is a materially better incident.
Stateless ACL matches each packet against ordered rules with no memory of prior packets — simple, fast, and trivial to scale to line rate, but you must write the return-traffic rule yourself. It lives on routers and at coarse network boundaries where throughput matters and flows are simple. Choose it where you need raw speed and the policy is "this CIDR may reach that CIDR."
Stateful firewall tracks connections so return traffic is allowed automatically and policy is expressed in terms of who initiates — far less error-prone, at the cost of a finite connection table that can be exhausted. It lives on hosts, edge appliances, and cloud security groups. Choose it for anything facing real users, where bidirectional flows and least-privilege are the point.
- Leaving egress default-allow. A compromised host can then exfiltrate data to any IP on any port, turning a contained breach into a data-loss incident; lock outbound to the destinations the workload actually needs.
- Forgetting the return rule on a stateless ACL. The outbound request leaves but every reply is dropped, so connections hang and time out — the classic symptom of a stateless device configured as if it tracked state.
- Sizing nf_conntrack_max for average load, not peak. Under a connection-flood DDoS the table fills, the kernel logs "table full, dropping packet," and new legitimate flows are refused while bandwidth still looks fine.
- Using reject on internet-facing rules where drop is wanted. Reject returns an ICMP unreachable that confirms the host exists and maps your topology for a scanner; drop makes the same scan slow and uninformative.
- Opening a port with a source of 0.0.0.0/0 "just for now." A management port like 22 or 3389 exposed to the whole internet is found by scanners within minutes; scope every allow to a real source range.
- Default-deny both directions and add explicit allows. Ingress and egress should both fail closed, so a forgotten rule breaks a feature loudly instead of leaving an exposure you never notice.
- Prefer a stateful firewall for anything user-facing and let conntrack handle return traffic, reserving stateless ACLs for high-throughput boundaries where the policy is purely CIDR-to-CIDR.
- Monitor nf_conntrack_count against nf_conntrack_max and alert at 80%, so you raise the ceiling or tighten timeouts before a flood pushes the table to "full" and drops live users.
- Drop, don't reject, on internet-facing rules to avoid handing scanners a confirmation that a host exists; reserve reject for internal segments where fast failure helps legitimate clients more than it helps an attacker.
- Scope every allow rule to the narrowest source range that works — a specific CIDR or security group, never 0.0.0.0/0 for management ports — so a single broad rule can't undo the rest of the policy.
Knowledge Check
You allow inbound HTTPS on a host firewall and the replies flow without a second rule. Why?
- It is stateful and tracks the connection, so return packets match the existing flow
- Its egress policy defaults to allow, so all outbound replies are permitted anyway
- It is a stateless filter, and stateless filters always permit the return traffic for you by default
- Port 443 is exempt from filtering once any rule references it
A connection-rate DDoS fills your conntrack table though bandwidth is barely used. What breaks first?
- Established connections drop immediately as their entries are evicted
- New connections are refused because no free table entry remains
- The uplink saturates and all traffic queues behind the flood
- The rule set silently reorders and starts matching wrong packets
Why is default-deny the safer posture than default-allow for a firewall policy?
- It requires fewer rules to express the same policy
- It evaluates packets faster than default-allow does
- It fails closed, so a forgotten rule breaks a feature rather than leaving an exposure
- It removes the need for any tracked connection table on the firewall entirely, even a stateful one
You got correct