Modern Linux packet filtering framework replacing iptables, providing a unified, consistent syntax for IPv4, IPv6, ARP, and bridge filtering through the nft command-line tool.

Addresses below are RFC 5737 documentation ranges or placeholders - swap in your own.

Table of Contents#

  1. Overview
  2. Architecture
  3. Installation
  4. Basic Configuration
  5. Sets, Maps, and Concatenations
  6. NAT and Masquerading
  7. Quotas and Rate Limiting
  8. Rule Management
  9. Saving and Restoring Rules
  10. Complete Configuration Example
  11. Migrating from IPTables
  12. Testing and Verification
  13. Troubleshooting
  14. See Also
  15. Sources

1. Overview#

NFTables is the modern packet classification framework in the Linux kernel, replacing iptables, ip6tables, arptables, and ebtables with a single unified tool. It was merged into the Linux kernel in version 3.13 and became the default firewall backend in most distributions.

Key advantages over iptables:

  • Unified syntax - one tool (nft) handles IPv4, IPv6, ARP, and bridge filtering
  • Atomic rule updates - load entire rulesets in a single transaction
  • Sets and maps - native data structures for efficient matching against many values
  • No fixed tables - create your own tables and chains with custom names
  • Better performance - rules compile to a pseudo-bytecode VM in the kernel
  • Readable configuration files - nftables uses a structured config format

2. Architecture#

2.1. Address Families#

Each table belongs to an address family that determines what traffic it processes:

FamilyDescription
ipIPv4 packets (default)
ip6IPv6 packets
inetBoth IPv4 and IPv6 (recommended for most setups)
arpARP packets
bridgeBridge/layer-2 packets
netdevIngress traffic before routing (attached to a device)

The inet family is the most practical choice for host firewalls, as rules apply to both IPv4 and IPv6 without duplication.

2.2. Tables#

Tables are containers for chains. Unlike iptables, nftables has no predefined tables; you create your own with any name. Tables are scoped to an address family.

# Create a table
nft add table inet myfilter

# List tables
nft list tables

# Delete a table (and all its chains/rules)
nft delete table inet myfilter

2.3. Chains#

Chains hold rules. There are two types:

  • Base chains - attached to a Netfilter hook (input, forward, output, prerouting, postrouting); these receive packets from the kernel
  • Regular chains - not attached to any hook; used as jump targets for organizing rules

Base chains require a type, hook, and priority:

# Create a base chain for filtering incoming packets
nft add chain inet myfilter input { type filter hook input priority 0 \; policy drop \; }

Chain types and their valid hooks:

TypeValid HooksPurpose
filterinput, forward, output, prerouting, postroutingPacket filtering
natprerouting, input, output, postroutingNetwork address translation
routeoutputRerouting decisions

Priority determines processing order (lower numbers run first). Common priorities:

PriorityNameTypical Use
-400conntrack (raw)Connection tracking bypass
-300manglePacket mangling
-200dstnatDNAT/port forwarding
0filterStandard filtering
100securitySecurity modules
300srcnatSNAT/masquerading

2.4. Rules#

Rules are added to chains and consist of match expressions and a verdict:

nft add rule inet myfilter input tcp dport 22 accept

The general syntax:

nft add rule <family> <table> <chain> <match expressions> <verdict>

2.5. Sets and Maps#

Sets are native data structures for grouping values (IPs, ports, interfaces) and matching against them efficiently. Maps extend sets by mapping a key to a value or verdict. See section 5 for details.

2.6. Verdicts#

VerdictDescription
acceptAllow the packet
dropSilently discard the packet
rejectDiscard and send an error (ICMP/TCP RST)
queueSend to userspace
jump <chain>Jump to another chain
goto <chain>Go to another chain (no return)
returnReturn to the calling chain

3. Installation#

NFTables is included in modern Linux distributions:

# Debian/Ubuntu
sudo apt install nftables

# RHEL/CentOS/Fedora
sudo dnf install nftables

# Arch Linux
sudo pacman -S nftables

Enable the service for rule persistence:

sudo systemctl enable --now nftables

4. Basic Configuration#

Important: Tables and chains must exist before you can add rules to them. Unlike iptables, nftables has no predefined tables or chains.

4.1. Creating Tables and Chains#

# Create a table for both IPv4 and IPv6
nft add table inet filter

# Create base chains with default policies
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 \; }

4.2. Essential Baseline Rules#

# Allow loopback
nft add rule inet filter input iifname "lo" accept

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

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

# Allow ICMP (ping) for both IPv4 and IPv6
nft add rule inet filter input ip protocol icmp accept
nft add rule inet filter input ip6 nexthdr icmpv6 accept

4.3. Allowing Services#

# Allow SSH
nft add rule inet filter input tcp dport 22 accept

# Allow HTTP and HTTPS
nft add rule inet filter input tcp dport { 80, 443 } accept

