OpenBSD: Optimizing Packet Filter (PF) Rules
When OpenBSD replaced IPFilter with PF in 3.0, the world changed. PF's syntax is cleaner, but its power lies in how it handles large rulesets. If you have 500 lines in your pf.conf, you're likely killing your throughput.
Use Tables, Not Lists
The biggest mistake is using long lists of IP addresses in rules. Every time a packet arrives, PF has to iterate through that list.
The Wrong Way:
block in quick on ext_if from 1.2.3.4 to any
block in quick on ext_if from 5.6.7.8 to any
block in quick on ext_if from 9.10.11.12 to any
The Right Way (Tables): Tables use Radix trees, allowing for O(log n) lookups. Even with 100,000 IPs, the performance hit is negligible.
table <spammers> persist file "/etc/spammers"
block in quick on ext_if from <spammers> to any
State Modulation and Optimization
PF is stateful. Once a packet matches, subsequent packets in that flow skip the ruleset entirely. You can optimize how states are handled:
set optimization aggressive
set limit states 20000
# Keep state but optimize for high-traffic web servers
pass in on ext_if proto tcp from any to any port 80 \
flags S/SA modulate state
By using modulate state, PF provides high-quality initial sequence numbers for the connection, shielding older or poorly implemented TCP stacks from hijacking. PF isn't just a firewall; it's a security-hardened traffic shaper.
Aunimeda provides DevOps engineering and infrastructure services - CI/CD pipelines, containerization, cloud deployments, and monitoring setups.
Contact us to discuss your infrastructure needs. See also: DevOps Services, Custom Software Development