nftables is a packet filtering framework that replaces the older iptables, ip6tables, and arptables. It’s designed to be more efficient, flexible, and easier to use.

Let’s see it in action. Imagine we want to allow SSH traffic (TCP port 22) from a specific IP address, 192.168.1.100, to our server, while blocking all other incoming traffic.

First, we need to create a table. Tables are the top-level containers for firewall rules.

sudo nft add table ip filter

This command creates an IPv4 table named filter. The ip keyword specifies the address family.

Next, we create chains within this table. Chains are ordered lists of rules that packets traverse. We’ll create three common chains: input (for packets destined for the local machine), forward (for packets being routed through the machine), and output (for packets originating from the local machine).

sudo nft add chain ip filter input { type filter hook input priority 0 \; policy drop \; }
sudo nft add chain ip filter forward { type filter hook forward priority 0 \; policy drop \; }
sudo nft add chain ip filter output { type filter hook output priority 0 \; policy accept \; }

Here’s what’s happening:

  • type filter: This chain is for packet filtering.
  • hook input: This chain is attached to the input hook, meaning it processes packets arriving at the local machine.
  • priority 0: This determines the order in which hooks are processed. Lower numbers mean earlier processing.
  • policy drop: This is crucial. It means that if a packet reaches the end of the input chain without matching any explicit accept rule, it will be dropped. This is a "default deny" stance, which is good security practice.
  • policy accept: For the output chain, we’re defaulting to accept, meaning outgoing connections are allowed by default.

Now, let’s add our specific rule to allow SSH from 192.168.1.100.

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

This rule is added to the input chain within the filter table for the ip family. It says: "If the source IP address (ip saddr) is 192.168.1.100 AND the destination TCP port (tcp dport) is 22, then accept the packet."

Because our input chain has a policy drop, any other incoming traffic that doesn’t match this rule (or any other accept rules we might add later) will be dropped.

Let’s verify our configuration:

sudo nft list ruleset

You’ll see output similar to this:

table ip filter {
        chain input {
                type filter hook input priority 0; policy drop;
                ip saddr 192.168.1.100 tcp dport 22 accept
        }

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

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

This output shows our table, our chains, their policies, and the single rule we added to the input chain.

The real power of nftables comes from its ability to create more complex rules and structures. You can define sets of IP addresses, ports, or even entire connection states to match against. For example, to allow established SSH connections, you’d add:

sudo nft add rule ip filter input tcp dport 22 ct state established,related accept

This rule allows traffic on TCP port 22 if the connection is already established or related to an established connection, as tracked by the connection tracking (ct) module.

The mental model is hierarchical: Tables contain Chains, and Chains contain ordered Rules. Packets enter the system and are processed by chains attached to specific hooks (like input, output, forward). Each chain has a policy (what to do if no rule matches) and a sequence of rules. Rules are evaluated in order. The first rule that matches a packet determines its fate (accept, drop, reject, or jump to another chain).

The most surprising thing about nftables’ performance is how it handles sets. Instead of iterating through individual rules for each IP address in a large list, you can define a set and match against the entire set in a single rule. This is dramatically more efficient. For instance, if you wanted to allow SSH from a whole subnet, 192.168.1.0/24, you could do:

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

This add set command creates a named set allowed_ssh_sources. The add element command populates it. The final add rule then uses this set. The system doesn’t check each IP in 192.168.1.0/24 individually; it performs a highly optimized lookup against the set.

The next concept you’ll encounter is the use of nftables expressions and maps for even more granular control and complex policy enforcement.

Want structured learning?

Take the full Nftables course →