Back to Blog
Infrastructure8 July 2025Michael Hettwer5 min read

nftables instead of iptables: the migration in practice

How we move aging iptables baselines to nftables: rule files, sets and counters, the compatibility layer, and rollback thinking for a safe cutover.

Anyone inheriting an older server fleet runs into the same situation: the firewall lives in a script full of iptables lines, somewhere between packet filtering, NAT tricks and comments from different decades. Modernizing to nftables is not a fashion decision; it pays off in readability, in performance, and in staying aligned with the distribution, which edges the old world further aside with every release. This post describes the transition the way we run it on real servers: in steps with a way back.

Inventory first, rule file second

The first nft command deserves preparation on paper. A dump of the running rules, mirrored against the script, with a question per rule: what does it protect, what depends on it, would renaming an interface break it? The answer is rarely elegant, but it is the raw material for the new structure. Our target shape is always the same: a rule file that starts with flush ruleset, defines tables per family, opens base chains with clear hook points and priorities, and organizes the rest in named chains jumped to from the base chains. Rules that turn out to be oddities get discussed with their owner instead of migrating silently.

Sets and counters instead of line lists

The real gain of nftables shows in the patterns iptables made painful. Port collections and address ranges become sets that can be filled from the outside, elements with timeouts included. Blacklists that used to explode into loops with hundreds of individual lines become one set whose content a dynamic process feeds; the rule count itself stays small and constant. We place counters deliberately at the decision points: ACCEPT and DROP with counters shows within a week which rules actually see traffic. That is the best test against dead rules, which otherwise only announce themselves by missing when needed.

The compatibility layer and its limits

For machines whose iptables rules cannot migrate immediately, a compatibility layer exists: the classic command runs against an nftables-backed implementation, and iptables syntax lands transparently in nftables tables. It is a workable ramp, particularly where third-party software insists on the syntax. Two limits deserve clarity on every use: first, a second space of truth appears once native nftables rules sit beside it, and debugging blind between the two is miserable. Second, the layer hides the benefits: sets, counters and atomic rule replacement only pay off once rules are native. We use the ramp for the transition month and treat the native rule file as the destination.

A safe cutover, and what can go wrong

The change itself is routine when rollback is designed in. Before every switch, the current state is saved, not as a screenshot but as a file that reloads with one command. The new set first enters test mode: nftables can load rules with a forced expiry, so a cutover that locks you out reverses itself automatically. That makes it reasonable to switch from a terminal sitting on the server, with a real safety net. Observation follows: connection counters, log lines from the new chains, and feedback from the applications. The classic mistakes are all familiar: a forgotten established-connection rule that kills the SSH session, missing loopback acceptance that wedges internal resolution, and an ordering where DROP comes before the ACCEPT meant to carry the traffic.

The firewall baseline is always coordinated with the SSH access concept described in our SSH hardening post: rules that cut the administrative access are not progress. The update topic touches this too, because rule files move with the package base; our piece on critical security updates frames the tempo. For the full network and firewall scope, our network and firewall page is the right starting point.

What we set by default

Rules in version control

The real migration does not end with the first stable rule set; it ends with its custody. An nftables file is text, and text belongs under version control, with one comment line per change stating who adjusted what, when, and why. The benefit shows up in the incident: an event can be evaluated against the history, and the question "what changed about our protection?" has a datable answer. For the same reason we keep the rule file as the single source of truth; anything else that writes rules may report at most, never define.

The habit that comes with it: read the diff view before every push, rule by rule. Two minutes that prevent the most expensive failure in this category, the accidentally widened permission that surfaces months later.

On the servers we manage, every migration ends with the same result: a versioned rule file that defines the complete situation from flush ruleset upward, sets for everything fed by data, counters at the hot spots, and the certainty that a reboot restores exactly the state a script run would produce. From there, the next change is a commit, not an ad hoc command over SSH. That state, not the particular software choice, is the real goal of the migration. If you want your firewall baseline brought onto this footing, talk to us; we handle migrations and reviews within our network services.

Sources

  • nftables project wiki, https://wiki.nftables.org/wiki-nftables/index.php/Main_Page (accessed 2026-11-04)
  • manpages.debian.org, nft(8), https://manpages.debian.org/bookworm/nftables/nft.8.en.html (accessed 2026-11-04)
  • Debian Wiki, nftables, https://wiki.debian.org/nftables (accessed 2026-11-04)