Skip to main content
RunBook Academy

LinuxXXV · Firewallsnftables

nftables architecture - tables, chains, and rules

Intermediate⏱ ~14 minnft

What you'll learn

  • Describe nftables tables, chains, hooks, and rules
  • Write a basic ruleset
  • Combine IPv4 and IPv6 rules in inet tables
  • Persist the ruleset across reboots

Prerequisites

Verified against Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL 9.x · Rocky Linux 9.x · AlmaLinux 9.x · Linux kernel 6.1 LTS / 6.6 LTS · systemd 255+ · OpenSSH 8.7p1 (RHEL 9) / 9.6p1 (Ubuntu 24.04) · nftables 1.0.x · chrony 4.x · Pacemaker 2.1.x · Corosync 3.1.x · 2026-08-09

Not yet marked complete on this device.

nftables replaces iptables as the modern Linux firewall. It uses a cleaner syntax, combines IPv4 and IPv6 in one table, and is faster. This lesson covers the structure.

The hierarchy

Family (ip, ip6, inet, bridge, netdev, arp)
  Table (filter, nat, mangle, raw, security)
    Chain (a sequence of rules with a hook and a priority)
      Rule (a match expression and a verdict)
  • Family: inet combines IPv4 and IPv6 - usually what you want.
  • Table: filter is the standard for accept/drop decisions.
  • Chain: a list of rules. Built-in chains attach to netfilter hooks.
  • Rule: matches packets and applies a verdict (accept, drop, reject, queue, return).

The basic commands

nft list ruleset                # show everything
nft list tables                 # show tables only
nft list table inet filter      # show one table
nft list chain inet filter input # show one chain
nft flush ruleset               # delete everything
nft delete table inet filter    # delete one table

A minimal ruleset

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

# Allow established/related
nft add rule inet filter input ct state established,related accept

# Allow loopback
nft add rule inet filter input iif lo accept

# Allow SSH from management network
nft add rule inet filter input ip saddr 10.0.0.0/24 tcp dport 22 accept

# Allow ICMP from monitoring
nft add rule inet filter input ip saddr 10.0.0.0/24 ip protocol icmp accept

# Drop invalid
nft add rule inet filter input ct state invalid drop

# Log what is about to hit the chain's drop policy.
# `add` appends, so this rule runs last - after every accept.
# `limit rate` is mandatory: an unlimited log rule is a remote
# disk-fill vector.
nft add rule inet filter input limit rate 5/minute burst 10 packets \
  log prefix "nft-drop: " level warn

The default policy is drop on INPUT and FORWARD; OUTPUT is accept because the host must make outbound connections.

Rule syntax

nft add rule <table> <chain> <matches> <verdict>

Common matches:

MatchMeaning
iifname "<iface>"Incoming interface by name, matched per packet — use this
oifname "<iface>"Outgoing interface by name, matched per packet — use this
iif <iface>Incoming interface by index, resolved once at load time
oif <iface>Outgoing interface by index, resolved once at load time
ip saddr <addr>Source IP (or CIDR)
ip daddr <addr>Destination IP
ip protocol <proto>IP protocol (tcp, udp, icmp)
tcp dport <port>TCP destination port
udp dport <port>UDP destination port
ct state <state>Connection tracking state
tcp flags <flags>TCP flags

Statements come in two kinds, and the difference decides whether the rest of the chain still runs. Confusing them is how a ruleset ends up with rules below a counter that the author believed were unreachable, or above an accept that the author believed still got evaluated.

Verdicts (terminating — evaluation of the chain stops):

VerdictMeaning
acceptAllow the packet, leave the chain
dropSilently discard
rejectDiscard and send ICMP unreachable (or TCP RST)
queueHand the packet to a userspace program
returnLeave this chain, resume in the caller
jump / gotoTransfer to another chain

Non-terminating statements (the packet carries on to the next rule):

StatementMeaning
counterCount packets and bytes, decide nothing
log prefix "..." level warnLog and continue
limit rate ...Match only within a rate, usually paired with a verdict
meta mark set 0x1Set the packet mark, used by policy routing
ct helper set "ftp-standard"Assign a conntrack helper

NAT statements — snat, dnat, masquerade, redirect — are a third category. They are valid nftables, not iptables leftovers, and they belong in a nat-type chain. redirect is a special form of dnat that always translates to the local host’s address, so it is only valid in prerouting and output.

Conntrack (stateful) matches

nft add rule inet filter input ct state established,related accept
nft add rule inet filter input ct state invalid drop
nft add rule inet filter input ct state new tcp dport 22 accept

States: new, established, related, invalid. Most rules allow established,related first (so response traffic flows), then block invalid, then allow specific new flows.

Sets for many addresses

If you have many addresses to allow (e.g. an IP allowlist):

nft add set inet filter allowed_nets { type ipv4_addr \; flags interval \; }
nft add element inet filter allowed_nets { 10.0.0.0/24, 192.168.1.0/24 }

nft add rule inet filter input ip saddr @allowed_nets accept

Sets are efficient (interval matching) and make rules maintainable.

Persist across reboots

nftables rules are kernel state. They do not survive a reboot. Dump the live ruleset into /etc/nftables.conf, but prepend flush ruleset so the file is self-contained:

# Write a loadable, idempotent ruleset file
{ printf '#!/usr/sbin/nft -f\n\nflush ruleset\n\n'; sudo nft list ruleset; } \
  | sudo tee /etc/nftables.conf
sudo chmod 0750 /etc/nftables.conf
sudo nft -c -f /etc/nftables.conf     # verify before enabling

# On boot, systemd runs nftables.service which loads the file
sudo systemctl enable nftables
sudo systemctl start nftables

The default /etc/nftables.conf on most distros has a placeholder ruleset; replace it with your own.

Atomicity

nftables can apply a ruleset atomically using a file:

sudo nft -c -f /etc/nftables.conf     # check syntax, change nothing
sudo nft -f /etc/nftables.conf        # atomic, but ADDITIVE - it only
                                      # replaces the ruleset if the file
                                      # starts with 'flush ruleset'

Atomic and replacing are two different guarantees. Atomic means the whole file is committed in one transaction: no window where half the rules are live. It does not mean the previous ruleset is discarded. Discarding is what flush ruleset at the top of the file does, and it is inside the same transaction, so the flush-and-load pair is still atomic - the host is never left unfirewalled.

For remote changes, write the file first, validate syntax, then load.

Knowledge check

Knowledge check · 5 questions

  1. Q1. Which nftables family combines IPv4 and IPv6 rules?

  2. Q2. nftables rules survive a reboot.

  3. Q3. Which of the following are terminating nftables verdicts - statements that stop evaluation of the chain? Select all that apply.

  4. Q4. A host persists its firewall with `nft list ruleset > /etc/nftables.conf`. After three change-and-reload cycles the input chain has 18 rules where the operator expects 6, and deleting an allow rule does not close the port. What happened?

  5. Q5. Why must `nft insert rule ... log prefix "nft-drop: "` not be used to log dropped packets?

Passing score: 75%. Answers are checked in this browser.