How to Install and Configure nftables on Linux: A Complete iptables Replacement

Linux tutorial - IT technology blog
Linux tutorial - IT technology blog

Quick start: Set up a basic firewall with nftables in 5 minutes

Managing Linux firewalls used to be a hassle when maintaining two separate rule sets: iptables for IPv4 and ip6tables for IPv6. With nftables, Linux consolidates everything into a unified framework. You get cleaner syntax, atomic rule updates with zero downtime, and significantly higher packet-processing performance.

Step 1: Install nftables and disable legacy firewalls

Before enabling nftables, disable higher-level firewall services to avoid netfilter hook conflicts in the Linux kernel:

# On Ubuntu / Debian
sudo apt update && sudo apt install -y nftables
sudo systemctl stop ufw && sudo systemctl disable ufw

# On RHEL / AlmaLinux / Rocky Linux
sudo dnf install -y nftables
sudo systemctl stop firewalld && sudo systemctl disable firewalld

Step 2: Load a minimal ruleset to protect your server

The default configuration file is located at /etc/nftables.conf. Open it in a text editor:

sudo nano /etc/nftables.conf

Delete the existing contents and paste in the standard ruleset below. This configuration maintains active SSH sessions, opens standard web ports, and drops all remaining unauthorized traffic:

#!/usr/sbin/nft -f

flush ruleset

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

        # Allow local loopback traffic
        iif "lo" accept

        # Keep active connections established (prevents dropping current SSH session)
        ct state established,related accept
        ct state invalid drop

        # Allow ICMP ping for both IPv4 and IPv6
        ip protocol icmp accept
        ip6 nexthdr ipv6-icmp accept

        # Open service ports: SSH (22), HTTP (80), and HTTPS (443)
        tcp dport 22 accept
        tcp dport { 80, 443 } accept
    }
}

Step 3: Verify syntax and start the service

Don’t rush into starting the service right away. A single typo in your configuration file could lock you out of SSH permanently.

# 1. Check for syntax errors without applying rules (dry-run)
sudo nft -c -f /etc/nftables.conf

# 2. Start and enable service on system boot
sudo systemctl enable --now nftables

# 3. Verify active ruleset in the kernel
sudo nft list ruleset

With just a single inet table, your system is fully protected across both network protocols without duplicating rules.

Core architecture and syntax of nftables

The nftables model is organized into a clean hierarchical tree: Table → Chain → Rule.

1. Tables and Address Families

Unlike iptables, nftables does not come with predefined tables like filter or nat. You explicitly create tables and assign appropriate address families:

  • inet: Supports a dual-stack mix of IPv4 and IPv6 in the same table. This is the recommended choice for most internet-facing servers.
  • ip / ip6: Handles IPv4 or IPv6 packets exclusively.
  • netdev: Supports the ingress hook, allowing you to filter or drop malicious traffic right at the network driver before it reaches the TCP/IP stack—ideal for DDoS mitigation.
  • bridge / arp: Used for Layer 2 bridging and handling ARP/MAC address resolution tables.

2. Understanding Regular Chains vs. Base Chains

Chains organize and group packet-filtering rules. nftables distinguishes between two types:

  • Base chain: Hooks directly into the Linux kernel network stack using the hook keyword (such as input, output, or forward). This type of chain requires setting an execution priority and a default policy (accept or drop).
  • Regular chain: Has no system hooks attached. It acts like a helper function that other rules can jump to, making complex configurations cleaner and modular.

3. Rule Syntax: Read naturally from left to right

nftables syntax completely eliminates the cryptic, abbreviated flags of iptables. Rules are expressed intuitively using the pattern: [match criteria] [action].

# Match multiple ports in a single rule
tcp dport { 80, 443, 8080, 8443 } accept

# Rate limit ping requests to prevent ICMP flooding
ip protocol icmp limit rate 5/second accept

Advanced techniques: NAT, Port Forwarding, and Brute-force Prevention

Enable IP Forwarding in the Kernel

Before setting up NAT or port forwarding, you must enable packet forwarding in the Linux kernel:

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

1. Configuring Source NAT (Masquerade)

This scenario applies when turning a Linux server into a gateway sharing an Internet connection with a local LAN or internal containers:

table ip nat_gateway {
    chain postrouting {
        type nat hook postrouting priority srcnat; policy accept;
        oifname "eth0" masquerade
    }
}

2. Port Forwarding (DNAT)

Forward traffic from a public IP to an internal application server (e.g., forwarding port 8080 on interface eth0 to port 80 of server 192.168.10.50):

table ip nat_gateway {
    chain prerouting {
        type nat hook prerouting priority dstnat; policy accept;
        iifname "eth0" tcp dport 8080 dnat to 192.168.10.50:80
    }
}

3. Automated SSH Brute-force Protection with Dynamic Sets

Instead of installing memory-hungry daemons like Fail2ban, you can leverage native dynamic sets and meters in nftables to drop attack IPs directly at the kernel level:

table inet my_firewall {
    # Dynamic blacklist for offending IPs, expires after 15 minutes
    set ssh_blacklist {
        type ipv4_addr
        flags dynamic, timeout
        timeout 15m
    }

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

        iif "lo" accept
        ct state established,related accept

        # Immediately drop traffic from blacklisted IPs
        ip saddr @ssh_blacklist drop

        # Flag IPs opening over 4 new SSH connections per minute and add to blacklist
        tcp dport 22 ct state new meter ssh_meter { ip saddr limit rate over 4/minute } add @ssh_blacklist { ip saddr } drop
        
        # Accept remaining legitimate SSH connections
        tcp dport 22 ct state new accept
    }
}

Real-world production insights

Performance: O(1) vs. O(n)

In a microservice ecosystem processing around 40,000 requests/sec, blocking over 3,000 IPs with iptables once pushed ksoftirqd load to 25% CPU per core. This was because iptables evaluated rules sequentially line by line (O(n) complexity).

Switching to nftables and grouping IP addresses into a Set (backed by kernel hash tables or red-black trees) dropped lookup times to O(1). CPU utilization for softirq dropped from 25% to under 2%, significantly stabilizing network latency.

Automatic migration from iptables to nftables

If your server currently hosts hundreds of complex iptables rules, you don’t need to rewrite them manually:

# 1. Export current ruleset to a file
sudo iptables-save > iptables_backup.rules

# 2. Automatically translate rules to nftables syntax
sudo iptables-restore-translate -f iptables_backup.rules > rules_converted.nft

# 3. Inspect converted ruleset
cat rules_converted.nft

Safety measures to avoid getting locked out of SSH

Whenever modifying firewall rules on a remote server, always set up a rollback mechanism to protect against accidental lockouts:

# Schedule automatic ruleset flush after 5 minutes in background
sleep 300 && sudo nft flush ruleset &

If your SSH session remains active after reloading the rules, simply run kill %1 to cancel the background sleep job. In the worst-case scenario, the firewall ruleset will automatically flush after 5 minutes, restoring normal access.

Share: