For over twenty years, iptables has been the undisputed king of Linux networking. Whether configuring a basic home router or a complex enterprise firewall, system administrators relied on the iptables NAT (Network Address Translation) tables to masquerade internal IP addresses behind a public IP.
However, iptables is architecturally obsolete. It relies on a linear rule evaluation structure (causing extreme CPU usage on massive rulesets) and requires completely separate tools (iptables, ip6tables, arptables) to manage different protocols.
The Linux kernel has officially replaced iptables with nftables. Built around a pseudo-state machine and an incredibly efficient bytecode engine, nftables consolidates IPv4 and IPv6 routing into a single, unified syntax. It evaluates rules simultaneously, significantly reducing latency on high-throughput routers.
This guide explains how to abandon legacy iptables and architect a high-performance Linux NAT router using the modern nftables framework.
Understanding the nftables Architecture
Unlike iptables, which ships with hardcoded tables (filter, nat, mangle) and chains (INPUT, FORWARD), nftables starts completely empty. You must define the tables, chains, and hooks yourself.
To configure NAT, you need:
- A Table: A logical container for your rules (e.g.,
ip natfor IPv4, orinet natfor a combined IPv4/IPv6 table). - A Prerouting Chain: Hooks into the kernel before the routing decision is made. Used for Destination NAT (Port Forwarding).
- A Postrouting Chain: Hooks into the kernel after the routing decision is made. Used for Source NAT (Masquerading internal traffic to the internet).
Step 1: Enabling IP Forwarding
Before any NAT rules can function, the Linux kernel must be explicitly told to act as a router and forward packets between interfaces.
Open the sysctl configuration file:
sudo nano /etc/sysctl.d/99-routing.conf
Add the following lines to enable IPv4 and IPv6 forwarding:
net.ipv4.ip_forward=1
net.ipv6.conf.all.forwarding=1
Apply the changes immediately:
sudo sysctl -p /etc/sysctl.d/99-routing.conf
Step 2: Installing nftables
Most modern Linux distributions (Debian 11+, Ubuntu 22.04+, RHEL 9) ship with nftables pre-installed, but you should verify it.
sudo apt-get update
sudo apt-get install nftables
Enable the nftables service so your rules persist across reboots:
sudo systemctl enable nftables
Step 3: Constructing the NAT Ruleset
Instead of running dozens of separate command-line entries (as was common with iptables), the best practice in nftables is to write a clean, declarative configuration file.
Assume your router has two interfaces:
eth0: The External/WAN interface (Public IP).eth1: The Internal/LAN interface (e.g.,10.0.0.0/24).
Open the primary configuration file:
sudo nano /etc/nftables.conf
Clear the file and insert the following architecture:
flush ruleset
# Create a unified table for both IPv4 and IPv6
table inet my_nat {
# Define the Postrouting chain for SNAT (Masquerading)
chain postrouting {
type nat hook postrouting priority 100; policy accept;
# Masquerade traffic leaving via the WAN interface (eth0)
# This translates internal IPs (10.0.0.x) to the Public IP.
oifname "eth0" masquerade
}
# Define the Prerouting chain for DNAT (Port Forwarding)
chain prerouting {
type nat hook prerouting priority -100; policy accept;
# Port Forwarding: Route inbound TCP port 80/443 on WAN to internal web server 10.0.0.50
iifname "eth0" tcp dport { 80, 443 } dnat ip to 10.0.0.50
# Port Forwarding: Route inbound TCP port 2222 to internal SSH server 10.0.0.60 port 22
iifname "eth0" tcp dport 2222 dnat ip to 10.0.0.60:22
}
}
Step 4: Applying and Verifying the Ruleset
Because the syntax is declarative, you can load the entire file atomically. If there is a syntax error, the kernel rejects the whole file, ensuring you never end up in a partially configured, broken state.
Apply the ruleset:
sudo nft -f /etc/nftables.conf
To verify the active running configuration inside the kernel memory:
sudo nft list ruleset
The output will beautifully display the nested tables, chains, and rules exactly as you structured them.
Step 5: Managing the Rules Dynamically
While the configuration file is ideal for static setups, you can still manipulate the tables on the fly without restarting the service.
For example, to quickly add a new Port Forwarding rule for a game server (UDP port 27015 to 10.0.0.70):
sudo nft add rule inet my_nat prerouting iifname "eth0" udp dport 27015 dnat ip to 10.0.0.70
This dynamic syntax compiles directly to kernel bytecode, executing significantly faster than legacy iptables insertions.
Conclusion
The era of iptables is over. By migrating routing architectures to the modern nftables framework, network engineers can consolidate complex IPv4 and IPv6 translation logic into unified, human-readable configuration files. Combining this elegant syntax with the kernel’s highly optimized bytecode evaluation engine allows Linux to operate as a high-throughput, enterprise-grade NAT router capable of handling massive connection states with minimal CPU overhead.