iptables & nftables Cheatsheet
Persistence
Use this iptables & nftables reference while you build software engineering projects, review code, or refresh the syntax you reach for most.
Rules are not saved by default
Everything iptables adds lives in kernel memory. A reboot clears it, and a
default ACCEPT policy means the host comes back with no firewall at all. Saving
is a separate, distribution-specific step.
| Distribution | Save mechanism | File |
|---|---|---|
| Debian, Ubuntu | iptables-persistent / netfilter-persistent | /etc/iptables/rules.v4, rules.v6 |
| Debian, Ubuntu (native nft) | nftables.service | /etc/nftables.conf |
| RHEL, Rocky, Alma, Fedora | iptables-services (optional) | /etc/sysconfig/iptables, ip6tables |
| RHEL family (native nft) | nftables.service | /etc/sysconfig/nftables.conf |
| RHEL family (default) | firewalld, own config | /etc/firewalld/zones/*.xml |
| Arch Linux | iptables.service / nftables.service | /etc/iptables/iptables.rules, /etc/nftables.conf |
| Ubuntu with ufw | ufw saves automatically | /etc/ufw/*.rules |
| Alpine Linux | /etc/init.d/iptables save | /etc/iptables/rules-save |
# which service actually owns the ruleset on this box systemctl list-unit-files | grep -E 'iptables|nftables|firewalld|ufw' systemctl is-enabled firewalld nftables iptables ufw 2>/dev/null
If firewalld or ufw is enabled, use its interface. Raw iptables rules
survive until the next reload of either tool, then vanish without warning.
Debian and Ubuntu
sudo apt install iptables-persistent # asks to save current rules on install sudo netfilter-persistent save # write rules.v4 and rules.v6 sudo netfilter-persistent reload # load them again sudo systemctl enable netfilter-persistent # or write the files directly sudo iptables-save > /etc/iptables/rules.v4 sudo ip6tables-save > /etc/iptables/rules.v6
Do not forget rules.v6. A host with a tight IPv4 ruleset and an empty IPv6 one
is fully open on IPv6, and most Linux distributions have IPv6 enabled with an
address configured by SLAAC.
For a native nftables setup on the same family of distributions:
sudo nft list ruleset > /etc/nftables.conf
sudo systemctl enable --now nftables
sudo nft -c -f /etc/nftables.conf # syntax check without loadingRHEL family
# only if you are deliberately not using firewalld sudo dnf install iptables-services sudo systemctl disable --now firewalld sudo systemctl enable --now iptables ip6tables sudo service iptables save # writes /etc/sysconfig/iptables sudo iptables-save > /etc/sysconfig/iptables # the native nftables service instead sudo systemctl enable --now nftables # reads /etc/sysconfig/nftables.conf
/etc/sysconfig/nftables.conf normally just includes other files, so add your
own file and reference it there rather than pasting a whole ruleset in.
| Task | RHEL 7 | RHEL 8, 9, 10 |
|---|---|---|
| Default front end | firewalld, iptables backend | firewalld, nftables backend |
| Raw iptables option | iptables-services | iptables-services, still packaged |
| Native nft option | not really | nftables.service, first-class |
| Backing kernel API | x_tables | nftables |
The save file format
iptables-save output is a plain text file you can read, diff, and edit.
# Generated by iptables-save v1.8.10 *filter :INPUT DROP [12:720] :FORWARD DROP [0:0] :OUTPUT ACCEPT [8241:1042113] :logdrop - [0:0] -A INPUT -i lo -j ACCEPT -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT -A INPUT -p tcp -m tcp --dport 22 -j ACCEPT -A INPUT -j logdrop -A logdrop -m limit --limit 5/min -j LOG --log-prefix "drop: " -A logdrop -j DROP COMMIT
| Line | Meaning |
|---|---|
*filter | start of a table block |
:INPUT DROP [12:720] | built-in chain, its policy, and [packets:bytes] |
:logdrop - [0:0] | a user chain, - because it has no policy |
-A ... | one rule, exactly as you would type it |
COMMIT | apply this table's block atomically |
sudo iptables-save -c > with-counters.rules # keep counters sudo iptables-save -t nat # one table only sudo iptables-restore < saved.rules # replaces everything sudo iptables-restore -n < extra.rules # no flush, append instead sudo iptables-restore -t < saved.rules # test parse only
iptables-restore flushes and replaces by default, which is what makes it
atomic. Use -n only when you genuinely want to add to the live ruleset.
Editing the file rather than the live ruleset
For anything beyond a one-line change, editing the save file and restoring it is
safer and faster than a dozen -I commands, because the whole ruleset lands in
one atomic operation and the file is reviewable.
sudo cp /etc/iptables/rules.v4 /root/rules.v4.bak
sudoedit /etc/iptables/rules.v4
sudo iptables-restore -t < /etc/iptables/rules.v4 # parse check first
sudo iptables-restore < /etc/iptables/rules.v4| Step | Reason |
|---|---|
| Copy first | one-command rollback |
-t parse check | a typo mid-file otherwise leaves a half-applied ruleset |
| Restore, then test from another host | proves the file, not just the syntax |
| Commit the file to config management | the running ruleset stops being the source of truth |
Rebooting without locking yourself out
# stage the new rules, keep the old file until proven sudo iptables-restore -t < /tmp/new.rules && sudo iptables-restore < /tmp/new.rules # prove it with a brand new session, not the one you are typing in ssh -o ControlMaster=no you@host 'echo reachable' # only then persist sudo cp /tmp/new.rules /etc/iptables/rules.v4
| Safety net | How |
|---|---|
| Timed rollback | ( sleep 300 && sudo iptables-restore < /root/rules.v4.bak ) & |
| Second session | keeps working on ESTABLISHED even if new connections are blocked |
| Console access | cloud serial console or IPMI, know how to reach it before you need it |
| Reboot test in a window | the only real proof that persistence works |
| Config management | rules rebuilt from source, not recovered from a dead host |
The reboot test matters more than it sounds. Saving rules and loading them are separate services, and a ruleset that is saved but whose unit is not enabled looks perfectly fine until the machine restarts.
Firewall state at boot, the ordering trap
Rules load from a systemd unit, so there is a window between the network coming up and the firewall being applied. On a host that must never be exposed, set the policies in the save file rather than relying on rules alone, and make sure the firewall unit orders before the services it protects.
systemctl show -p After,Before nftables.service systemctl show -p After,Before netfilter-persistent.service # make a service wait for the firewall sudo systemctl edit myapp.service # [Unit] # After=nftables.service # Wants=nftables.service
| Risk | Mitigation |
|---|---|
| Service listens before rules load | order the unit After= the firewall unit |
| Empty IPv6 ruleset | save and enable rules.v6 or use the inet family in nftables |
| Two backends both loaded | check iptables-legacy -S as well as nft list ruleset |
| firewalld overwrites manual rules | manage through firewall-cmd, or disable firewalld deliberately |
| Cloud image resets rules | the provider's own firewall or user-data script may also be writing |