PQ
PQ.Hosting

Currency

nftables: The Modern Firewall for Ubuntu 22.04 That Makes iptables Obsolete

Author
PQ
March 30, 2026
6 min read
63 views
nftables: The Modern Firewall for Ubuntu 22.04 That Makes iptables Obsolete

Ubuntu 22.04 uses nftables by default — but most tutorials still show iptables. The iptables package on modern systems is just a wrapper over nftables via iptables-nft. Rules written in iptables are automatically translated to nftables syntax. Working with nftables directly is better: cleaner syntax, higher performance, and one table replaces iptables, ip6tables, arptables, and ebtables.

Check What the System Is Using

Confirm that nftables is active, not legacy iptables:

sudo iptables --version

If the output shows (nf_tables) — the system is already on nftables. If (legacy) — an explicit migration is needed.

View current nftables rules:

sudo nft list ruleset

On a fresh Ubuntu 22.04 the output will be empty or contain only UFW's base table if it is installed.

Architecture: Tables, Chains, Rules

In iptables there were several fixed tables: filter, nat, mangle. In nftables you create your own tables with any names and choose their address family.

Families:

  • ip — IPv4 only
  • ip6 — IPv6 only
  • inet — IPv4 and IPv6 simultaneously (most convenient for most tasks)
  • arp, bridge, netdev — specialized

Chain types:

  • type filter hook input — incoming traffic
  • type filter hook forward — transit
  • type filter hook output — outgoing
  • type nat hook prerouting — DNAT before routing
  • type nat hook postrouting — SNAT after routing

Installation and First Run

nftables is already installed on Ubuntu 22.04. Check and enable:

sudo apt install nftables
sudo systemctl enable --now nftables

Configuration file:

sudo nano /etc/nftables.conf

Apply after changes:

sudo nft -f /etc/nftables.conf

Check syntax without applying:

sudo nft -c -f /etc/nftables.conf

Basic Stateful Firewall for VPS

A minimal working configuration that allows SSH, HTTP, HTTPS and blocks everything else:

#!/usr/sbin/nft -f

flush ruleset

table inet filter {
    chain input {
        type filter hook input priority 0; policy drop;

        iif lo accept

        ct state established,related accept

        ct state invalid drop

        ip protocol icmp accept
        ip6 nexthdr icmpv6 accept

        tcp dport 22 accept
        tcp dport { 80, 443 } accept
    }

    chain forward {
        type filter hook forward priority 0; policy drop;
    }

    chain output {
        type filter hook output priority 0; policy accept;
    }
}

Key decisions explained:

iif lo accept — allow loopback. Without this, DNS resolution and inter-process communication break.

ct state established,related accept — allow replies to already-established connections. This is stateful: no need to write rules in both directions.

ct state invalid drop — explicitly drop packets with invalid connection state. Protection against certain attacks.

policy drop on the input chain — deny everything not explicitly allowed. Safer than policy accept.

Sets: Working With Groups of Addresses and Ports

Sets are one of nftables' biggest advantages over iptables. Instead of dozens of individual rules — one rule and a list.

Allow multiple ports:

tcp dport { 22, 80, 443, 8080 } accept

Create a named set for blocked IPs:

table inet filter {
    set blocklist {
        type ipv4_addr
        flags interval
        elements = { 192.168.1.0/24, 10.0.0.1, 203.0.113.0/28 }
    }

    chain input {
        type filter hook input priority 0; policy drop;

        ip saddr @blocklist drop

        ct state established,related accept
        tcp dport { 22, 80, 443 } accept
    }
}

Add an IP to the set on the fly without reloading rules:

sudo nft add element inet filter blocklist { 198.51.100.1 }

Remove:

sudo nft delete element inet filter blocklist { 198.51.100.1 }

Set with timeout — automatically remove IPs after a set time (similar to a temporary ban):

set blocklist {
    type ipv4_addr
    flags dynamic, timeout
    timeout 1h
}

NAT: Port Forwarding and Masquerade

Forward incoming port 8080 to an internal service on 3000:

table ip nat {
    chain prerouting {
        type nat hook prerouting priority -100;
        tcp dport 8080 dnat to :3000
    }

    chain postrouting {
        type nat hook postrouting priority 100;
        oif "eth0" masquerade
    }
}

masquerade automatically uses the outgoing interface IP as the source NAT address. More convenient than snat to when the interface IP may change.

Enable IP forwarding (required for NAT):

sudo sysctl -w net.ipv4.ip_forward=1
echo "net.ipv4.ip_forward=1" | sudo tee -a /etc/sysctl.d/99-forward.conf

Rate Limiting: Brute Force Protection

Limit new SSH connections — brute force protection without fail2ban:

chain input {
    type filter hook input priority 0; policy drop;

    ct state established,related accept

    tcp dport 22 ct state new limit rate 5/minute burst 10 packets accept
    tcp dport 22 ct state new drop

    tcp dport { 80, 443 } accept
}

limit rate 5/minute burst 10 packets — maximum 5 new connections per minute with a short-term burst allowance of 10. Everything above is dropped by the next rule.

Logging Dropped Packets

Log what is being dropped (useful during debugging):

chain input {
    type filter hook input priority 0; policy drop;

    ct state established,related accept
    tcp dport { 22, 80, 443 } accept

    limit rate 5/minute log prefix "nft drop: " flags all
    drop
}

View logs:

sudo journalctl -k | grep "nft drop"

Migrating From iptables

Export current iptables rules to nftables format:

sudo iptables-save | sudo iptables-restore-translate -f /dev/stdin

The output shows equivalent nftables rules. Copy them into /etc/nftables.conf.

Check that UFW rules do not conflict:

sudo nft list ruleset | grep -A5 "ufw"

If using UFW — either keep it, or disable and switch to pure nftables:

sudo ufw disable
sudo systemctl stop ufw

Persisting Rules Across Reboots

Save the current ruleset to file:

sudo nft list ruleset > /etc/nftables.conf

The nftables systemd service applies /etc/nftables.conf at startup automatically. Confirm the service is enabled:

sudo systemctl enable nftables

Quick Reference

Task Command
View all rules sudo nft list ruleset
Apply config sudo nft -f /etc/nftables.conf
Check syntax sudo nft -c -f /etc/nftables.conf
Flush all rules sudo nft flush ruleset
Add IP to blocklist sudo nft add element inet filter blocklist { IP }
Remove IP from blocklist sudo nft delete element inet filter blocklist { IP }
List tables sudo nft list tables
List chains sudo nft list chains
Translate iptables rules sudo iptables-save | iptables-restore-translate -f /dev/stdin
Save ruleset sudo nft list ruleset > /etc/nftables.conf

 

Share this article