nftables is the firewall in every current Debian and most other Linux distributions. These are the commands and rule patterns from the nftables and iptables on Debian lesson, where each one was run on Debian 13 (nftables 1.1.3) with its output shown. The same ruleset style protects the servers in the OpenVPN, WireGuard and PowerDNS lessons.
Everyday commands
| Task | Command |
| Show everything | nft list ruleset |
| List the tables | nft list tables |
| One chain, with rule handles | nft -a list chain inet filter forward |
| Check a file without loading it | nft -c -f /etc/nftables.conf |
| Load a file (atomic: all or nothing) | nft -f /etc/nftables.conf |
| Check, then reload the saved rules | nft -c -f /etc/nftables.conf && systemctl reload nftables |
| Load the rules at every boot | systemctl enable --now nftables |
| Insert a rule at the top of a chain | nft insert rule inet filter forward … accept |
| Delete a rule by its handle | nft delete rule inet filter forward handle 9 |
| Add or remove a set element | nft add element inet filter admins { 10.10.0.80 } / nft delete element … |
| Show a set | nft list set inet filter admins |
| Follow packets through the rules | nft monitor trace (after a rule sets meta nftrace set 1) |
| See tracked connections | conntrack -L -p tcp --dport 80 |
| Turn on forwarding (routers) | net.ipv4.ip_forward = 1 in /etc/sysctl.d/, then sysctl --system |
A router ruleset, piece by piece
table inet filter {
set admins {
type ipv4_addr
elements = { 10.10.0.20 }
}
chain input {
type filter hook input priority filter; policy drop;
ct state established,related accept
ct state invalid drop
iif lo accept
icmp type { echo-request, destination-unreachable, time-exceeded } accept
iifname "eth1" ip saddr @admins tcp dport 22 accept comment "SSH from admin PCs"
limit rate 6/minute burst 5 packets log prefix "nft input drop: "
counter comment "dropped by the policy"
}
chain forward {
type filter hook forward priority filter; policy drop;
ct state established,related accept
ct state invalid drop
iifname "eth1" oifname "eth0" accept comment "the office to the internet"
iifname "eth0" oifname "eth1" ct status dnat accept comment "port forwards"
counter comment "dropped by the policy"
}
}
table ip nat {
chain prerouting {
type nat hook prerouting priority dstnat;
iifname "eth0" tcp dport 8080 dnat to 10.10.0.80:80
}
chain postrouting {
type nat hook postrouting priority srcnat;
oifname "eth0" masquerade
}
}
policy drop on input and forward: anything not accepted is dropped, so a forgotten rule fails safe.
ct state established,related accept first: replies to allowed traffic are let back in, so the rules only need to describe new connections.
- input is traffic to the firewall itself; forward is traffic through it. A router needs both, plus IP forwarding.
ct status dnat accepts exactly the connections a port forward created, without repeating the addresses.
- A rate-limited
log before the final counter shows what's being dropped (in journalctl -k) without flooding the log.
Sets, maps and rate limits
| Pattern | Example |
| Named set of addresses | ip saddr @admins |
| Anonymous set | tcp dport { 80, 443 } |
| Port forwards in one map | dnat ip to tcp dport map @forwards, with 8080 : 10.10.0.80 . 80 |
| Per-source rate limit (dynamic set) | update @web_flood { ip saddr limit rate over 10/minute burst 5 packets } drop |
iptables on a modern Debian
On Debian 13, iptables is iptables-nft: it writes nftables rules into tables called ip filter, ip nat and so on. Both sets of rules then run on the same hooks, and a packet must pass both: an iptables DROP blocks traffic nftables accepted, and an iptables ACCEPT can't override an nftables drop.
| Task | Command |
| Which iptables is this? | iptables -V (shows nf_tables) |
| Translate one rule | iptables-translate -A INPUT -p tcp --dport 22 -j ACCEPT |
| Translate a saved ruleset | iptables-save > old.rules && iptables-restore-translate -f old.rules |
Changing rules without locking yourself out
- Check the file:
nft -c -f new.conf catches syntax errors, not mistakes in logic.
- Schedule a rollback before loading:
systemd-run --on-active=60s --timer-property=AccuracySec=1s --unit=nft-rollback nft -f /etc/nftables.conf. Without AccuracySec, systemd may run the timer up to a minute late.
- Load the new rules, then open a new SSH session. Your current session survives a bad SSH rule (it's already established); the next login doesn't.
- If the new login works, make the change permanent and
systemctl stop nft-rollback.timer. If not, wait: the old rules come back by themselves.
Troubleshooting
| Symptom | Check |
| Traffic through the router fails, firewall looks right | sysctl net.ipv4.ip_forward; then the forward chain's counters |
| Rule seems to be ignored | Rule order: nft -a list chain …; an earlier rule matched first. Or an iptables rule on the same hook |
| Port forward doesn't work | The dnat rule's counter, the forward chain's ct status dnat rule, and the server's route back |
log rules print nothing in a container | The host must allow it: net.netfilter.nf_log_all_netns=1 |
Docker's rules vanished after flush ruleset | flush ruleset removes every table, including other software's; replace one table with destroy table … instead |