IPCHAINS HOWTO: from ipchains and iptables to nftables
ipchains was the packet filter of the Linux 2.2 kernel. No current kernel or distribution has it. Its job passed to iptables in Linux 2.4 and then to nftables, which every current distribution uses. If you still have an ipchains or iptables script, this page shows how the old rules map to nftables and how to move them over without breaking access to the machine.
Four generations of Linux firewalls
| Tool | Kernels | Status as of November 2026 |
|---|---|---|
| ipfwadm | 2.0 | Historic |
| ipchains | 2.1.102 and later, the 2.2 series | Historic; removed from current kernels |
| iptables (and ip6tables, arptables, ebtables) | 2.4 and later | Still maintained (iptables 1.8.13 as of November 2026); usually runs as iptables-nft on top of nftables |
nftables (nft) | 3.13 and later | The current framework (nftables 1.1.7 as of November 2026) |
There is no tool in current distributions that reads ipchains syntax. The practical path is to rewrite ipchains rules by hand into iptables form (the table below helps), then let the official translator turn iptables rules into nftables. Or skip the middle step and write the nftables rules directly; for a small rule set that is often quicker.
What changed between the generations
Most ipchains rules have a direct equivalent, but one change in meaning catches people out. In ipchains, the input chain saw every packet coming in, including packets that would be forwarded to another machine. From iptables on, INPUT only sees packets addressed to the machine itself, and forwarded packets only pass through FORWARD. nftables keeps that model: its input and forward hooks are separate.
| Task | ipchains (2.2) | iptables | nftables |
|---|---|---|---|
| Default deny | -P input DENY | -P INPUT DROP | policy drop; in the chain declaration |
| Chain names | lower case: input, forward, output | upper case: INPUT, FORWARD, OUTPUT | you choose the names; the hook decides where they run |
| Port match | port after the address: -d 0/0 22 | -p tcp --dport 22 | tcp dport 22 |
| New TCP connections | -y | --syn | tcp flags syn / fin,syn,rst,ack or ct state new |
| Logging | -l on a rule | -j LOG | log prefix "..." |
| Masquerading | -A forward ... -j MASQ | -t nat -A POSTROUTING ... -j MASQUERADE | masquerade in a type nat hook postrouting chain |
| Transparent proxy | -j REDIRECT 3128 | -t nat -A PREROUTING ... -j REDIRECT --to-ports 3128 | redirect to :3128 |
| IPv6 | not supported | separate ip6tables rules | one inet table covers IPv4 and IPv6 |
In iptables, interface matches also became stricter: in the filter table, -i (incoming interface) works only in INPUT and FORWARD, and -o (outgoing) only in FORWARD and OUTPUT (in the nat table, PREROUTING takes -i and POSTROUTING takes -o). In nftables the same idea is iifname and oifname.
Step 1: check what you have
iptables -V
sudo iptables-save
sudo nft list ruleset
iptables -V tells you which variant the iptables command is. (nf_tables) means it is iptables-nft: it accepts the old syntax but stores the rules in nftables, so nft list ruleset shows them as tables named ip filter, ip nat and so on. (legacy) means the old in-kernel xtables code. Debian switches between the two with update-alternatives --set iptables /usr/sbin/iptables-nft (or -legacy).
Do not mix the legacy tools with nftables on the same machine. Rules from both are active at the same time, and the result is hard to predict.
Step 2: translate the rules
The iptables package includes two translators. They only print text; they do not change the running firewall.
One command at a time:
$ iptables-translate -A INPUT -p tcp --dport 22 -j ACCEPT
nft 'add rule ip filter INPUT tcp dport 22 counter accept'
$ iptables-translate -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j MASQUERADE
nft 'add rule ip nat POSTROUTING oifname "eth0" ip saddr 192.168.1.0/24 counter masquerade'
A whole saved rule set, including chain policies:
sudo iptables-save > save.txt
iptables-restore-translate -f save.txt > ruleset.nft
sudo ip6tables-save > save6.txt
ip6tables-restore-translate -f save6.txt > ruleset6.nft
For a small IPv4 rule set, the output looks like this (the date comments are left out):
add table ip filter
add chain ip filter INPUT { type filter hook input priority 0; policy drop; }
add chain ip filter FORWARD { type filter hook forward priority 0; policy drop; }
add chain ip filter OUTPUT { type filter hook output priority 0; policy accept; }
add rule ip filter INPUT iifname "lo" counter accept
add rule ip filter INPUT ct state related,established counter accept
add rule ip filter INPUT tcp dport 22 counter accept
add rule ip filter INPUT ip protocol icmp counter accept
Read the output before you load it
- Untranslated rules become comments. When an extension has no translation, the line starts with
#. For example,iptables-translate -A INPUT -m recent --name ssh --setprintsnft # -A INPUT -m recent --name ssh --set. Loading that file silently leaves the rule out. Search the output for#and rewrite those rules by hand. - Policies need the whole file. A single
iptables-translate -P INPUT DROPprints “Translation not implemented”.iptables-restore-translatedoes carry the policy into the chain declaration. - Every rule gets a
counter. That is harmless; remove it if you do not need packet counts. - ARP and bridge rules have their own translators,
arptables-translateandebtables-translate, in the same package; check their output the same way.
Step 3: tidy up into native nftables
A literal translation keeps the old structure: separate IPv4 and IPv6 tables, one rule per port. Once it works, rewrite it the nftables way. The translated rules above become:
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority filter; policy drop;
ct state established,related accept
ct state invalid drop
iifname "lo" accept
meta l4proto { icmp, ipv6-icmp } accept
tcp dport 22 accept
}
chain forward {
type filter hook forward priority filter; policy drop;
}
}
One inet table now covers IPv4 and IPv6, and a set like tcp dport { 22, 80, 443 } replaces a list of separate rules (iptables needed the multiport match for that). Note the added ICMPv6 rule: an old ip6tables set that dropped all ICMPv6 breaks IPv6 neighbor discovery.
Step 4: switch over safely
- Set a rollback timer if you are working over SSH (see avoiding lock-out).
- Check the new file:
sudo nft -c -f ruleset.nft. - Stop and disable whatever loaded the old rules at boot (for example a
netfilter-persistentservice on Debian or Ubuntu,iptables.serviceon Fedora or RHEL, or a script in/etc/rc.local). - Load the new rules with
sudo nft -f ruleset.nft, then test a new SSH login and each service. - Install the file where the
nftablesservice loads it at boot (/etc/nftables.confon Debian, an include in/etc/sysconfig/nftables.confon Fedora and RHEL) and enable the service. - Reboot once in a planned window and check
sudo nft list ruleset.
Programs that still speak iptables. Docker, some VPN scripts and older fail2ban setups add their own iptables rules. With iptables-nft those land in nftables tables of their own and keep working next to yours, as long as your file does not start with flush ruleset. On such hosts, replace only your own table, as shown in the Firewall HOWTO.
Translating old masquerading
The classic ipchains “three-line masquerade” turned on forwarding, set the forward policy to deny, and masqueraded the LAN. Today the same job takes a NAT chain plus a forward chain that allows LAN traffic out; the full, tested setup is in the IP Masquerade HOWTO. The old ipmasqadm port forwarding becomes a dnat to rule in a type nat hook prerouting chain.
The classic 2000 version of the IPCHAINS HOWTO, with the full ipchains syntax, is kept by the LDP at tldp.org.
Sources
- Moving from iptables to nftables, nftables wiki
- xtables-translate(8): iptables-translate and iptables-restore-translate, Debian 13 manual pages
- xtables-nft(8): iptables-nft, Debian 13 manual pages
- Linux packet filtering HOWTO: differences between iptables and ipchains, netfilter.org
- The iptables project, netfilter.org
- The nftables project, netfilter.org
- Performing Network Address Translation (NAT), nftables wiki
- nftables, Debian Wiki
- Linux IPCHAINS-HOWTO (2000), The Linux Documentation Project
The translator output on this page was produced with iptables 1.8.11 (Debian 13) and the rule sets were test-loaded with nftables 1.1.3.