I finally moved a box off iptables and onto nftables this week, mostly to stop putting it off. The kernel side has been in mainline since 3.13, the nft tooling has settled down enough to trust, and on a recent enough distro it's all just there waiting. The thing that pushed me over the edge was a ruleset that had grown four near-identical chains, one each for v4 and v6 in two directions, and I was tired of editing everything twice.
That last part is the headline win. With nftables you get one ruleset that handles both address families in a single inet table, instead of the iptables/ip6tables split where every rule has a twin you have to keep in sync. Sets are first-class too, so a list of allowed ports stops being twenty individual rules and becomes one:
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
ct state established,related accept
iif lo accept
tcp dport { 22, 80, 443 } accept
ip protocol icmp accept
ip6 nexthdr icmpv6 accept
}
}
That's the whole inbound policy, both families, in a dozen readable lines. The old version was the better part of a screen and I never enjoyed reading it.
It is not all finished and shiny. The iptables-translate helper gets you most of the way but not all of it, so expect to hand-fix the awkward rules. Tooling and examples online still overwhelmingly assume iptables, so when something doesn't work you're more on your own than you'd like. And if you run anything like fail2ban or a container runtime that pokes at iptables directly, you'll want to check it's happy before you commit, because the two don't always coexist gracefully on the same box. For a single, hand-managed firewall, though, the ruleset is genuinely nicer to live with, and that one-table-for-both-families thing alone has earned its place.