I have written iptables rules for the better part of fifteen years, and muscle memory is a powerful thing. nftables has been the default backend on Debian and friends for ages now, with iptables quietly shimmed over nft, and I kept telling myself the translation layer was fine. It is fine. It is also a good excuse to never learn the actual tool.
This weekend I bit the bullet and rewrote the homelab firewall natively. The thing that finally sold me is sets. In iptables you end up with a wall of nearly identical rules, one per port, one per address. In nftables you write it once:
table inet filter {
set trusted {
type ipv4_addr
elements = { 10.0.0.0/24, 192.168.10.5 }
}
chain input {
type filter hook input priority 0; policy drop;
ct state established,related accept
iif "lo" accept
ip saddr @trusted tcp dport { 22, 443, 8006 } accept
}
}
That one chain replaces a depressing number of lines I used to maintain by hand, and the inet family means I am not duplicating everything for IPv6. Atomic ruleset reloads are the other quiet win: nft -f swaps the whole thing in or fails cleanly, so you do not get that horrible window where half your rules have applied and you have locked yourself out.
It took an evening and a couple of typos to get comfortable. If you have been leaning on the compatibility shim like I was, just write the native rules. You will not go back.