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#
- Overview
- Architecture
- 2.1 Address Families
- 2.2 Tables
- 2.3 Chains
- 2.4 Rules
- 2.5 Sets and Maps
- 2.6 Verdicts
- Installation
- Basic Configuration
- Sets, Maps, and Concatenations
- 5.1 Anonymous Sets
- 5.2 Named Sets
- 5.3 Maps and Vmaps
- NAT and Masquerading
- Quotas and Rate Limiting
- Rule Management
- 8.1 Listing Rules
- 8.2 Deleting Rules
- 8.3 Flushing Rules
- Saving and Restoring Rules
- Complete Configuration Example
- Migrating from IPTables
- Testing and Verification
- Troubleshooting
- See Also
- 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:
| Family | Description |
|---|---|
ip | IPv4 packets (default) |
ip6 | IPv6 packets |
inet | Both IPv4 and IPv6 (recommended for most setups) |
arp | ARP packets |
bridge | Bridge/layer-2 packets |
netdev | Ingress 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 myfilter2.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:
| Type | Valid Hooks | Purpose |
|---|---|---|
filter | input, forward, output, prerouting, postrouting | Packet filtering |
nat | prerouting, input, output, postrouting | Network address translation |
route | output | Rerouting decisions |
Priority determines processing order (lower numbers run first). Common priorities:
| Priority | Name | Typical Use |
|---|---|---|
| -400 | conntrack (raw) | Connection tracking bypass |
| -300 | mangle | Packet mangling |
| -200 | dstnat | DNAT/port forwarding |
| 0 | filter | Standard filtering |
| 100 | security | Security modules |
| 300 | srcnat | SNAT/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 acceptThe 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#
| Verdict | Description |
|---|---|
accept | Allow the packet |
drop | Silently discard the packet |
reject | Discard and send an error (ICMP/TCP RST) |
queue | Send to userspace |
jump <chain> | Jump to another chain |
goto <chain> | Go to another chain (no return) |
return | Return 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 nftablesEnable the service for rule persistence:
sudo systemctl enable --now nftables4. 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 accept4.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 accept4.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: " drop5. 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 } accept5.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 acceptNamed 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.confCreate 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 accept6.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 accept6.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.57. 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 } reject8. 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 ruleset8.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 58.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 input9. 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.confThe 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.confSyntax Comparison#
| IPTables | NFTables |
|---|---|
iptables -A INPUT -p tcp --dport 22 -j ACCEPT | nft add rule inet filter input tcp dport 22 accept |
iptables -A INPUT -s 10.0.0.0/8 -j DROP | nft add rule inet filter input ip saddr 10.0.0.0/8 drop |
iptables -A INPUT -m multiport --dports 80,443 -j ACCEPT | nft add rule inet filter input tcp dport { 80, 443 } accept |
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE | nft add rule inet nat postrouting oifname "eth0" masquerade |
iptables -A INPUT -m conntrack --ctstate ESTABLISHED -j ACCEPT | nft add rule inet filter input ct state established accept |
iptables -L -v -n | nft list ruleset |
iptables -F | nft flush ruleset |
Key Differences#
- No predefined tables or chains; you must create them first
- No separate tools for IPv4/IPv6; use the
inetfamily - Sets replace repeated rules for multiple ports/IPs
- Atomic ruleset loading (
nft -f) prevents partial-apply issues - Counters are optional (add
counterkeyword 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 traceTroubleshooting#
| Issue | Cause | Solution |
|---|---|---|
| "Error: No such file or directory" | Table or chain does not exist | Create the table and chain before adding rules |
| Rules not matching any packets | Counters not added, or rules in wrong order | Add counter keyword to rules; check chain priority and policy |
| "Error: Could not process rule: File exists" | Duplicate table/chain creation | Use nft list tables to check; create only if missing |
| Rules lost after reboot | nftables.service not enabled or config not saved | Run nft list ruleset > /etc/nftables.conf and enable the service |
| IPv6 traffic bypasses rules | Using ip family instead of inet | Use inet family tables for dual-stack filtering |
| NAT not working | Missing nat chain or IP forwarding disabled | Create nat type chains and set net.ipv4.ip_forward=1 |
| Sets not updating | Trying to modify anonymous set | Use named sets for dynamic updates |
| iptables and nft conflict | Both managing rules simultaneously | Choose one; if using FirewallD, let it manage nftables exclusively |
| Syntax error in config file | Missing semicolons or incorrect escaping | In shell commands, escape semicolons with \;; in config files use plain ; |