Fixing pf State Table Exhaustion on FreeBSD
New connections start silently failing on a busy firewall, and pfctl reports the state table is full. Here's how to confirm it and size the table correctly for real traffic levels.
A busy pf firewall that starts silently dropping new connections, with existing connections unaffected, is a classic symptom of state table exhaustion — pf tracking the maximum number of connections it’s configured for and refusing new ones past that limit.
Step 1: confirm the state table is actually full
pfctl -si
Check the state limit line against current entries — a current count sitting at or near the configured limit, combined with new-connection failures, confirms this specific cause.
Step 2: check the current configured limit
pfctl -sm
The default states limit (commonly 10,000 in older configurations) is far too low for a busy server or gateway handling many concurrent connections.
Step 3: raise the state limit
# /etc/pf.conf
set limit states 100000
pfctl -f /etc/pf.conf
Reloading the ruleset applies the new limit without dropping existing connections.
Step 4: check for a specific rule causing excessive state creation
pfctl -ss | wc -l
pfctl -ss | awk '{print $2}' | cut -d: -f1 | sort | uniq -c | sort -rn | head
If one source IP or one rule accounts for a disproportionate share of states, that’s worth investigating separately — it may indicate a misbehaving client, a port scan, or a rule that’s broader than intended and matching more traffic than expected.
Step 5: tune state timeouts for faster cleanup of stale entries
# /etc/pf.conf
set timeout { tcp.established 3600, tcp.closing 60 }
Shorter timeouts for connections in transitional states free up table entries faster, reducing how large a limit is actually needed for the same real traffic level.
Step 6: monitor state table usage going forward
pfctl -si | grep -A2 "State Table"
Checking this periodically (or feeding it into monitoring) catches a table approaching its limit before it starts silently dropping connections again.
Why silent failure is what makes this particularly disruptive
Unlike many resource-exhaustion problems, a full pf state table doesn’t produce an obvious error anywhere — new connections simply don’t establish, which can look like a network, DNS, or application problem long before anyone thinks to check pfctl -si. Making state-table monitoring a routine check on any pf-based firewall handling real production traffic avoids exactly that confusing failure mode.