nftables is actually a userspace daemon that hooks into the kernel’s netfilter framework, but it’s the nft command-line tool that manipulates the rulesets and is what most people interact with.

Here’s how you can use nftables to filter traffic that’s being routed through your system, effectively turning it into a firewall or router.

Let’s say you have a server with two network interfaces: eth0 facing your internal network and eth1 facing the internet. You want to allow internal clients to access the internet, but block them from accessing a specific internal server on eth0 from the internet (which is obviously a bad idea, but illustrates the point).

First, you need to enable IP forwarding in your kernel. This is crucial because without it, your server won’t pass packets between interfaces at all.

sysctl net.ipv4.ip_forward=1

This command tells the kernel to allow packets to be forwarded from one network interface to another. You’ll want to make this permanent by editing /etc/sysctl.conf and uncommenting or adding the line:

net.ipv4.ip_forward = 1

Now, let’s set up nftables. We’ll create a table and a chain specifically for handling routed traffic. The forward chain is designed precisely for this purpose: packets that are destined for a different IP address than the one on the incoming interface, and are not for the local machine, go through the forward chain.

Here’s a basic nftables ruleset:

nft add table ip filter
nft add chain ip filter forward { type filter hook forward priority 0 \; policy accept \; }
nft add rule ip filter forward ip saddr 192.168.1.0/24 ip daddr 8.8.8.8 accept
nft add rule ip filter forward ip saddr 192.168.1.0/24 ct state established,related accept
nft add rule ip filter forward ip saddr 192.168.1.0/24 ip daddr 192.168.1.100 drop
nft add rule ip filter forward drop

Let’s break this down.

nft add table ip filter: This creates a new table named filter for IPv4 traffic. Tables are containers for chains.

nft add chain ip filter forward { type filter hook forward priority 0 \; policy accept \; }: This creates a forward chain within the filter table.

  • type filter: This chain is for filtering packets.
  • hook forward: This chain will be attached to the forward hook point in the netfilter framework, meaning it processes packets that are being routed.
  • priority 0: This determines the order in which chains are processed if multiple chains are attached to the same hook. Lower numbers have higher priority.
  • policy accept: This is the default action for the chain. If no rule matches a packet, it will be accepted. We’ll add specific drop rules later to override this for unwanted traffic.

nft add rule ip filter forward ip saddr 192.168.1.0/24 ip daddr 8.8.8.8 accept: This rule allows traffic originating from your internal network (192.168.1.0/24) to reach Google’s DNS server (8.8.8.8). This is a common example of allowing outbound internet access.

nft add rule ip filter forward ip saddr 192.168.1.0/24 ct state established,related accept: This is a crucial rule for stateful firewalling. ct state established,related allows return traffic for connections that were initiated from your internal network. Without this, your internal clients wouldn’t be able to receive responses from the internet. ct refers to the connection tracking module.

nft add rule ip filter forward ip saddr 192.168.1.0/24 ip daddr 192.168.1.100 drop: This rule explicitly blocks traffic originating from your internal network (192.168.1.0/24) destined for a specific internal server (192.168.1.100). This is the specific restriction we wanted to implement.

nft add rule ip filter forward drop: This is a catch-all rule. Because the default policy is accept, any traffic that reaches this point and hasn’t been explicitly accepted by a previous rule will be dropped. This ensures that only traffic you’ve explicitly allowed gets through.

The order of rules matters significantly. Established and related connections are typically allowed first. Then, specific desired outbound traffic is permitted. Finally, any specific unwanted traffic is dropped, and then a general drop rule catches everything else.

The most surprising true thing about nftables is how its rule processing is not strictly sequential in the way one might initially assume when seeing a list of add rule commands. While the commands are executed in the order you provide them to build the chain, within the kernel, nftables uses a more optimized, almost bytecode-like representation. When packets arrive, the kernel traverses this compiled ruleset, and the traversal itself has a specific order. If a packet matches a rule, the verdict of that rule (accept, drop, reject, or jump to another chain) is applied immediately, and processing for that packet stops within that chain. This is why placing a broad drop rule too early can prevent subsequent, more specific accept rules from ever being evaluated.

To make these rules persistent across reboots, you’ll need to save your nftables configuration. The exact method depends on your Linux distribution, but commonly you would use:

nft list ruleset > /etc/nftables.conf

Then, ensure the nftables service is enabled to load this configuration on boot:

systemctl enable nftables.service

The next concept you’ll likely encounter is handling Network Address Translation (NAT) for outbound traffic, which is often configured using nftablesnat table and prerouting/postrouting chains.

Want structured learning?

Take the full Nftables course →