# Allow DNS
nft add rule inet filter input tcp dport 53 accept
nft add rule inet filter input udp dport 53 accept

# Allow from a specific subnet
nft add rule inet filter input ip saddr 192.0.2.0/24 tcp dport 22 accept

4.4. Blocking Traffic#

# Drop traffic from a specific IP
nft add rule inet filter input ip saddr 198.51.100.50 drop

# Reject with ICMP error
nft add rule inet filter input ip saddr 198.51.100.50 reject with icmp type port-unreachable

# Log and drop
nft add rule inet filter input ip saddr 10.0.0.0/8 log prefix "blocked-rfc1918: " drop

5. Sets, Maps, and Concatenations#

5.1. Anonymous Sets#

Anonymous sets are defined inline within a rule and cannot be modified after creation:

# Allow multiple ports
nft add rule inet filter input tcp dport { 22, 80, 443, 8080 } accept

# Allow multiple source IPs
nft add rule inet filter input ip saddr { 192.0.2.10, 192.0.2.20, 198.51.100.5 } accept

5.2. Named Sets#

Named sets are persistent, can be updated dynamically, and referenced from multiple rules:

# Create a named set of IPv4 addresses
nft add set inet filter blocklist { type ipv4_addr \; }

# Add elements
nft add element inet filter blocklist { 198.51.100.50, 198.51.100.51, 198.51.100.52 }

# Use in a rule
nft add rule inet filter input ip saddr @blocklist drop

# Remove an element
nft delete element inet filter blocklist { 198.51.100.51 }

# Create a set of ports
nft add set inet filter allowed_ports { type inet_service \; }
nft add element inet filter allowed_ports { 22, 80, 443 }
nft add rule inet filter input tcp dport @allowed_ports accept

Named sets can also have flags for automatic timeout or size limits:

# Set with auto-expiring elements (useful for temporary bans)
nft add set inet filter tempban { type ipv4_addr \; flags timeout \; }
nft add element inet filter tempban { 198.51.100.100 timeout 1h }
nft add rule inet filter input ip saddr @tempban drop

# Set with size limit
nft add set inet filter ratelimit_set { type ipv4_addr \; flags dynamic \; size 65536 \; }

5.3. Maps and Vmaps#

Maps associate keys with values; verdict maps (vmaps) associate keys with verdicts:

# Verdict map: route traffic based on destination port
nft add chain inet filter web_traffic
nft add chain inet filter ssh_traffic

nft add rule inet filter input tcp dport vmap { 22 : jump ssh_traffic, 80 : jump web_traffic, 443 : jump web_traffic }

# Named map for port redirection
nft add map inet filter portmap { type inet_service : ipv4_addr \; }
nft add element inet filter portmap { 80 : 192.0.2.10, 443 : 192.0.2.20 }

6. NAT and Masquerading#

NAT requires a table with nat type chains. IP forwarding must be enabled:

sudo sysctl -w net.ipv4.ip_forward=1
echo "net.ipv4.ip_forward = 1" | sudo tee /etc/sysctl.d/99-forward.conf

Create the NAT table and chains:

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 \; }

6.1. Masquerading#

# Masquerade all traffic leaving through eth0
nft add rule inet nat postrouting oifname "eth0" masquerade

# Allow forwarded traffic (in the filter table)
nft add rule inet filter forward iifname "eth1" oifname "eth0" accept
nft add rule inet filter forward iifname "eth0" oifname "eth1" ct state established,related accept

6.2. Port Forwarding (DNAT)#

# Forward port 80 to internal host 192.0.2.10:8080
nft add rule inet nat prerouting tcp dport 80 dnat to 192.0.2.10:8080

# Allow the forwarded traffic
nft add rule inet filter forward ip daddr 192.0.2.10 tcp dport 8080 ct state new accept

6.3. Source NAT (SNAT)#

For systems with a static public IP, SNAT is more efficient than masquerading:

nft add rule inet nat postrouting oifname "eth0" snat to 203.0.113.5

7. Quotas and Rate Limiting#

# Rate limit SSH connections (3 per minute per source IP)
nft add rule inet filter input tcp dport 22 ct state new meter ssh_meter { ip saddr limit rate 3/minute } accept

# Rate limit ICMP
nft add rule inet filter input ip protocol icmp limit rate 5/second accept

# Bandwidth quota (log after 1 GB)
nft add rule inet filter input quota over 1 gbytes log prefix "quota-exceeded: "

# Connection limit per source IP
nft add rule inet filter input tcp dport 80 ct state new meter http_meter { ip saddr ct count over 25 } reject

8. Rule Management#

8.1. Listing Rules#

# List entire ruleset
nft list ruleset

# List a specific table
nft list table inet filter

# List with handles (needed for deletion)
nft list table inet filter -a

# List a specific chain
nft list chain inet filter input

# JSON output
nft -j list ruleset

8.2. Deleting Rules#

