iptables is a shim now. The shim has edge cases. Rewrite your rules.
Published by RodHat

nftables landed in the kernel in 2014. Twelve years ago. I know this because I was already irritated about the iptables-to-nftables transition at the time, and I have been irritated about it for every year since, because the transition never actually finished.
It has now finished. Mostly. In the way things finish on Linux — with an asterisk.
Debian 13 “Trixie” shipped with iptables pointing at iptables-nft. That
means when you run iptables -A INPUT -p tcp --dport 22 -j ACCEPT, you are
not writing a rule to the ip_tables kernel module. You are writing a rule
that the iptables-nft frontend translates into an nftables ruleset and loads
via the nf_tables subsystem. The xt_* kernel modules still exist — you can
install iptables-legacy and get the old behavior — but nobody installs that
package on a new machine. Ubuntu completed the same flip two releases back.
RHEL-alikes finished it even earlier. Alpine defaults to nftables rules directly
because musl-based people were never sufficiently sentimental about iptables to
delay the transition.
This is correct. This is how it should have gone. I would have preferred it happened a decade ago, but here we are.
Why nftables is better, for the record
The iptables data model is one table per address family: iptables for IPv4,
ip6tables for IPv6, arptables for ARP, ebtables for bridging. You
maintain four separate rule sets, with four separate tools, and you pray they
stay consistent. If you’ve ever managed a dual-stack box with both iptables
and ip6tables rules and wondered how they got out of sync, that’s your answer.
nftables uses a single framework. One table can have chains that match on any address family. You write one rule that handles both IPv4 and IPv6 traffic, or you write separate rules if you need to distinguish them. The choice is yours, not baked into the tool’s architecture.
The bigger win is atomicity. iptables appends rules one at a time. If your
firewall-restore script fails halfway through, you have a half-loaded ruleset
and you find out when packets start behaving strangely. nft loads an entire
ruleset file atomically — it validates the whole thing and either applies all of
it or none of it. Your firewall goes from one valid state to the next valid
state. There is no half-state.
Sets and verdict maps let you write rules that look up membership in O(1) rather than traversing a linear rule chain. A blocklist with 50,000 CIDRs in iptables is 50,000 rules the kernel evaluates in sequence for every packet. In nftables that same blocklist is a set — one lookup, hash table or radix tree depending on what you’re storing. The performance difference at scale is not subtle.
The part where the shim bites you
iptables-nft is a compatibility layer, not a transparent one. The translation
between iptables semantics and nftables internals has edges, and you will find
them in the specific ways that make production incidents memorable.
Connection tracking rules that reference specific --ctstate values translate
correctly for simple cases. When you get into complex --ctstate RELATED,ESTABLISHED
combinations alongside module-specific matches like --dport with multiport, the
translated nftables ruleset can produce subtly different match ordering than the
original iptables rules. Not wrong, exactly. Different.
More importantly: if you’re using both the iptables-nft shim and direct nft
rules on the same machine — because some application writes iptables rules and you
also manage a native nftables ruleset — they’re now sharing the same nf_tables
subsystem with separate table namespaces. They coexist without conflict, but
nft list ruleset shows you both, and iptables -L shows you only the shim’s
view. Debugging which rule is matching a packet when you have both in play requires
nft monitor trace, not iptables -L -n -v. Anyone expecting the iptables
tooling to show them the full picture is going to spend an afternoon confused.
Docker and libvirt still write iptables rules. They use the shim now, which means
their rules end up in nf_tables. If you also manage nf_tables rules by hand, you
need to know which table namespaces they’re using (DOCKER, LIBVIRT_FWD,
LIBVIRT_INP, LIBVIRT_OUT, LIBVIRT_FWI) so you don’t accidentally flush them.
nft flush ruleset in a HOWTO is the new rm -rf /. Do not do it on a machine
running containers.
What you should actually do
If you manage Linux firewalls and you’re still using iptables syntax, you have two reasonable options:
-
Keep using
iptablescommands through the shim and accept that the shim is mostly fine for standard cases. Understand that you’re writing to nf_tables regardless, and learnnft monitorfor debugging. -
Migrate your ruleset to native nftables syntax. The
iptables-translatetool converts individual rules. For a complete ruleset,iptables-restore-translatehandles the whole thing at once. Read the output before you apply it — translation is a starting point, not the answer. Native nft rules will always be cleaner than translated ones.
Option 2 is the right one. The transition is done. The shim is not going to get
more stable; the ecosystem is moving toward native nftables and the tools, the
documentation, and the examples all reflect that now. Writing iptables rules in
2026 is like configuring /etc/rc.conf to load modules the old way on a system
that’s already moved to a new init — technically functional, increasingly a
maintenance liability.
The xt_* modules are still in the kernel, but there are active conversations
among the netfilter maintainers about the deprecation path. FreeBSD users have
had pf(4) for twenty-three years and never had this problem. I’m not going to
make that point again. I’ve made it enough times.
See also: pf tables and self-updating firewall rules for the right way to do this if you have a choice of operating system, and policy routing with ip rule for the netfilter-adjacent problem of routing asymmetry that trips people up once the firewall rules are finally working.
Sources
- Debian 13 (Trixie) Release Notes — Notable Changes — Debian Project
- Moving from iptables to nftables — netfilter.org / nftables wiki