TTR Networks

Intermediate · nftables and iptables on Debian: host and router firewalls · section 4 of 10

Changing rules safely

✓ Lab-tested 21 October 2026 on Debian 13.7, nftables 1.1.3, iptables 1.8.11

The classic firewall mistake is locking yourself out of a remote machine. The lab shows the sneaky version: a ruleset that seems fine.

The change narrows SSH to the admin PC, but with a typo: 10.10.0.2 instead of 10.10.0.20. It loads without complaint. The SSH session that loaded it keeps working, because its packets are established. The mistake only shows when you log in again, and then there's no way in. Recovery needed the console (in the lab, docker exec; on a real server, the provider's web console or a trip to the building).

Check first

nft -c -f FILE parses the file and checks every rule against the kernel without changing anything. It catches syntax errors and unknown names (here, acept), with the line and position. It can't catch a rule that's valid but wrong, like the typo'd address.

A timed rollback

Before loading a risky change on a remote machine, schedule its undo:

systemd-run --on-active=60s --timer-property=AccuracySec=1s --unit=nft-rollback nft -f /etc/nftables.conf
nft -f /root/new.conf

The timer reloads the known-good /etc/nftables.conf in a minute, whatever happens. Then, from a new SSH session, check you can still get in. If you can, make the change permanent (copy it over /etc/nftables.conf) and cancel the rollback with systemctl stop nft-rollback.timer. If you can't, wait a minute and you're back.

In the lab, the typo'd change locked SSH out again, and access came back by itself 30 seconds later. Note AccuracySec=1s: systemd timers fire within a minute of their time by default, to save power. In an earlier run without it, a 30-second rollback still hadn't happened 40 seconds later.

The same idea works with any change: keep the working configuration in place, test the new one with an automatic way back, and only then replace the file.

/root/new.conf: the change, with a typo
#!/usr/sbin/nft -f
# /root/new.conf: a change to test before it replaces /etc/nftables.conf.
flush ruleset

table inet filter {
  chain input {
    type filter hook input priority filter; policy drop;

    ct state established,related accept comment "replies to traffic we started"
    ct state invalid drop
    iif lo accept comment "the machine talking to itself"

    icmp type { echo-request, destination-unreachable, time-exceeded } accept
    iifname "eth1" ip saddr 10.10.0.2 tcp dport 22 accept comment "SSH from the admin PC only"

    counter comment "everything else hits the policy: drop"
  }

  chain output {
    type filter hook output priority filter; policy accept;
  }
}
  • Line 14: Meant to be 10.10.0.20, the admin PC. It's a valid address, so nothing complains.
Locked out (real output)
root@pc:~# ssh fw "nft -f /root/new.conf && echo loaded; nft list chain inet filter input | grep admin"
loaded
		iifname "eth1" ip saddr 10.10.0.2 tcp dport 22 accept comment "SSH from the admin PC only"

root@pc:~# ssh fw hostname
ssh: connect to host 10.10.0.1 port 22: Connection timed out

root@fw:~# systemctl reload nftables

root@pc:~# ssh fw hostname
fw
  • Lines 1–3: Loaded from an SSH session, which keeps working: its packets are established.
  • Lines 5–6: A new login is a new connection, and nothing allows it.
  • Line 8: Recovery from the console: load the file in /etc again.
  • Lines 10–11: Back in.
nft -c catches a typo, not a mistake (real output)
root@fw:~# nft -c -f /root/typo.conf; echo "exit code $?"
/root/typo.conf:14:59-65: Error: syntax error, unexpected comment
    iifname "eth1" ip saddr 10.10.0.20 tcp dport 22 acept comment "SSH from the admin PC only"
                                                          ^^^^^^^
exit code 1
  • Lines 2–4: The line and position of the problem. Nothing was loaded.
  • Line 5: A non-zero exit code, so scripts can stop here.
The same mistake with a timed rollback (real output)
root@pc:~# ssh fw "systemd-run --on-active=30s --timer-property=AccuracySec=1s --unit=nft-rollback nft -f /etc/nftables.conf && nft -f /root/new.conf && echo loaded"
Running timer as unit: nft-rollback.timer
Will run service as unit: nft-rollback.service
loaded

root@pc:~# ssh fw hostname
ssh: connect to host 10.10.0.1 port 22: Connection timed out

root@pc:~# sleep 30; ssh fw hostname
fw

root@pc:~# ssh fw "journalctl -u nft-rollback --no-pager -o short-iso | tail -2"
2026-10-21T07:46:49+00:00 fw systemd[1]: Started nft-rollback.service - [systemd-run] /usr/sbin/nft -f /etc/nftables.conf.
2026-10-21T07:46:49+00:00 fw systemd[1]: nft-rollback.service: Deactivated successfully.
  • Lines 1–4: First schedule the undo, then load the change.
  • Lines 6–7: Locked out again…
  • Lines 9–10: …but 30 seconds later the timer has reloaded the old rules.
  • Lines 13–14: What the timer did.
A good change, kept (real output)
root@pc:~# ssh fw "systemd-run --on-active=60s --timer-property=AccuracySec=1s --unit=nft-rollback nft -f /etc/nftables.conf && nft -f /root/new.conf && echo loaded"
Running timer as unit: nft-rollback.timer
Will run service as unit: nft-rollback.service
loaded

root@pc:~# ssh fw hostname
fw

root@pc:~# ssh fw "cp /root/new.conf /etc/nftables.conf && systemctl stop nft-rollback.timer && systemctl list-timers --all --no-pager | grep -c nft-rollback"
0
  • Lines 1–4: The file with the typo fixed (10.10.0.20), loaded with a minute's safety net.
  • Lines 6–7: A brand-new session gets in: the change works.
  • Lines 9–10: Make it permanent and cancel the rollback. No timer left.

Check your understanding

You load new rules over SSH and your session still works. Is the change safe?

  • Not yet: test with a new SSH session, because your current one is allowed as established.
  • Yes: if SSH were blocked, the session would have dropped.
  • Yes, if nft -c passed first.
  • Only if the policy is accept.