I switched to nftables the day I had to read an old iptables script and gave up. nftables reads like what it does: one syntax, no -A vs -I guessing, and a whole firewall that fits on a screen. If you have never looked at it, here is the 10-minute version.

The three things you need

  • A table groups rules for one family. inet covers IPv4 and IPv6 at once, which alone is a reason to switch.
  • A chain is a named list of rules hooked into the packet path: input, forward, output.
  • Each rule matches and jumps to a verdict: accept, drop, reject.

A firewall that fits in a file

table inet filter {
    chain input {
        type filter hook input priority filter; policy drop;
        ct state established,related accept
        iif lo accept
        ct state invalid drop
        tcp dport 22 accept      # ssh
        tcp dport 80,443 accept  # web
    }
    chain forward { type filter hook forward priority filter; policy drop; }
    chain output { type filter hook output priority filter; policy accept; }
}

Read it like prose: drop everything incoming, allow replies to my own connections, allow loopback, allow SSH and web. The policy drop is the whole philosophy — you list what you want, everything else dies.

Put it in /etc/nftables.conf, run nft -f /etc/nftables.conf, and please do it from a tmux session with a second window open. Remote firewalls punish mistakes instantly.

drop, reject, and why the difference matters

drop is silent: the client waits and times out. reject answers with a TCP RST, so the client fails immediately. For a closed port, reject is polite; for someone port-scanning you, drop is quieter:

nft add rule inet filter input tcp dport 23 reject with tcp reset

Port lists without the copy-paste

nft add set inet filter allowed_ports { type inet_service; }
nft add element inet filter allowed_ports { 22, 80, 443, 51820 }
nft add rule inet filter input tcp dport @allowed_ports accept

One rule, one set, and adding a port later is a single line. My SSH rate limit rides on the same idea:

nft add rule inet filter input tcp dport 22 ct state new limit rate 6/minute accept

Six fresh connections a minute is plenty for a human and useless for a brute-forcer.

When in doubt, nft list ruleset prints your live state as valid nftables syntax — you can paste it straight into the config file. That alone beats iptables-save, which always looked like it was written by a machine that hated you.