Nftables is the successor to iptables, and it’s a complete rewrite of the Linux packet filtering framework. The most surprising thing about nftables is that it’s not just a new syntax for the same old thing; it’s a fundamentally different architecture that is both more powerful and more efficient.

Let’s see it in action. Imagine you want to allow SSH traffic from a specific IP address and block everything else.

With iptables, this would look something like this:

# Allow SSH from 192.168.1.100
iptables -A INPUT -p tcp --dport 22 -s 192.168.1.100 -j ACCEPT

# Drop all other incoming traffic
iptables -P INPUT DROP

Now, let’s do the same with nftables. First, we need to create a table and a chain:

nft add table ip filter
nft add chain ip filter input { type filter hook input priority 0 \; policy drop \; }

Then, we add the rule to allow SSH:

nft add rule ip filter input tcp dport 22 ip saddr 192.168.1.100 accept

Notice how the policy drop is set directly on the chain definition. This is one of the fundamental differences: nftables uses a single, unified framework for packet filtering, Network Address Translation (NAT), and packet accounting, whereas iptables had separate tools for each.

The nftables framework is built around the concept of tables, chains, and rules.

  • Tables are containers for chains. You can have tables for different address families (e.g., ip for IPv4, ip6 for IPv6, inet for both).
  • Chains are sequences of rules that packets traverse. Chains are attached to specific hooks (like input, output, forward) on the network stack and have a priority that determines the order in which multiple chains attached to the same hook are processed.
  • Rules are the actual instructions that specify what to do with a packet (e.g., accept, drop, reject, or perform NAT). Each rule consists of a match (a condition that must be met for the rule to apply) and an action (what to do if the match is successful).

The key advantages of this structure are:

  1. Unified Syntax and Data Structure: iptables had a complex set of command-line tools (iptables, ip6tables, arptables, ebtables) and a kernel module that was difficult to extend. nftables uses a single nft command and a more efficient internal data structure that allows for easier addition of new features and protocols.
  2. Atomic Operations: nftables rules and configurations are applied atomically. This means that a set of changes is either applied entirely or not at all, preventing intermediate inconsistent states that could leave your network exposed.
  3. Improved Performance: nftables uses a more efficient bytecode interpreter in the kernel, leading to faster rule lookups and packet processing. Rulesets can also be more compact. For instance, you can use sets and maps to group multiple IPs or ports, significantly reducing the number of individual rules.

Let’s look at an example of using sets in nftables. If you wanted to allow SSH from a whole subnet, instead of having a rule for each IP, you could do this:

nft add set ip filter allowed_ssh_clients { type ipv4_addr \; }
nft add element ip filter allowed_ssh_clients { 192.168.1.0/24 }
nft add rule ip filter input tcp dport 22 ip saddr @allowed_ssh_clients accept

This is much cleaner and more efficient than managing numerous individual iptables rules.

The nftables configuration is typically managed through files. A common approach is to have a configuration file (e.g., /etc/nftables.conf) that defines your entire firewall policy. When the nftables service starts, it loads this configuration.

Here’s a snippet from a typical nftables.conf:

#!/usr/sbin/nft -f

flush ruleset

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

        # Accept established and related connections
        ct state established,related accept

        # Allow loopback traffic
        iifname "lo" accept

        # Allow SSH from specific trusted IPs
        tcp dport 22 ip saddr { 192.168.1.100, 10.0.0.5 } accept

        # Allow ICMP
        ip protocol icmp accept
    }

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

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

table ip nat {
    chain prerouting {
        type nat hook prerouting priority -100; policy accept;
    }

    chain postrouting {
        type nat hook postrouting priority 100; policy accept;
        # Example: Masquerade outgoing traffic from a specific interface
        oifname "eth0" masquerade
    }
}

This file defines three tables: filter for firewalling, and nat for Network Address Translation. Within the filter table, we have input, forward, and output chains. The input chain’s policy is set to drop, meaning any packet not explicitly matched by a rule will be discarded. We then add rules to accept established connections, traffic on the loopback interface, SSH from specific IPs, and ICMP. The nat table demonstrates a common use case: masquerading traffic originating from the eth0 interface to provide internet access to internal machines.

One aspect that often trips people up is the priority setting on chains. This number dictates the order in which chains attached to the same hook are evaluated. Lower numbers mean earlier evaluation. For example, a chain with priority -100 (like prerouting in NAT) will be evaluated before a chain with priority 0 (like the input chain in the filter table). This is crucial for NAT, where you want to modify the destination address before the packet reaches the input filter.

The next step in mastering nftables involves understanding its advanced features like connection tracking (ct) for stateful firewalling, NAT configurations, and how to integrate nftables with service managers like systemd for automatic loading of rulesets.

Want structured learning?

Take the full Iptables course →