Skip to content

nftables: Modern Linux Firewall

Warning

Before changing nftables, make sure you have physical or console access to the server. A misconfigured input chain can block SSH and lock you out.

nftables replaced iptables in the Linux kernel starting with version 3.13. If you’re still writing rules in iptables style, it’s time to reconsider. nftables performs better, has built-in dual-stack IPv4/IPv6 support, and lets you manage the entire ruleset as a whole instead of entering commands one by one.

Why Migrate from iptables

iptables has fundamental design problems. Each table (filter, nat, mangle) is a separate rule set with its own semantics. There is no built-in support for simultaneous IPv4 and IPv6 handling—you end up writing two separate rule sets. Performance degrades with a large number of rules due to linear lookup.

nftables takes a different approach. All protocols work within a single data structure—the ruleset. The kernel compiles rules into efficient lookup structures. Atomic ruleset replacement eliminates race conditions during rule updates.

Warning

RHEL 8 and newer redirect iptables to nftables by default. Ubuntu 22.04 and later do the same. Check with update-alternatives --display iptables.

Basic Commands: Viewing and Flushing Rules

The first command to memorize:

nft list ruleset

Output shows all tables, chains, and rules. Without tables, output is empty—this is normal.

Create a table named filter for packet handling:

nft add table inet filter

inet means the table handles both protocols. For IPv4-only use ip, for IPv6 use ip6.

Add a chain for incoming traffic:

nft add chain inet filter input { type filter hook input priority 0 \; policy accept \; }

Flags breakdown:

FlagPurpose
type filterChain type—packet filtering
hook inputAttachment point—incoming packets
priority 0Processing order relative to other hooks
policy acceptDefault action—allow everything

Flush rules in a chain:

nft flush chain inet filter input

Delete an entire table:

nft delete table inet filter

Adding and Deleting Rules by Handle

Create several rules and inspect their handles:

nft add rule inet filter input tcp dport 22 accept
nft add rule inet filter input tcp dport 80 accept
nft add rule inet filter input tcp dport 443 accept
nft list ruleset

Output shows something like:

table inet filter {
    chain input {
        type filter hook input priority 0; policy accept;
        tcp dport 22 accept
        tcp dport 80 accept
        tcp dport 443 accept
    }
}

Handles are hidden by default. To work with specific rules:

nft -a list ruleset

The -a flag reveals the handle for each rule. Now you can delete by number:

nft delete rule inet filter input handle 3
Tip

Handles change whenever you add or delete a rule. If your script modifies the ruleset, save nft -a list ruleset output to a file for tracking.

Add a rule with priority before existing ones—at the chain start:

nft insert rule inet filter input tcp dport 2222 accept

The add command appends to the end, insert adds at the start. For insertion at a specific position:

nft add rule inet filter input position 2 tcp dport 8080 accept

Atomic Ruleset Replacement

Adding rules one by one creates a window where some rules are active and others are not yet applied. This is unacceptable for production.

The solution: write the complete ruleset to a file and load it atomically:

nft list ruleset > /etc/nftables.conf

/etc/nftables.conf is the standard location across most distributions. Now edit it:

#!/usr/sbin/nft -f

flush ruleset

table inet filter {
    chain input {
        type filter hook input priority 0; policy drop;
        
        ct state established,related accept
        ct state invalid drop
        
        iif lo accept
        
        tcp dport 22 accept
        tcp dport 80 accept
        tcp dport 443 accept
        
        counter drop
    }
}

Load it:

nft -f /etc/nftables.conf
Note

flush ruleset clears everything before loading. If you need to append to existing rules, remove this line.

Check without applying:

nft -c -f /etc/nftables.conf

The -c flag performs syntax validation without changing state. Useful in CI/CD before deployment.

Enable persistence on systemd distros:

systemctl enable --now nftables

nftables.service loads /etc/nftables.conf on boot. After editing the file, systemctl restart nftables applies it.

Forward Chain

If the host is not a router, leave forward empty with a drop policy. When IP forwarding is on, the minimum is established replies plus transit between interfaces:

nft add chain inet filter forward { type filter hook forward priority 0 \; policy drop \; }
nft add rule inet filter forward ct state established,related accept
nft add rule inet filter forward iifname "eth0" oifname "eth1" accept

For debugging, log before the implicit drop:

nft add rule inet filter forward log prefix "nft-forward-drop: " level warn

Logs go to journalctl -k or /var/log/kern.log.

Common Scenarios: Blocking Ports and IPs

Block incoming connection from a specific IP:

nft add rule inet filter input ip saddr 1.2.3.4 drop

Block outgoing to a specific IP:

nft add rule inet filter output ip daddr 5.6.7.8 drop

Block an IP range (CIDR):

nft add rule inet filter input ip saddr 10.0.0.0/8 drop

Block a port for everyone:

nft add rule inet filter input tcp dport 25 drop

Allow a port only for a specific subnet:

nft add rule inet filter input ip saddr 192.168.1.0/24 tcp dport 5432 accept

Log dropped packets:

nft add rule inet filter input counter drop

Counters appear in nft list ruleset output—they show packet and byte counts.

NAT via Masquerading

For sharing a single IP with a local network:

nft add table ip nat
nft add chain ip nat postrouting { type nat hook postrouting priority 100 \; }
nft add rule ip nat postrouting ip saddr 192.168.0.0/24 masquerade

Masquerading automatically substitutes the external IP of the interface. For port forwarding NAT:

nft add chain ip nat prerouting { type nat hook prerouting priority -100 \; }
nft add rule ip nat prerouting tcp dport 8080 dnat to 192.168.0.100:80
Warning

NAT in nftables works only for IPv4. For IPv6, use stateless NAT66 or routing-level addressing.

iptables-nft Compatibility Mode

Some distributions keep iptables “working” by translating to nftables:

update-alternatives --set iptables /usr/sbin/iptables-nft
update-alternatives --set ip6tables /usr/sbin/ip6tables-nft

The problem is these are two different worlds. iptables-nft translates commands to nftables, but reverse compatibility does not work. Rules created via iptables will not appear directly in nft list ruleset.

Warning

Do not use iptables and nftables simultaneously. The result is unpredictable. Either fully migrate to nftables or stay on iptables. Check current mode: iptables -V shows whether iptables-legacy or iptables-nft is in use.

For migration from iptables, use the iptables-translate utility:

iptables-translate -A INPUT -p tcp --dport 22 -j ACCEPT

Output: nft add rule ip filter input tcp dport 22 accept. Manual verification of output is mandatory—automatic translation is not perfect.

nftables is not the future—it is the present. If you administer Linux servers, spend an evening on migration. The file /etc/nftables.conf with the complete ruleset is your backup and deployment in one.