2 min read #devops
On this page

nftables Operations Manual

nftables is the next-generation firewall framework for Linux, replacing iptables/ip6tables/arptables/ebtables. It is managed using the nft command, and configurations can be loaded atomically. On Gentoo, the kernel must have CONFIG_NF_TABLES and related modules enabled.

Core concepts: table (namespace, grouped by family), chain (rule container, categorized by type/hook/priority), rule (match + action), set (collection of IPs/ports), verdict (accept/drop/reject/jump).

Status Viewing

# List all rules
nft list ruleset                          # Full ruleset (human-readable)
nft -a list ruleset                       # With handle numbers (for deleting/modifying rules)

# View by table/chain
nft list table inet filter                 # View a specific table
nft list chain inet filter input           # View a specific chain
nft list table ip nat                      # IPv4 NAT table

# View sets / counters
nft list sets                              # All named sets
nft list counters                          # All counters

# Ruleset statistics
nft --stateless list ruleset               # Without state objects (more compact)
nft list ruleset | wc -l                   # Rough estimate of size

Basic Rule Management

# Temporary modifications (lost on reboot, for testing)
nft add rule inet filter input tcp dport 22 accept
nft delete rule inet filter input handle 5  # Delete by handle
nft flush chain inet filter input           # Flush a single chain
nft flush ruleset                           # Flush all rules (! Dangerous)

# View handles
nft -a list chain inet filter input
# Output:  tcp dport 22 accept # handle 5

Persistence and Atomic Loading

# Save the current ruleset
nft list ruleset > /etc/nftables.conf

# Atomic replacement (recommended)
nft -f /etc/nftables.conf                  # Full replacement, rolls back on failure

# Check syntax (without loading)
nft -c -f /etc/nftables.conf

# systemd auto-load
systemctl enable nftables
systemctl restart nftables                  # Load from /etc/nftables.conf

Common Table/Chain Structure Templates

table inet filter {                        # inet = IPv4 + IPv6
    chain input {
        type filter hook input priority 0; policy drop;
        # Allow lo
        iif lo accept
        # Allow established/related connections
        ct state established,related accept
        # Allow SSH
        tcp dport 22 accept
        # Allow ICMPv6 (required for IPv6)
        ip6 nexthdr icmpv6 accept
    }
    chain forward {
        type filter hook forward priority 0; policy drop;
        ct state established,related accept
    }
    chain output {
        type filter hook output priority 0; policy accept;
    }
}

Rule Syntax

# Matching
tcp dport {22, 80, 443}                    # Port set
ip saddr 192.168.1.0/24                    # Source IP subnet
ip daddr != 10.0.0.0/8                     # Non-destination subnet
iif eth0                                   # Input interface
oif wg0                                    # Output interface
ct state new                               # New connection
ct state established,related               # Established/related
tcp flags syn / syn,ack,rst                # Match SYN (new connection request)
meta l4proto {tcp, udp}                    # Transport layer protocol
meta hour "09:00"-"18:00"                  # Time matching
limit rate 10/second                       # Rate limiting
counter                                    # Count (does not change action)
log prefix "SSH: "                         # Log

# Actions
accept                                     # Allow
drop                                       # Drop (no response)
reject                                     # Reject (returns ICMP/RST)
jump another_chain                         # Jump to custom chain
return                                     # Return from custom chain
dnat to 192.168.1.100                      # Destination NAT
snat to 203.0.113.1                        # Source NAT
masquerade                                 # Dynamic SNAT (outgoing IP is variable)
redirect to :8080                          # Local port redirection

NAT / Port Forwarding

# Table definition
table ip nat {
    chain prerouting {
        type nat hook prerouting priority dstnat; policy accept;
    }
    chain postrouting {
        type nat hook postrouting priority srcnat; policy accept;
    }
}

# DNAT (Port forwarding, external → internal)
nft add rule ip nat prerouting iif eth0 tcp dport 8443 dnat to 192.168.1.100:443

# SNAT (Unified outgoing IP)
nft add rule ip nat postrouting oif eth0 snat to 203.0.113.1

# MASQUERADE (Dynamic IP outgoing, e.g., DHCP interface)
nft add rule ip nat postrouting oif eth0 masquerade

# Port redirection (local 80 → 8080)
nft add rule ip nat prerouting tcp dport 80 redirect to :8080

Sets (Efficient Batch Matching)

# Define named set
nft add set inet filter blackhole { type ipv4_addr\; }   # Shell requires escaping semicolon
# Or in config file:
#   set blackhole { type ipv4_addr; }

# Dynamically add/delete elements
nft add element inet filter blackhole { 10.10.10.10, 10.10.10.11 }
nft delete element inet filter blackhole { 10.10.10.10 }

# Use in rules
# ip saddr @blackhole drop

# Sets with timeouts (auto-expire)
# set ratelimit { type ipv4_addr; timeout 1h; }
# ip saddr @ratelimit counter drop

# Set types:
# ipv4_addr / ipv6_addr          IP addresses
# ipv4_addr . inet_service       IP+port combination
# ifname                         Interface name
# mark                           Connection mark

Operations and Maintenance

# Insert rule at specific position (default is append)
nft insert rule inet filter input position 3 tcp dport 8080 accept

# Replace rule
nft replace rule inet filter input handle 7 tcp dport 2222 accept

# Rule comments
# In config file:  tcp dport 22 accept comment "SSH access"

# Reset counters
nft reset counters

# Real-time monitoring
nft monitor                              # Monitor ruleset changes

# Debugging
nft --debug=netlink list ruleset         # View netlink messages
nft --debug=scanner -f rules.conf        # View lexical analysis

Common Scenarios

# Prevent SSH brute force (limit new connection rate)
# tcp dport 22 ct state new limit rate 4/minute accept
# tcp dport 22 ct state new drop

# Allow only specific IPs to access a port
# ip saddr {10.0.0.1, 10.0.0.2} tcp dport 5432 accept
# tcp dport 5432 drop

# Transparent proxy (TPROXY, requires mihomo/v2ray)
# table ip mangle {
#     chain prerouting { type filter hook prerouting priority mangle; }
# }
# meta l4proto tcp ip daddr != 127.0.0.0/8 tproxy to :12345

# Note for Docker users
# Docker inserts its own iptables rules, which may cause order issues when mixed with nftables.
# Recommendation: Either use only iptables (Docker compatibility mode), or ensure nftables rules are loaded before Docker
# Check: nft list ruleset | grep -i docker
# Docker's nftables support: /etc/docker/daemon.json → "iptables": false

Migration Notes (iptables → nft)

# View current iptables rules
iptables-save

# Automatic translation (imperfect, for reference only)
iptables-translate -A INPUT -p tcp --dport 22 -j ACCEPT
# Output: nft add rule ip filter INPUT tcp dport 22 counter accept

# Batch translation (full ruleset)
iptables-restore-translate < <(iptables-save)