nftables Linux packet filtering

Version 1.1.7 · source

Start here

nft list ruleset
Show everything
nft -a list ruleset
Show with rule handles
nft add table inet <table>
Create a table
nft add chain inet <table> <chain> '{ type filter hook input priority 0; policy drop; }'
Base chain
nft add rule inet <table> <chain> tcp dport 22 accept
Allow SSH
nft delete rule inet <table> <chain> handle <n>
Remove a rule
nft -f <file>
Load a rule file
nft flush ruleset
Remove all rules

Concepts

Table family: ip ip6 inet arp bridge netdev
Chain base (hook + priority) or regular
Rule matches then statements
  1. prerouting dnat, early filter
  2. input / forward to host / through
  3. output from host
  4. postrouting snat, masquerade

Inspect

nft list ruleset
Print all tables
nft list tables
List table names
nft list table <family> <table>
One table
nft list chain <family> <table> <chain>
One chain
nft -a list ruleset
Show rule handles
nft -n list ruleset
Numeric ports and addresses
nft -s list ruleset
Hide counters and stateful data
nft -j list ruleset
JSON output
Also nft -j -f <file> to load JSON. Handy for scripts with jq.
nft monitor
Watch ruleset changes live
Add 'trace' to watch packets marked with nftrace.
nft describe <expression>
Explain a match or type

Tables

nft add table inet <table>
Create a table
nft delete table inet <table>
Delete table with its content
nft flush table inet <table>
Remove all its rules
nft list tables <family>
Tables of one family

Chains

nft add chain inet <table> input '{ type filter hook input priority 0; policy drop; }'
Base chain on a hook
Hooks: prerouting, input, forward, output, postrouting. Policy defaults to accept.
nft add chain inet <table> <chain>
Regular chain (jump target)
nft delete chain inet <table> <chain>
Delete an empty chain
nft flush chain inet <table> <chain>
Remove all rules in chain
nft rename chain inet <table> <chain> <new>
Rename a chain
nft add chain inet <table> input '{ type filter hook input priority 0; policy accept; }'
Change the policy (redeclare)
jump <chain> / goto <chain>
Continue in another chain
jump returns after the chain ends; goto does not.

Rules

nft add rule inet <table> <chain> <match> <statement>
Append a rule
nft insert rule inet <table> <chain> <match> <statement>
Insert at top of chain
nft add rule inet <table> <chain> position <n> <rule>
Add after handle n
The position is a handle, not a line number. insert places before it.
nft delete rule inet <table> <chain> handle <n>
Delete a rule by handle
Get handles with nft -a list. Deleting by rule text is not supported.
nft replace rule inet <table> <chain> handle <n> <rule>
Replace a rule
nft add rule inet <table> input tcp dport 22 accept
Allow SSH
nft add rule inet <table> input tcp dport { 80, 443 } accept
Allow several ports
nft add rule inet <table> input ip saddr 10.0.0.0/8 drop
Block a network
nft add rule inet <table> input iifname lo accept
Allow loopback
nft add rule inet <table> input ct state established,related accept
Allow replies
nft add rule inet <table> input ct state invalid drop
Drop invalid packets
nft add rule inet <table> input icmp type echo-request accept
Allow ping

Matches

ip saddr / ip daddr <addr>
IPv4 source / destination
ip6 saddr / ip6 daddr <addr>
IPv6 source / destination
tcp dport / udp dport <port>
Destination port
tcp sport <port>
Source port
tcp dport 1000-2000
Port range
tcp flags syn
TCP flag set
iifname "eth0" / oifname "eth0"
Interface by name
Wildcard works: iifname "eth*". Use iif / oif for an interface that always exists.
meta l4proto tcp
Transport protocol
ct state new
Connection tracking state
<field> != <value>
Negate a match
<field> { a, b, c }
Match any of several values

Statements

accept / drop
Final verdict
reject
Drop and send an error
reject with tcp reset
Refuse with a TCP RST
counter
Count packets and bytes
log prefix "<text> "
Log to the kernel log
Put log before the verdict. Read with journalctl -k. Add level warn to pick the level.
limit rate 10/second
Match only below a rate
limit rate over 10/minute drop
Drop above a rate
return
Leave the current chain
meta nftrace set 1
Trace this packet
Watch with nft monitor trace.

