The risky part of replacing iptables is not learning where the braces go. It is discovering, too late, that the rules you translated were never the whole firewall.
An old host may have rules written by a boot script, a distribution service, a container engine, a VPN, and an administrator at the command line. Some commands may already use the nf_tables kernel backend through the compatibility layer while retaining familiar iptables syntax. A generated nftables file can therefore be syntactically correct and operationally wrong: it may duplicate another manager’s work, omit a rule that appears only at runtime, or be overwritten on the next service reload.
The safe migration has two deliberately separate goals. First, preserve behavior under one clearly named owner. Only after that cutover is observable and reversible should the team exploit native nftables features such as shared IPv4/IPv6 chains, sets, maps, and atomic ruleset updates. Translation is the bridge. Simplification is a later engineering project.[1][5]
The cover image shows Pablo Neira Ayuso at a 2016 workshop devoted to getting a practical grasp of nftables. The setting is apt: firewall migration is hands-on systems work, where the decisive evidence comes from the rules the kernel has loaded and the packets that actually traverse them.[7][8]
Freeze ownership before translating syntax
Begin with an ownership ledger, not a new configuration file. Record iptables -V and ip6tables -V so the inventory names the backend rather than assuming it. Capture iptables-save, ip6tables-save, and nft list ruleset; if a mixed or uncertain host exposes both variants, also capture the explicit iptables-legacy-save / ip6tables-legacy-save and iptables-nft-save / ip6tables-nft-save views. Include ipset, ebtables, and arptables state when those surfaces are in scope.[1][6] Then identify every process that can change the result. Search service units, network-up hooks, configuration-management roles, container or cluster networking, VPN lifecycle scripts, and hand-maintained recovery scripts. Record which component is authoritative after reboot, after an interface change, and after an application deploy.
This step prevents the most common category error: treating a backend as an owner. A host can run firewalld while the kernel stores the resulting policy in nftables. That does not mean a parallel hand-written nftables.service ruleset should manage the same hooks. Debian’s current guidance makes the layers unusually legible: nftables is its default firewalling framework, installed iptables commands use the nf_tables backend through iptables-nft, and administrators who do not want to own low-level scripts should consider the system-integrated firewalld wrapper. It also advises against mixing nftables with the older rule-management surfaces unless the operator understands the interaction.[6]
The same reasoning applies beyond those two services. If an orchestrator owns pod forwarding or a VPN owns a mark-and-routing chain, either keep that ownership and design a documented integration point, or move it deliberately. Do not silently copy its current runtime output into a static file and call the dependency removed.
Before any cutover, the ledger should answer four questions:
- Which program creates each table or chain?
- Which event reloads or mutates it?
- Which flows depend on it, including IPv6 and forwarding?
- Which single mechanism will restore access if the replacement fails?
If the answers are incomplete, the migration is not ready for syntax work.
Treat translation as a parity scaffold
The Netfilter project provides two useful lanes. iptables-translate and ip6tables-translate convert individual commands, while iptables-restore-translate and ip6tables-restore-translate convert saved rulesets into input that nft -f can read.[1] This is the right way to establish a first native draft because it preserves more ordering and policy detail than a manual rewrite made from memory.
It is not an automatic proof. The migration guide shows that an unsupported translation can appear as a commented line instead of active policy. Separately, the current nft manual warns that an xt statement indicates a rule created through the xtables compatibility interface; nftables itself cannot restore that statement.[1][2] Every translation comment, compatibility statement, extension, custom target, and warning is therefore a test obligation, not cleanup noise.
Keep the first draft intentionally boring. Preserve base-chain hooks, priorities, default policies, connection-tracking conditions, NAT direction, interface matches, logging limits, and rule order. Preserve separate IPv4 and IPv6 behavior even when the two files look repetitive. A migration that simultaneously translates, deduplicates, renames, and changes policy leaves no clean explanation when one flow breaks.
Also distinguish iptables-nft from iptables-legacy. The former presents xtables syntax while programming the nftables backend; the latter uses the older x_tables subsystem. Netfilter’s migration guide cautions against operating the legacy and nftables subsystems together because the result can be unexpected.[1] The compatibility lane can reduce application breakage during a transition, but it should be a named, temporary dependency. Otherwise two command languages continue to describe one kernel policy and operators never know which surface is canonical.
Make the file a transaction—and a rollback artifact
Native nftables rewards file-based operation. The command nft -c -f candidate.nft checks the file’s commands without applying them, while nft -f candidate.nft submits the command stream for application.[2] At the ruleset level, nftables can load a replacement atomically, so packets do not observe a half-built firewall while commands are being applied.[3]
That does not mean every file is automatically a replacement. A file containing only add commands can accumulate objects on top of the current state. The nftables operations guide demonstrates a full-namespace backup pattern: write flush ruleset, append the listed ruleset, and load the file atomically with nft -f.[3] Use that global flush only when this artifact has exclusive ownership of the namespace’s entire nftables policy; flush ruleset removes every nftables table and object, including another manager’s work. Where ownership is deliberately shared, make the transaction delete and recreate only the tables or families this artifact owns.
Use that property to create a controlled cutover:
- Validate the candidate with
nft -c -f candidate.nft. - Apply it first in a network namespace, disposable clone, or canary host that exercises the same roles.
- Keep a console or out-of-band management path that does not depend on the rule being changed.
- Schedule an automatic rollback unless a separate health check cancels it.
- Preserve the pre-cutover dumps, the native candidate, package versions, and the exact apply and restore commands in the change record. If the old policy includes legacy or
xtcompatibility state, roll it back through the service or frontend that created it rather than assuming nativenftcan restore it.[2]
Atomicity protects the transition between two rulesets. It cannot prove that the new ruleset expresses the intended policy. A perfectly atomic SSH lockout is still a lockout.
Test behavior, not textual resemblance
The parity suite should be written from flows, not chains. At minimum, test local ingress, local egress, routed forwarding, established connections, new connections, NAT in every used direction, loopback traffic, administrative access, DNS, and both IP families. Add service-specific cases for marks, policy routing, tunnels, bridges, or container networking. For every allowed flow, include a nearby denied flow so a broad accidental accept does not masquerade as success.
Counters help, but nftables does not attach one implicitly to every rule; counters are explicit stateful objects or statements.[5] Place them on the translated decision points during the observation window. Compare which policy outcomes increment under the same test traffic, while remembering that a byte-for-byte counter match is not realistic once background traffic is present.
For ambiguous paths, nftables has a stronger instrument. A narrowly scoped rule can set meta nftrace for selected packets, and nft monitor trace then reports their path with a trace identifier.[7] Scope tracing to a test address, port, or interface. Turning it on indiscriminately creates a flood of evidence that is harder, not easier, to interpret.
The promotion gate is behavioral: the canary survives a reboot; the intended owner reconstructs the same policy; critical allowed and denied paths behave correctly; counters and traces explain the result; and rollback has been rehearsed. “The translated file loaded” satisfies only one of those conditions.
Simplify only after parity holds
Once the native ruleset has run cleanly, nftables can start paying back the migration cost. An inet family table lets IPv4 and IPv6 packets traverse the same base chains, while family-specific expressions can still distinguish the protocols when necessary.[4] That makes genuinely shared policy—established traffic, dual-stack service ports, interface boundaries—share one implementation without pretending that every v4 and v6 rule is identical.
Sets replace repeated near-identical rules with typed collections. Maps associate keys with values, and verdict maps can associate a key directly with a decision or chain. The Netfilter documentation identifies this generic set-and-map infrastructure, along with intervals and concatenations, as one of the main differences from iptables.[5] The benefit is not merely fewer lines. Policy objects such as “office networks,” “published services,” and “destination translations” become named data that can be reviewed separately from control flow.
Make those improvements one semantic change at a time. Consolidate the dual-stack filter path, test it, and stop. Replace one repeated address block with a set, test it, and stop. Move one NAT selection into a map, test it, and stop. Retain comments that explain ownership and intent; short syntax without a maintenance story is just compressed ambiguity.
Base-chain hooks and priorities deserve special restraint. nftables lets operators choose where a chain enters packet processing and at what priority, which is powerful when several subsystems coexist.[2] It also means an apparently harmless reorganization can change whether a rule runs before connection tracking, NAT, or another manager’s chain. Treat a priority change as a packet-path change, not formatting.
Know when direct nftables is the wrong destination
Direct nftables is a good fit when a team owns a complex host or network firewall, needs explicit hooks and priorities, benefits from sets or maps, and can test packet paths as code. It is a poor fit when nobody owns Linux networking after the migration, when a higher-level manager already expresses the required policy safely, or when an appliance or cluster platform promises to control the underlying ruleset.
Small teams should be especially honest about the operational bargain. Native syntax can make a large ruleset more coherent, but it does not supply review, staging, console access, monitoring, or on-call knowledge. If a system-integrated firewalld wrapper covers the real requirement, keeping that higher-level owner may be the more maintainable choice.[6] If a platform rewrites host networking continuously, the right migration may be to update the platform’s supported configuration rather than bypass it.
The failure signal is simple: after cutover, operators still reach for both iptables and nft, multiple services can recreate overlapping chains, or nobody can explain which file will win at reboot. In that state, the syntax has changed but the control problem has not.
An iptables-to-nftables migration is complete when one owner can reconstruct, observe, test, and roll back the firewall. The shorter ruleset can come later. Behavior parity is the first release.
Sources
- Netfilter Project, “Moving from iptables to nftables” — command and whole-ruleset translation, the
iptables-nftcompatibility backend, unsupported-expression warnings, and the danger of mixing legacy and nftables subsystems. - Netfilter Project,
nft(8)manual — file input, check-only validation, ruleset inspection, compatibility statements, expressions, maps, and verdict behavior. - Netfilter Project, “Operations at ruleset level” — full-ruleset listing, explicit flushing, backup construction, and atomic file-based restore.
- Netfilter Project, “Nftables families” —
ip,ip6,inet,arp,bridge, andnetdevfamily boundaries, including shared dual-stack traversal ininettables. - Netfilter Project, “Main differences with iptables” — explicit counters, dual-stack administration, dynamic updates, sets, maps, intervals, and concatenations.
- Debian Project, “nftables” — independent downstream guidance on nftables as the default framework, the
iptables-nftcompatibility layer,firewalldas a higher-level owner, boot configuration, and avoiding mixed rule-management surfaces. - Netfilter Project, “Ruleset debug/tracing” — scoped
nftraceactivation and packet-path inspection withnft monitor trace. - NLUUG, “Pablo Neira Ayuso — Getting a Practical Grasp of nftables” (May 26, 2016), Wikimedia Commons — provenance and event context for the archival workshop image used as the article cover.