From iptables to nftables: Enterprise Firewall on OpenWrt
OpenWrt 22.03 switched its firewall backend from iptables (fw3) to nftables (fw4). For hobbyists running a single router, the change was mostly invisible — UCI still works, LuCI still generates rules. But for us, managing 900+ SD-WAN appliances with custom enterprise rulesets layered on top of fw4, the migration was a significant undertaking.
This post covers why we migrated, how iptables and nftables differ in practice, the fw4 zone-based model, a sample enterprise ruleset, performance differences, and the pitfalls we hit along the way.
Why Migrate
Section titled “Why Migrate”The short answer: iptables is deprecated. The kernel iptables interface is a compatibility layer on top of nftables in modern kernels. New features land in nftables only. Upstream OpenWrt has moved on, and fighting the platform is a losing strategy.
The longer answer: nftables is genuinely better for enterprise use cases.
- Atomic rule replacement. In iptables, updating a ruleset means flushing chains and re-adding rules one by one. During the flush, there is a window where traffic is not filtered. nftables replaces entire rulesets atomically.
- Single framework. iptables, ip6tables, arptables, and ebtables are separate tools with separate syntax. nftables handles all of them in one tool with one syntax.
- Sets and maps. nftables has native support for IP sets, verdict maps, and concatenated matches. In iptables, you need ipset as a separate module. In nftables, sets are first-class citizens.
- Better performance with large rulesets. nftables uses a virtual machine internally, compiling rules into bytecode. For rulesets with hundreds of rules (common in enterprise), this is measurably faster than iptables’ linear chain traversal.
Syntax Comparison
Section titled “Syntax Comparison”For engineers used to iptables, the nftables syntax takes some adjustment. Here are common enterprise rules in both formats.
Block incoming on WAN, allow established
Section titled “Block incoming on WAN, allow established”iptables:
iptables -A INPUT -i eth0 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPTiptables -A INPUT -i eth0 -j DROPnftables:
chain input { type filter hook input priority 0; policy drop; iifname "eth0" ct state established,related accept}Port forwarding (DNAT)
Section titled “Port forwarding (DNAT)”iptables:
iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 8443 -j DNAT --to-destination 10.0.1.50:443iptables -A FORWARD -i eth0 -p tcp -d 10.0.1.50 --dport 443 -j ACCEPTnftables:
chain prerouting { type nat hook prerouting priority -100; iifname "eth0" tcp dport 8443 dnat to 10.0.1.50:443}
chain forward { type filter hook forward priority 0; policy drop; iifname "eth0" ip daddr 10.0.1.50 tcp dport 443 accept}IP set matching
Section titled “IP set matching”iptables + ipset:
ipset create blocked_ips hash:ipipset add blocked_ips 203.0.113.5ipset add blocked_ips 198.51.100.0/24iptables -A FORWARD -m set --match-set blocked_ips src -j DROPnftables:
set blocked_ips { type ipv4_addr flags interval elements = { 203.0.113.5, 198.51.100.0/24 }}
chain forward { type filter hook forward priority 0; policy drop; ip saddr @blocked_ips drop}The nftables version does not need a separate tool. Sets are defined inline, support intervals natively, and are updated atomically.
Zone-Based Firewall on OpenWrt (fw4)
Section titled “Zone-Based Firewall on OpenWrt (fw4)”OpenWrt’s fw4 is a zone-based firewall that generates nftables rules from UCI configuration. Understanding fw4 is essential because your custom rules must coexist with its generated ruleset.
fw4 creates the following nftables tables:
inet fw4— the main table containing all firewall chains.- Chains are named by convention:
input_<zone>,output_<zone>,forward_<zone>_<zone>, etc.
A zone definition in UCI:
config zone option name 'lan' list network 'lan' option input 'ACCEPT' option output 'ACCEPT' option forward 'REJECT'
config zone option name 'wan' list network 'wan' 'wan6' option input 'DROP' option output 'ACCEPT' option forward 'DROP' option masq '1' option mtu_fix '1'fw4 translates this into nftables chains with jump targets. The key insight is that fw4 generates a chain forward with jumps to zone-specific forwarding chains. You can insert custom rules either through UCI (which regenerates the nftables rules) or by adding a custom nftables include file.
Custom Rules via Include
Section titled “Custom Rules via Include”fw4 supports including custom nftables rule files:
config include option path '/etc/nftables.d/enterprise.nft' option position 'chain-pre' option chain 'forward'This is where we inject enterprise-specific rules that go beyond what UCI zone config can express.
Sample Enterprise Ruleset
Section titled “Sample Enterprise Ruleset”Here is a simplified version of the enterprise ruleset we deploy to branch appliances. It handles WAN ingress hardening, LAN segmentation, rate limiting, and geo-blocking.
# Threat intelligence blocklist (updated daily via cron)set threat_ips { type ipv4_addr flags interval # Populated by /etc/cron.d/update-threat-list}
# Geo-block: countries we never expect traffic fromset geo_blocked { type ipv4_addr flags interval # Populated by /etc/cron.d/update-geo-list}
# Rate limit new connections from WANchain wan_ratelimit { ct state new meter wan_connrate { ip saddr limit rate over 50/second } drop}
chain enterprise_forward { # Drop known threats ip saddr @threat_ips counter drop ip daddr @threat_ips counter drop
# Geo-blocking ip saddr @geo_blocked counter drop
# LAN segmentation: IoT cannot reach corporate iifname "br-iot" oifname "br-corp" counter reject
# Corporate to server VLAN: only specific ports iifname "br-corp" oifname "br-servers" tcp dport { 22, 80, 443, 3306, 5432 } accept iifname "br-corp" oifname "br-servers" counter reject
# Guest: internet only, no internal access iifname "br-guest" oifname "br-*" counter reject iifname "br-guest" oifname "eth0" accept}
chain enterprise_input { # Rate limit on WAN interface iifname "eth0" jump wan_ratelimit
# Allow management access only from VPN iifname "wg0" tcp dport { 22, 443 } accept iifname "eth0" tcp dport { 22, 443 } counter drop
# SNMP from monitoring server only ip saddr 10.255.0.10 udp dport 161 accept}Performance Comparison
Section titled “Performance Comparison”We benchmarked iptables (fw3) vs nftables (fw4) on identical hardware (Hopbox branch appliance) with a production-representative ruleset of approximately 200 rules across all chains.
| Metric | iptables (fw3) | nftables (fw4) | Difference |
|---|---|---|---|
| Rule load time | ~800ms | ~50ms | 16x faster |
| Forwarding throughput (1K rules) | ~920 Mbps | ~960 Mbps | ~4% improvement |
| New connection rate | ~45K conn/s | ~52K conn/s | ~15% improvement |
| Atomic ruleset swap | Not supported | <5ms | N/A |
| Memory usage (1K rules) | ~12 MB | ~4 MB | 3x less |
The biggest practical difference is atomic rule replacement. With iptables, a ruleset update on a busy appliance would occasionally drop packets during the flush-and-reload. With nftables, the new ruleset is compiled and swapped in a single kernel transaction. Zero packet loss during updates.
Migration Strategy Across 900+ Devices
Section titled “Migration Strategy Across 900+ Devices”We did not migrate all 900+ devices at once. The rollout was phased over three months.
Phase 1: Lab Validation (Week 1-2)
Section titled “Phase 1: Lab Validation (Week 1-2)”Converted the enterprise ruleset from iptables syntax to nftables. Ran the new rules in a lab environment with traffic replay from production captures. Validated every rule by checking counters against expected traffic patterns.
Phase 2: Canary Deployment (Week 3-4)
Section titled “Phase 2: Canary Deployment (Week 3-4)”Deployed to 10 branch offices across different customer profiles (retail, office, warehouse). Monitored for two weeks, comparing firewall logs between iptables and nftables devices to ensure identical allow/deny decisions.
Phase 3: Rolling Update (Week 5-12)
Section titled “Phase 3: Rolling Update (Week 5-12)”Pushed the firmware update (OpenWrt with fw4) to devices in batches of 50-100 per week. Each batch was monitored for 48 hours before proceeding. Rollback firmware was staged on every device in case of issues.
Phase 4: Cleanup (Week 13+)
Section titled “Phase 4: Cleanup (Week 13+)”Removed all iptables compatibility packages from the firmware image. Reclaimed flash storage and reduced the attack surface. Updated all documentation and runbooks.
Common Pitfalls
Section titled “Common Pitfalls”These are the issues we encountered during migration. Save yourself the debugging time.
1. iptables-nft is not nftables
Section titled “1. iptables-nft is not nftables”OpenWrt can run iptables-nft, which accepts iptables syntax but translates it to nftables in the kernel. This is a compatibility shim, not a migration. The generated nftables rules are ugly, hard to debug, and do not use nftables features like sets and maps. Do a proper rewrite.
2. Chain priority matters
Section titled “2. Chain priority matters”In iptables, chain ordering within a table is implicit. In nftables, chains have explicit priority values. If your custom chain has the wrong priority, it might run before or after fw4’s chains, producing unexpected behavior.
# fw4's forward chain uses priority 0# Your pre-filter chain should use a lower (earlier) prioritychain enterprise_prefilter { type filter hook forward priority -10; # These rules run BEFORE fw4's forward chain}3. Conntrack zones
Section titled “3. Conntrack zones”If you use multiple WAN interfaces (common in SD-WAN), conntrack zone assignment must be configured correctly in nftables. fw4 handles this for zones defined in UCI, but custom rules that manipulate conntrack may need explicit zone assignment.
4. Logging syntax
Section titled “4. Logging syntax”iptables: -j LOG --log-prefix "DROP: "
nftables: log prefix "DROP: " drop
The logging in nftables is an action, not a target. You can combine it with other actions on the same rule. But the log format is different — if your log parsing pipeline expects iptables-format log lines, update your parsers.
5. Rule counters are off by default
Section titled “5. Rule counters are off by default”In iptables, every rule has a packet/byte counter. In nftables, counters are optional and must be explicitly added with the counter keyword. If you rely on per-rule counters for monitoring, add them explicitly or you will get no data.
Conclusion
Section titled “Conclusion”The migration from iptables to nftables is not optional for OpenWrt-based products — iptables is deprecated and fw4 is the present and future. The good news is that nftables is genuinely better: atomic updates, native sets, cleaner syntax, and better performance with large rulesets. The migration requires rewriting custom rules (do not rely on the iptables-nft shim), phased rollout (do not push to 900 devices on day one), and careful attention to chain priorities and fw4 integration. The result is a firewall framework that can handle enterprise-grade rulesets without the iptables-era compromises.