Rules are deleted by handle number:

# List rules with handles
nft list table inet filter -a
# Output shows: ... handle 5

# Delete by handle
nft delete rule inet filter input handle 5

8.3. Flushing Rules#

# Flush all rules in all tables
nft flush ruleset

# Flush a specific table
nft flush table inet filter

# Flush a specific chain
nft flush chain inet filter input

9. Saving and Restoring Rules#

# Save current ruleset to the default config file
nft list ruleset > /etc/nftables.conf

# The nftables.service loads /etc/nftables.conf on boot
sudo systemctl enable nftables

# Manually restore
nft -f /etc/nftables.conf

The config file uses nftables' native scripting format:

#!/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
        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;
    }
}

10. Complete Configuration Example#

A production-ready host firewall with SSH, web services, rate limiting, and a blocklist:

#!/usr/sbin/nft -f
flush ruleset

table inet filter {
    set blocklist {
        type ipv4_addr
        flags interval
        elements = { 10.0.0.0/8, 172.16.0.0/12 }
    }

    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

        # Connection tracking
        ct state established,related accept
        ct state invalid drop

        # Blocklist
        ip saddr @blocklist drop

        # Rate-limit SSH
        tcp dport 22 ct state new meter ssh_limit { ip saddr limit rate 5/minute burst 3 packets } accept

        # Allowed services
        tcp dport @allowed_tcp_ports accept

        # ICMP
        ip protocol icmp icmp type { echo-request, destination-unreachable, time-exceeded } accept
        ip6 nexthdr icmpv6 icmpv6 type { echo-request, nd-neighbor-solicit, nd-router-advert, nd-neighbor-advert } accept

        # Log everything else before dropping
        log prefix "nft-drop: " flags all counter
    }

    chain forward {
        type filter hook forward priority 0; policy drop;
    }

    chain output {
        type filter hook output priority 0; policy accept;
    }
}

11. Migrating from IPTables#

Automated Translation#

The iptables-translate tool converts iptables commands to nftables syntax:

# Translate a single command
iptables-translate -A INPUT -p tcp --dport 22 -j ACCEPT
# Output: nft add rule ip filter input tcp dport 22 counter accept

# Translate an entire saved ruleset
iptables-restore-translate -f /etc/iptables/rules.v4 > /etc/nftables.conf

Syntax Comparison#

IPTablesNFTables
iptables -A INPUT -p tcp --dport 22 -j ACCEPTnft add rule inet filter input tcp dport 22 accept
iptables -A INPUT -s 10.0.0.0/8 -j DROPnft add rule inet filter input ip saddr 10.0.0.0/8 drop
iptables -A INPUT -m multiport --dports 80,443 -j ACCEPTnft add rule inet filter input tcp dport { 80, 443 } accept
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADEnft add rule inet nat postrouting oifname "eth0" masquerade
iptables -A INPUT -m conntrack --ctstate ESTABLISHED -j ACCEPTnft add rule inet filter input ct state established accept
iptables -L -v -nnft list ruleset
iptables -Fnft flush ruleset

Key Differences#

  • No predefined tables or chains; you must create them first
  • No separate tools for IPv4/IPv6; use the inet family
  • Sets replace repeated rules for multiple ports/IPs
  • Atomic ruleset loading (nft -f) prevents partial-apply issues
  • Counters are optional (add counter keyword to rules that need them)

12. Testing and Verification#

# List full ruleset
nft list ruleset

# List with handles (for deletion)
nft list ruleset -a

# Monitor rule hits in real time (requires counter on rules)
watch -n 1 'nft list chain inet filter input'

# Test port access from another host
nc -zv <server-ip> 22

# Validate a config file without applying
nft -c -f /etc/nftables.conf

# Debug: trace packet matching (temporary)
nft add rule inet filter input meta nftrace set 1
nft monitor trace

Troubleshooting#

IssueCauseSolution
"Error: No such file or directory"Table or chain does not existCreate the table and chain before adding rules
Rules not matching any packetsCounters not added, or rules in wrong orderAdd counter keyword to rules; check chain priority and policy
"Error: Could not process rule: File exists"Duplicate table/chain creationUse nft list tables to check; create only if missing
Rules lost after rebootnftables.service not enabled or config not savedRun nft list ruleset > /etc/nftables.conf and enable the service
IPv6 traffic bypasses rulesUsing ip family instead of inetUse inet family tables for dual-stack filtering
NAT not workingMissing nat chain or IP forwarding disabledCreate nat type chains and set net.ipv4.ip_forward=1
Sets not updatingTrying to modify anonymous setUse named sets for dynamic updates
iptables and nft conflictBoth managing rules simultaneouslyChoose one; if using FirewallD, let it manage nftables exclusively
Syntax error in config fileMissing semicolons or incorrect escapingIn shell commands, escape semicolons with \;; in config files use plain ;

See Also#

Sources#