Sets and maps

nft add set inet <table> <set> '{ type ipv4_addr; }'
Create a named set
Types: ipv4_addr, ipv6_addr, ether_addr, inet_service, inet_proto, mark, ifname.
nft add set inet <table> <set> '{ type ipv4_addr; flags interval; }'
Set that holds ranges/CIDRs
nft add set inet <table> <set> '{ type ipv4_addr; flags timeout; timeout 1h; }'
Set with expiring entries
nft add element inet <table> <set> { 1.2.3.4, 5.6.7.8 }
Add elements
nft delete element inet <table> <set> { 1.2.3.4 }
Remove an element
nft add rule inet <table> input ip saddr @<set> drop
Use a set in a rule
nft list set inet <table> <set>
Show a set
nft get element inet <table> <set> { 1.2.3.4 }
Is it in the set?
nft add map ip nat <map> '{ type inet_service : ipv4_addr; }'
Create a map
tcp dport vmap { 22 : accept, 23 : drop }
Verdict map in a rule
dnat to tcp dport map { 80 : 10.0.0.5, 8080 : 10.0.0.6 }
Port to host map for DNAT

NAT

nft add table ip nat
Table for NAT
nft add chain ip nat postrouting '{ type nat hook postrouting priority 100; }'
Source NAT chain
nft add chain ip nat prerouting '{ type nat hook prerouting priority -100; }'
Destination NAT chain
nft add rule ip nat postrouting oifname "eth0" masquerade
Share an uplink
Needs IP forwarding: sysctl net.ipv4.ip_forward=1. Masquerade suits dynamic addresses.
nft add rule ip nat postrouting ip saddr 192.168.1.0/24 snat to <ip>
Source NAT to fixed IP
nft add rule ip nat prerouting tcp dport 8080 dnat to <ip>:80
Port forward to a host
Also accept the traffic in the forward chain if its policy is drop.
nft add rule ip nat prerouting tcp dport 80 redirect to :8080
Redirect to a local port
nft add rule inet nat prerouting dnat ip to <ip>
DNAT in the inet family
In inet chains name the family: dnat ip to ... or dnat ip6 to ...

Save and restore

nft list ruleset > <file>
Save the ruleset
nft -f <file>
Load a rule file
The file is applied atomically: all of it or nothing.
nft -c -f <file>
Check a file without applying
nft flush ruleset
Remove everything
systemctl enable --now nftables
Load /etc/nftables.conf at boot
Path and service name are distro conventions (Debian, Fedora, Arch); check yours.
define <name> = <value>
Variable in a rule file
include "<path>"
Include another file

Recipes

Minimal host firewall (/etc/nftables.conf)
#!/usr/sbin/nft -fflush rulesettable inet filter {  chain input {    type filter hook input priority 0; policy drop;    ct state established,related accept    ct state invalid drop    iifname lo accept    icmp type echo-request accept    tcp dport { 22, 80, 443 } accept  }  chain forward { type filter hook forward priority 0; policy drop; }  chain output { type filter hook output priority 0; policy accept; }}
Block an IP dynamically with a set
nft add set inet filter blocked '{ type ipv4_addr; flags timeout; }'nft insert rule inet filter input ip saddr @blocked dropnft add element inet filter blocked '{ 203.0.113.9 timeout 1h }'
Router with masquerade and port forward
sysctl -w net.ipv4.ip_forward=1nft add table ip natnft 'add chain ip nat prerouting { type nat hook prerouting priority -100; }'nft 'add chain ip nat postrouting { type nat hook postrouting priority 100; }'nft add rule ip nat postrouting oifname "eth0" masqueradenft add rule ip nat prerouting iifname "eth0" tcp dport 8080 dnat to 192.168.1.10:80
Safe edit: check, then apply
nft list ruleset > /root/rules.baknft -c -f new.nft && nft -f new.nft# roll backnft flush ruleset; nft -f /root/rules.bak