Many servers still carry an iptables firewall written years ago. Current distributions are built around nftables, and the iptables tools now mostly act as a translation layer. Moving to native nftables gives you one config file for IPv4 and IPv6, sets instead of long rule lists, and fewer surprises when tools mix. This guide converts an existing ruleset with the official translation tools and switches over without losing SSH access.
Before you start
- A Linux VPS running Ubuntu 22.04 or 24.04, Debian 12, or AlmaLinux or Rocky Linux 8 or 9, with an existing iptables ruleset.
- Root or a sudo user.
- No UFW or firewalld in use. Those tools manage their own rules; keep using them, or disable them first. See UFW vs firewalld vs nftables vs iptables.
- Access to the browser console in your VPS provider's control panel.
Warning: Switching firewalls changes every rule at once. Make sure the translated ruleset allows your SSH port before you load it, and schedule an automatic rollback as shown below. If SSH stops working, log in through the browser console and run sudo nft flush ruleset.
Install the tools if they are missing:
# Ubuntu and Debian
sudo apt install nftables iptables
# AlmaLinux and Rocky Linux
sudo dnf install nftables iptables-nftCheck which iptables backend you use
sudo iptables -Viptables v1.8.x (nf_tables): you run iptables-nft. Your rules already live in the kernel's nftables engine, written through a compatibility layer. This is the default on all the distributions above.iptables v1.8.x (legacy): you run the old x_tables backend. Rules live in a separate kernel subsystem that nftables does not see.
On Debian and Ubuntu, the backend is selected with alternatives:
update-alternatives --display iptablesEither way, translating to native nft syntax is the clean path. Dumping nft list ruleset on an iptables-nft system is not enough: those tables are owned by iptables, and recent nft versions mark them as such.
Export the current rules
Save both IPv4 and IPv6 rules. These files are also your backup:
sudo iptables-save | sudo tee /root/iptables-v4.rules
sudo ip6tables-save | sudo tee /root/iptables-v6.rulesLook through them and note which ports are allowed, especially SSH.
Translate the ruleset
iptables-restore-translate converts a whole saved file into nft commands:
sudo iptables-restore-translate -f /root/iptables-v4.rules | sudo tee /root/ruleset-v4.nft
sudo ip6tables-restore-translate -f /root/iptables-v6.rules | sudo tee /root/ruleset-v6.nftYou can also translate a single rule to learn the syntax:
iptables-translate -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPTnft 'add rule ip filter INPUT tcp dport 22 ct state new counter accept'Check for anything the tool could not convert. Untranslated rules appear as comment lines starting with #:
grep -n '^#' /root/ruleset-v4.nft /root/ruleset-v6.nftRewrite those by hand, or decide you no longer need them. Rules added by Docker or Fail2ban usually show up here too. Leave those out, because those services recreate their own rules when they start.
Build and test the new config
Create the config file. On Ubuntu and Debian it is /etc/nftables.conf; on AlmaLinux and Rocky Linux it is /etc/sysconfig/nftables.conf:
#!/usr/sbin/nft -f
flush ruleset
include "/etc/nftables.d/ruleset-v4.nft"
include "/etc/nftables.d/ruleset-v6.nft"Move the translated files into place and check the syntax:
sudo mkdir -p /etc/nftables.d
sudo cp /root/ruleset-v4.nft /root/ruleset-v6.nft /etc/nftables.d/
sudo nft -c -f /etc/nftables.confThe translated output keeps the iptables layout: separate ip filter and ip6 filter tables with INPUT, FORWARD and OUTPUT chains. That works as it is. Later you can merge them into one inet table, as shown in Get started with nftables.
Switch over
- Schedule a rollback that clears all nftables rules in five minutes, in case something is wrong. The pattern is explained in Test firewall changes with an automatic rollback:
sudo systemd-run --on-active=5m --unit=nft-rollback /usr/sbin/nft flush ruleset- Stop the old service that restored iptables rules at boot:
# Ubuntu and Debian
sudo systemctl disable --now netfilter-persistent
# AlmaLinux and Rocky Linux
sudo systemctl disable --now iptables ip6tablesIgnore "unit not found" errors if you never used them.
- If you were on the legacy backend, clear the old rules so they cannot conflict:
sudo iptables-legacy -F && sudo iptables-legacy -X && sudo iptables-legacy -P INPUT ACCEPT
sudo ip6tables-legacy -F && sudo ip6tables-legacy -X && sudo ip6tables-legacy -P INPUT ACCEPT- Load the new ruleset and test with a new SSH session:
sudo nft -f /etc/nftables.conf- If SSH works, cancel the rollback and enable nftables at boot:
sudo systemctl stop nft-rollback.timer
sudo systemctl enable --now nftablesCheck it works
sudo nft list ruleset
systemctl is-enabled nftablesCompare the allowed ports with your original iptables-save output. From another computer, test that allowed ports connect and blocked ports do not:
nc -vz 203.0.113.10 22
nc -vz -w 3 203.0.113.10 3306Reboot during a quiet period and check nft list ruleset again.
Troubleshooting
- Translated file fails
nft -c: an extension had no direct nft equivalent. Find the line number in the error and rewrite that rule by hand. - Old rules still apply after switching: you were on the legacy backend and did not flush it. Check with
sudo iptables-legacy -S. - Rules disappear at boot:
netfilter-persistentoriptables.serviceis still enabled and loads the old file, or nftables is not enabled. - Docker or Fail2ban stopped working: restart them after loading, so they add their rules to the new ruleset.
- Locked out: log in through the browser console, run
sudo nft flush ruleset, and fix the SSH rule.
Next steps
- Replace long IP rule lists with sets: Use nftables sets for fast allowlists and blocklists.
- Practise on a spare Linux VPS before migrating production.
