nftables
Quick Reference
# List all rules
nft list ruleset
# List specific table
nft list table inet filter
# Add a table
nft add table inet filter
# Add a chain
nft add chain inet filter input { type filter hook input priority 0 \; policy accept \; }
# Add a rule
nft add rule inet filter input tcp dport 22 accept
# Delete a rule by handle
nft delete rule inet filter input handle 10
# Save ruleset
nft list ruleset > /etc/nftables.conf
# Load ruleset
nft -f /etc/nftables.conf
# Flush all rules
nft flush ruleset
Understanding nftables
nftables vs iptables
| Feature | iptables | nftables |
|---|---|---|
Tables |
Predefined (filter, nat, mangle, raw) |
User-defined, flexible |
Syntax |
Multiple commands (iptables, ip6tables, ebtables, arptables) |
Single unified command (nft) |
Address families |
Separate tools per family |
Single tool handles all |
Rules storage |
Individual rules |
Rulesets with transactions |
Performance |
Linear rule traversal |
Optimized with sets and maps |
Atomicity |
No atomic updates |
Full atomic ruleset replacement |
Debugging |
iptables -v counters |
Built-in tracing |
Architecture
┌─────────────────────────────────────────┐
│ nftables │
└─────────────────────────────────────────┘
│
┌───────────────────────────────┼───────────────────────────────┐
│ │ │
┌──────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐
│ Table: ip │ │ Table: inet │ │ Table: ip6 │
│ (IPv4) │ │ (IPv4+IPv6) │ │ (IPv6) │
└─────────────┘ └─────────────┘ └─────────────┘
│ │
┌──────▼──────┐ ┌──────▼──────┐
│ Chains │ │ Chains │
│ - input │ │ - input │
│ - forward │ │ - forward │
│ - output │ │ - output │
└─────────────┘ └─────────────┘
│ │
┌──────▼──────┐ ┌──────▼──────┐
│ Rules │ │ Rules │
│ - match │ │ Sets │
│ - action │ │ Maps │
└─────────────┘ └─────────────┘
Address Families
| Family | Description |
|---|---|
ip |
IPv4 packets only |
ip6 |
IPv6 packets only |
inet |
Both IPv4 and IPv6 (recommended) |
arp |
ARP packets |
bridge |
Bridge/layer 2 packets |
netdev |
Ingress hook on network device (early filtering) |
Packet Flow
Network
│
┌────────▼────────┐
│ prerouting │
│ (raw, nat) │
└────────┬────────┘
│
┌────────▼────────┐
│ Routing │
│ Decision │
└────────┬────────┘
┌────────────────┴────────────────┐
│ │
┌────────▼────────┐ ┌────────▼────────┐
│ input │ │ forward │
│ (filter) │ │ (filter) │
└────────┬────────┘ └────────┬────────┘
│ │
┌────────▼────────┐ │
│ Local Process │ │
└────────┬────────┘ │
│ │
┌────────▼────────┐ │
│ output │ │
│ (filter) │ │
└────────┬────────┘ │
│ │
└────────────────┬────────────────┘
│
┌────────▼────────┐
│ postrouting │
│ (nat) │
└────────┬────────┘
│
▼
Network
Basic Configuration
Tables
# Create a table
nft add table inet filter
nft add table ip nat
nft add table ip6 myfilter
# List tables
nft list tables
# Delete a table (must be empty)
nft delete table inet filter
# Flush a table (delete all chains and rules)
nft flush table inet filter
Chains
# Base chain (hooked into network stack)
nft add chain inet filter input { type filter hook input priority 0 \; policy accept \; }
# Chain types:
# filter - for packet filtering
# nat - for address translation
# route - for rerouting packets
# Hook points (filter):
# prerouting - before routing decision
# input - for local delivery
# forward - for forwarded packets
# output - for locally generated
# postrouting - after routing
# Priority (lower = earlier processing):
# raw: -300
# mangle: -150
# dstnat: -100
# filter: 0
# security: 50
# srcnat: 100
# Example chains
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 \; }
# Regular chain (no hook, called from other chains)
nft add chain inet filter tcp_chain
# Delete a chain
nft delete chain inet filter tcp_chain
# Flush a chain (delete all rules)
nft flush chain inet filter input
Rules
# Add rule (appends to chain)
nft add rule inet filter input tcp dport 22 accept
# Insert rule (prepends to chain)
nft insert rule inet filter input tcp dport 80 accept
# Add rule at specific position
nft add rule inet filter input position 5 tcp dport 443 accept
# Add rule with comment
nft add rule inet filter input tcp dport 22 accept comment \"SSH access\"
# List rules with handles
nft -a list chain inet filter input
# Delete rule by handle
nft delete rule inet filter input handle 10
# Replace rule
nft replace rule inet filter input handle 10 tcp dport 2222 accept
Rule Syntax
Matching Expressions
# Interface matching
nft add rule inet filter input iifname "eth0" accept
nft add rule inet filter output oifname "eth1" accept
nft add rule inet filter input iifname != "lo" drop
# Protocol matching
nft add rule inet filter input ip protocol tcp accept
nft add rule inet filter input ip6 nexthdr icmpv6 accept
nft add rule inet filter input meta l4proto { tcp, udp } accept
# Address matching
nft add rule inet filter input ip saddr 192.168.1.0/24 accept
nft add rule inet filter input ip daddr 10.0.0.1 drop
nft add rule inet filter input ip6 saddr 2001:db8::/32 accept
# Port matching
nft add rule inet filter input tcp dport 22 accept
nft add rule inet filter input tcp dport { 80, 443 } accept
nft add rule inet filter input tcp dport 1024-65535 accept
nft add rule inet filter input tcp sport != 80 drop
# Multiple conditions
nft add rule inet filter input ip saddr 192.168.1.0/24 tcp dport 22 accept
# Connection tracking
nft add rule inet filter input ct state established,related accept
nft add rule inet filter input ct state new tcp dport 22 accept
nft add rule inet filter input ct state invalid drop
Actions (Verdicts)
| Action | Description |
|---|---|
accept |
Accept the packet |
drop |
Silently drop the packet |
reject |
Drop with ICMP error reply |
queue |
Queue to userspace (NFQUEUE) |
continue |
Continue to next rule |
return |
Return from current chain |
jump |
Jump to another chain |
goto |
Go to another chain (no return) |
log |
Log the packet |
# Basic actions
nft add rule inet filter input tcp dport 22 accept
nft add rule inet filter input tcp dport 23 drop
# Reject with specific ICMP type
nft add rule inet filter input reject
nft add rule inet filter input reject with icmp type host-unreachable
nft add rule inet filter input reject with icmpv6 type no-route
nft add rule inet filter input reject with tcp reset
# Jump to another chain
nft add rule inet filter input tcp dport { 80, 443 } jump web_traffic
# Logging
nft add rule inet filter input tcp dport 22 log prefix \"SSH: \" accept
nft add rule inet filter input log flags all counter drop
Connection Tracking States
| State | Description |
|---|---|
new |
First packet of a connection |
established |
Part of established connection |
related |
Related to existing connection (e.g., ICMP error, FTP data) |
invalid |
Packet doesn’t belong to any known connection |
untracked |
Packet explicitly untracked |
# Stateful firewall rules
nft add rule inet filter input ct state invalid drop
nft add rule inet filter input ct state established,related accept
nft add rule inet filter input ct state new tcp dport 22 accept
nft add rule inet filter input ct state new tcp dport { 80, 443 } accept
Sets and Maps
Named Sets
# Create a set
nft add set inet filter allowed_ips { type ipv4_addr \; }
nft add set inet filter allowed_ports { type inet_service \; }
# Add elements to set
nft add element inet filter allowed_ips { 192.168.1.1, 192.168.1.2 }
nft add element inet filter allowed_ports { 22, 80, 443 }
# Use set in rule
nft add rule inet filter input ip saddr @allowed_ips accept
nft add rule inet filter input tcp dport @allowed_ports accept
# Delete element
nft delete element inet filter allowed_ips { 192.168.1.2 }
# Flush set
nft flush set inet filter allowed_ips
# List set
nft list set inet filter allowed_ips
Set Types
| Type | Description |
|---|---|
ipv4_addr |
IPv4 addresses |
ipv6_addr |
IPv6 addresses |
ether_addr |
MAC addresses |
inet_proto |
IP protocols |
inet_service |
TCP/UDP ports |
mark |
Packet marks |
ifname |
Interface names |
Set Flags
# Interval set (ranges)
nft add set inet filter port_ranges { type inet_service \; flags interval \; }
nft add element inet filter port_ranges { 1024-65535 }
# Timeout set (elements expire)
nft add set inet filter recent_connections { type ipv4_addr \; timeout 1h \; }
# Dynamic set (can be updated from rules)
nft add set inet filter blocked_ips { type ipv4_addr \; flags dynamic,timeout \; timeout 24h \; }
# Counter set
nft add set inet filter counted_ips { type ipv4_addr \; flags counter \; }
Anonymous Sets
# Inline sets (no explicit creation)
nft add rule inet filter input tcp dport { 22, 80, 443, 8080 } accept
nft add rule inet filter input ip saddr { 192.168.1.0/24, 10.0.0.0/8 } accept
Maps
# Create a map
nft add map inet filter port_mark { type inet_service : mark \; }
nft add element inet filter port_mark { 22 : 1, 80 : 2, 443 : 3 }
# Verdict map
nft add map inet filter port_verdict { type inet_service : verdict \; }
nft add element inet filter port_verdict { 22 : accept, 23 : drop, 80 : accept }
# Use map in rule
nft add rule inet filter input tcp dport vmap @port_verdict
nft add rule inet filter input meta mark set tcp dport map @port_mark
NAT Configuration
Source NAT (Masquerading)
# Create NAT table
nft add table ip nat
# Create postrouting chain
nft add chain ip nat postrouting { type nat hook postrouting priority 100 \; }
# Masquerade (dynamic SNAT)
nft add rule ip nat postrouting oifname "eth0" masquerade
# Static SNAT
nft add rule ip nat postrouting oifname "eth0" snat to 203.0.113.1
# SNAT with port range
nft add rule ip nat postrouting oifname "eth0" snat to 203.0.113.1:1024-65535
Destination NAT (Port Forwarding)
# Create prerouting chain
nft add chain ip nat prerouting { type nat hook prerouting priority -100 \; }
# Port forward
nft add rule ip nat prerouting iifname "eth0" tcp dport 80 dnat to 192.168.1.100
# Port forward with port change
nft add rule ip nat prerouting iifname "eth0" tcp dport 8080 dnat to 192.168.1.100:80
# DNAT range
nft add rule ip nat prerouting tcp dport 3000-3010 dnat to 192.168.1.100
# Redirect (local port forward)
nft add rule ip nat prerouting tcp dport 80 redirect to :8080
Dual-Stack NAT (IPv4 + IPv6)
# Use inet family for both
nft add table inet nat
nft add chain inet nat prerouting { type nat hook prerouting priority -100 \; }
nft add chain inet nat postrouting { type nat hook postrouting priority 100 \; }
# IPv4 masquerade
nft add rule inet nat postrouting ip saddr 192.168.1.0/24 oifname "eth0" masquerade
# IPv6 (usually not NAT, but possible)
nft add rule inet nat postrouting ip6 saddr fd00::/64 oifname "eth0" masquerade
Rate Limiting
Basic Rate Limiting
# Limit connections per second
nft add rule inet filter input tcp dport 22 limit rate 10/second accept
# Limit with burst
nft add rule inet filter input tcp dport 22 limit rate 10/second burst 20 packets accept
# Rate units: second, minute, hour, day
nft add rule inet filter input tcp dport 25 limit rate 100/minute accept
# Byte-based limiting
nft add rule inet filter input limit rate 10 mbytes/second accept
Per-Source Rate Limiting
# Create meter for tracking
nft add rule inet filter input tcp dport 22 \
meter ssh_ratelimit { ip saddr limit rate 3/minute } accept
# With timeout
nft add rule inet filter input tcp dport 80 \
meter http_ratelimit { ip saddr timeout 1h limit rate 100/minute } accept
# Per-source connection limit
nft add rule inet filter input tcp dport 22 ct state new \
meter ssh_connlimit { ip saddr ct count over 3 } drop
Logging and Monitoring
Logging Rules
# Basic logging
nft add rule inet filter input log prefix \"INPUT: \" drop
# Log with flags
nft add rule inet filter input log flags all prefix \"TRACE: \" drop
# Flags: tcp sequence, tcp options, ip options, skuid, ether, all
# Log level
nft add rule inet filter input log level info prefix \"INFO: \"
nft add rule inet filter input log level warn prefix \"WARN: \"
# Levels: emerg, alert, crit, err, warn, notice, info, debug
# Log specific traffic
nft add rule inet filter input tcp dport 22 ct state new log prefix \"SSH: \" accept
Counters
# Named counter
nft add counter inet filter http_counter
nft add rule inet filter input tcp dport 80 counter name http_counter accept
# Inline counter
nft add rule inet filter input tcp dport 443 counter accept
# View counters
nft list chain inet filter input
# Reset counter
nft reset counter inet filter http_counter
Tracing
# Enable tracing for specific traffic
nft add rule inet filter prerouting meta nftrace set 1
# View trace (requires nft monitor)
nft monitor trace
# Enable in raw table for full path
nft add table inet raw
nft add chain inet raw prerouting { type filter hook prerouting priority -300 \; }
nft add rule inet raw prerouting tcp dport 22 meta nftrace set 1
Complete Ruleset Examples
Basic Workstation Firewall
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
# Accept loopback
iifname "lo" accept
# Accept established/related connections
ct state established,related accept
# Drop invalid
ct state invalid drop
# Accept ICMP
ip protocol icmp accept
ip6 nexthdr icmpv6 accept
# Accept SSH from local network
ip saddr 192.168.1.0/24 tcp dport 22 accept
# Log dropped packets
log prefix "DROPPED: " flags all
}
chain forward {
type filter hook forward priority 0; policy drop;
}
chain output {
type filter hook output priority 0; policy accept;
}
}
Server with Services
#!/usr/sbin/nft -f
flush ruleset
# Define variables
define lan_if = "eth0"
define wan_if = "eth1"
define lan_net = 192.168.1.0/24
define admin_ips = { 192.168.1.10, 192.168.1.11 }
table inet filter {
# Allowed services
set allowed_tcp_ports {
type inet_service
elements = { 22, 80, 443 }
}
chain input {
type filter hook input priority 0; policy drop;
# Loopback
iifname "lo" accept
# Established connections
ct state established,related accept
ct state invalid drop
# ICMP/ICMPv6
ip protocol icmp icmp type { echo-request, echo-reply } accept
ip6 nexthdr icmpv6 accept
# SSH from admin IPs only
ip saddr $admin_ips tcp dport 22 accept
# Web services from anywhere
tcp dport { 80, 443 } accept
# Rate limit new connections
tcp flags syn limit rate 100/second burst 150 accept
# Log and drop
counter log prefix "INPUT DROP: "
}
chain forward {
type filter hook forward priority 0; policy drop;
}
chain output {
type filter hook output priority 0; policy accept;
}
}
NAT Router/Gateway
#!/usr/sbin/nft -f
flush ruleset
define wan_if = "eth0"
define lan_if = "eth1"
define lan_net = 192.168.1.0/24
define web_server = 192.168.1.100
define ssh_server = 192.168.1.50
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
iifname "lo" accept
ct state established,related accept
ct state invalid drop
# Allow ICMP
ip protocol icmp accept
ip6 nexthdr icmpv6 accept
# Allow SSH from LAN
iifname $lan_if tcp dport 22 accept
# Allow DHCP from LAN
iifname $lan_if udp dport { 67, 68 } accept
# Allow DNS from LAN
iifname $lan_if udp dport 53 accept
iifname $lan_if tcp dport 53 accept
log prefix "INPUT DROP: "
}
chain forward {
type filter hook forward priority 0; policy drop;
ct state established,related accept
ct state invalid drop
# Allow LAN to WAN
iifname $lan_if oifname $wan_if accept
# Allow port forwards from WAN
iifname $wan_if oifname $lan_if ip daddr $web_server tcp dport { 80, 443 } accept
iifname $wan_if oifname $lan_if ip daddr $ssh_server tcp dport 22 accept
log prefix "FORWARD DROP: "
}
chain output {
type filter hook output priority 0; policy accept;
}
}
table ip nat {
chain prerouting {
type nat hook prerouting priority -100;
# Port forwards
iifname $wan_if tcp dport 80 dnat to $web_server
iifname $wan_if tcp dport 443 dnat to $web_server
iifname $wan_if tcp dport 2222 dnat to $ssh_server:22
}
chain postrouting {
type nat hook postrouting priority 100;
# Masquerade outgoing traffic
oifname $wan_if masquerade
}
}
Docker Host Firewall
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
iifname "lo" accept
ct state established,related accept
ct state invalid drop
ip protocol icmp accept
ip6 nexthdr icmpv6 accept
# SSH
tcp dport 22 accept
# Docker published ports (managed by Docker)
# Don't add rules here; Docker manages its own
log prefix "INPUT DROP: "
}
chain forward {
type filter hook forward priority 0; policy drop;
ct state established,related accept
ct state invalid drop
# Docker bridge networks
iifname "docker*" accept
oifname "docker*" accept
# Container to internet
iifname "docker*" oifname "eth0" accept
iifname "eth0" oifname "docker*" ct state established,related accept
log prefix "FORWARD DROP: "
}
chain output {
type filter hook output priority 0; policy accept;
}
}
Service Management
Systemd Integration
# Enable nftables service
systemctl enable nftables
systemctl start nftables
# Check status
systemctl status nftables
# Reload rules
systemctl reload nftables
# Service uses /etc/nftables.conf by default
Persistence
# Save current ruleset
nft list ruleset > /etc/nftables.conf
# Test before applying
nft -c -f /etc/nftables.conf
# Apply ruleset
nft -f /etc/nftables.conf
# Atomic replace (transaction)
nft -f - << 'EOF'
flush ruleset
table inet filter {
chain input {
type filter hook input priority 0; policy accept;
}
}
EOF
Migration from iptables
Translation Tool
# Convert iptables rules to nftables
iptables-save > /tmp/iptables.rules
iptables-restore-translate -f /tmp/iptables.rules > /etc/nftables.conf
# Single rule translation
iptables-translate -A INPUT -p tcp --dport 22 -j ACCEPT
# Output: nft add rule ip filter INPUT tcp dport 22 counter accept
Common Conversions
| iptables | nftables |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
iptables Compatibility Layer
# Use iptables-nft (uses nftables backend)
update-alternatives --set iptables /usr/sbin/iptables-nft
update-alternatives --set ip6tables /usr/sbin/ip6tables-nft
# Check which backend is active
iptables -V
# Shows: iptables v1.8.x (nf_tables)
# Rules created by iptables-nft visible in nft
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
nft list ruleset | grep 22
Troubleshooting
Debugging Rules
# Check syntax without applying
nft -c -f /etc/nftables.conf
# Verbose output
nft -a list ruleset # Show handles
# List specific elements
nft list table inet filter
nft list chain inet filter input
nft list set inet filter allowed_ips
# Check counters
nft list chain inet filter input
# Look for counter values
# Enable tracing
nft add rule inet raw prerouting meta nftrace set 1
nft monitor trace
Common Issues
# Rule order matters
# BAD: drop before allow
nft add rule inet filter input drop
nft add rule inet filter input tcp dport 22 accept # Never reached!
# GOOD: allow before drop
nft add rule inet filter input tcp dport 22 accept
nft add rule inet filter input drop
# Check chain policy
nft list chain inet filter input | grep policy
# If policy is drop, you need explicit accepts
# Verify conntrack
nft add rule inet filter input ct state established,related accept
# This should be near the top for established connections
# Check for duplicate rules
nft -a list chain inet filter input
# Look for same matches with different handles
# Interface names
# Use quotes and exact names
nft add rule inet filter input iifname "eth0" accept # Exact match
nft add rule inet filter input iifname "eth*" accept # Wildcard
Network Issues
# Check if nftables is blocking traffic
# Temporarily set policy to accept
nft add chain inet filter input { policy accept \; }
nft flush chain inet filter input
# Add logging to see what's being dropped
nft insert rule inet filter input log prefix "DEBUG: "
# Check counter on specific rule
nft list chain inet filter input
# Counter shows packets/bytes matched
# Monitor in real-time
watch -n1 "nft list chain inet filter input"
# Check NAT rules
nft list table ip nat
# Verify prerouting and postrouting chains
Quick Command Reference
# === Table Operations ===
nft list tables # List all tables
nft add table inet filter # Create table
nft delete table inet filter # Delete table
nft flush table inet filter # Clear table
# === Chain Operations ===
nft list chains # List all chains
nft add chain inet filter input { type filter hook input priority 0 \; policy drop \; }
nft delete chain inet filter mychain # Delete chain
nft flush chain inet filter input # Clear chain
# === Rule Operations ===
nft add rule inet filter input tcp dport 22 accept # Add rule
nft insert rule inet filter input tcp dport 80 accept # Insert at beginning
nft -a list chain inet filter input # List with handles
nft delete rule inet filter input handle 10 # Delete by handle
# === Sets ===
nft add set inet filter myips { type ipv4_addr \; }
nft add element inet filter myips { 1.2.3.4, 5.6.7.8 }
nft delete element inet filter myips { 1.2.3.4 }
nft list set inet filter myips
nft flush set inet filter myips
# === Ruleset Operations ===
nft list ruleset # Show all rules
nft list ruleset > /etc/nftables.conf # Save rules
nft -f /etc/nftables.conf # Load rules
nft flush ruleset # Clear everything
# === Monitoring ===
nft monitor # Watch for changes
nft monitor trace # Packet tracing
nft -a list ruleset # Rules with handles
# === Service ===
systemctl enable nftables # Enable at boot
systemctl reload nftables # Reload rules
systemctl status nftables # Check status