nftables
Modern packet filter — tables, chains, sets, and rule management replacing iptables.
Ruleset Inspection
Show the entire ruleset — the truth about what nftables is doing
sudo nft list ruleset
List all tables
sudo nft list tables
List chains in a specific table
sudo nft list table inet filter
Show a specific chain with handle numbers — needed for deletion
sudo nft -a list chain inet filter input
Export ruleset as JSON — useful for programmatic manipulation
sudo nft -j list ruleset
Table and Chain Management
Create a table — inet family handles both IPv4 and IPv6
sudo nft add table inet filter
Create an input chain with default drop policy
sudo nft add chain inet filter input '{ type filter hook input priority 0; policy drop; }'
Create a forward chain
sudo nft add chain inet filter forward '{ type filter hook forward priority 0; policy drop; }'
Create an output chain — typically accept
sudo nft add chain inet filter output '{ type filter hook output priority 0; policy accept; }'
Delete a table — removes all chains and rules inside it
sudo nft delete table inet filter
Flush all rules from a chain — keeps the chain, removes rules
sudo nft flush chain inet filter input
Flush the entire ruleset — clean slate
sudo nft flush ruleset
Rule Management
Add a rule — allow SSH from trusted subnet
sudo nft add rule inet filter input tcp dport 22 ip saddr 10.50.0.0/16 accept
Insert a rule at the beginning of a chain — processed first
sudo nft insert rule inet filter input ct state established,related accept
Delete a rule by handle — get the handle from
nft -a listsudo nft delete rule inet filter input handle 15
Add a rule with a comment — self-documenting firewall
sudo nft add rule inet filter input tcp dport 443 accept comment \"allow HTTPS\"
Complete Stateful Firewall
Production-ready nftables firewall — the baseline
sudo nft flush ruleset
sudo nft -f - <<'EOF'
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
# Loopback
iif "lo" accept
# Connection tracking
ct state established,related accept
ct state invalid drop
# ICMP and ICMPv6
ip protocol icmp accept
ip6 nexthdr icmpv6 accept
# SSH from trusted
tcp dport 22 ip saddr 10.50.0.0/16 accept
}
chain forward {
type filter hook forward priority 0; policy drop;
}
chain output {
type filter hook output priority 0; policy accept;
}
}
EOF
Sets
Create a named set — group ports or addresses for cleaner rules
sudo nft add set inet filter allowed_ports '{ type inet_service; }'
Add elements to the set
sudo nft add element inet filter allowed_ports '{ 22, 80, 443 }'
Use the set in a rule — matches any element
sudo nft add rule inet filter input tcp dport @allowed_ports accept
Anonymous set — inline, no need to declare separately
sudo nft add rule inet filter input tcp dport { 22, 80, 443 } accept
IP address set — whitelist trusted sources
sudo nft add set inet filter trusted_hosts '{ type ipv4_addr; }'
sudo nft add element inet filter trusted_hosts '{ 10.50.1.20, 10.50.1.50, 10.50.1.60 }'
sudo nft add rule inet filter input ip saddr @trusted_hosts accept
Interval set — match entire subnets
sudo nft add set inet filter internal_nets '{ type ipv4_addr; flags interval; }'
sudo nft add element inet filter internal_nets '{ 10.50.0.0/16, 172.16.0.0/12 }'
Maps
Create a verdict map — different action per port
sudo nft add map inet filter port_policy '{ type inet_service : verdict; }'
sudo nft add element inet filter port_policy '{ 22 : accept, 80 : accept, 23 : drop }'
sudo nft add rule inet filter input tcp dport vmap @port_policy
Inline verdict map — compact syntax for simple cases
sudo nft add rule inet filter input tcp dport vmap { 22 : accept, 80 : accept, 443 : accept }
NAT
Create a NAT table and chains
sudo nft add table ip nat
sudo nft add chain ip nat prerouting '{ type nat hook prerouting priority -100; }'
sudo nft add chain ip nat postrouting '{ type nat hook postrouting priority 100; }'
Masquerade — dynamic SNAT for outbound (router pattern)
sudo nft add rule ip nat postrouting oifname "eth0" masquerade
SNAT — static source NAT
sudo nft add rule ip nat postrouting oifname "eth0" snat to 192.168.1.100
DNAT — port forwarding inbound traffic
sudo nft add rule ip nat prerouting tcp dport 8443 dnat to 10.50.1.20:443
Redirect — local port redirect (transparent proxy pattern)
sudo nft add rule ip nat prerouting tcp dport 80 redirect to :3128
Rate Limiting
Rate limit SSH connections — 3 per minute per source
sudo nft add rule inet filter input tcp dport 22 ct state new limit rate 3/minute accept
Rate limit with burst — allow short bursts before throttling
sudo nft add rule inet filter input tcp dport 22 ct state new limit rate 3/minute burst 5 packets accept
Per-source rate limiting with meters — more granular than global limit
sudo nft add rule inet filter input tcp dport 22 ct state new meter ssh-rate '{ ip saddr limit rate 3/minute }' accept
Logging
Log dropped packets with a prefix — appears in journalctl
sudo nft add rule inet filter input log prefix \"nft-dropped: \" counter drop
Log with rate limiting — prevent log floods
sudo nft add rule inet filter input limit rate 5/minute log prefix \"nft-dropped: \" counter drop
Log specific traffic without dropping — audit rule
sudo nft insert rule inet filter input tcp dport 22 log prefix \"nft-ssh: \" counter
Watch nftables log entries
journalctl -k -f | grep "nft-"
Counters
Add a named counter — track packets/bytes for specific rules
sudo nft add counter inet filter ssh_counter
sudo nft add rule inet filter input tcp dport 22 counter name ssh_counter accept
Show counter values
sudo nft list counter inet filter ssh_counter
Reset counters on all rules
sudo nft reset counters
Persistence
Save ruleset to config file — loaded at boot by nftables.service
sudo nft list ruleset > /etc/nftables.conf
Load from config file
sudo nft -f /etc/nftables.conf
Enable nftables service for boot persistence
sudo systemctl enable nftables
Validate config file syntax before applying
sudo nft -c -f /etc/nftables.conf
Migrating from iptables
Translate iptables rules to nftables — built-in migration tool
iptables-save | iptables-restore-translate -f /dev/stdin
Translate a single iptables rule
iptables-translate -A INPUT -p tcp --dport 22 -j ACCEPT
Key differences from iptables
iptables -A INPUT → nft add rule inet filter input
iptables -I INPUT 1 → nft insert rule inet filter input
iptables -D INPUT 3 → nft delete rule inet filter input handle N
iptables -F → nft flush ruleset
iptables -L -n -v → nft list ruleset
iptables -t nat → separate table ip nat (or inet with newer kernels)
-m conntrack --ctstate → ct state
-m multiport --dports → tcp dport { 22, 80, 443 }
-j LOG --log-prefix → log prefix "..."