nftables Configuration (iptables Successor): Complete Guide
Introduction
nftables represents the modern evolution of Linux packet filtering, designed as the successor to the venerable iptables, ip6tables, arptables, and ebtables frameworks. Introduced in the Linux kernel 3.13 and reaching maturity in recent years, nftables offers a unified, simplified, and more powerful approach to packet filtering and network address translation. Unlike the fragmented legacy tools that required separate utilities for IPv4, IPv6, ARP, and bridge filtering, nftables provides a single command-line interface (nft) and a cohesive ruleset syntax that handles all packet filtering scenarios.
nftables addresses many limitations of iptables while maintaining similar concepts, making the transition manageable for experienced administrators. Key improvements include better performance through reduced kernel-userspace communication, enhanced syntax with variables and dictionaries, improved IPv4/IPv6 dual-stack support, atomic ruleset updates, and a more logical, human-readable configuration language. Major Linux distributions including Debian 10+, Ubuntu 20.04+, RHEL 8+, and Rocky Linux 8+ have adopted nftables as their default firewall backend.
This comprehensive guide covers nftables from fundamental concepts to advanced configurations, including ruleset structure, family types, table and chain organization, NAT implementation, logging, performance optimization, and migration strategies from iptables. Whether you're deploying nftables on new systems or migrating existing iptables configurations, this guide provides the knowledge needed to leverage this powerful packet filtering framework.
Understanding nftables Architecture
Why nftables?
Advantages over iptables:
- Unified framework - Single tool for IPv4, IPv6, ARP, and bridge filtering
- Simplified syntax - More readable and maintainable rules
- Better performance - Reduced kernel-userspace communication
- Atomic updates - All rule changes applied atomically
- Built-in variables - Sets, maps, and dictionaries for complex matching
- Improved debugging - Better error messages and tracing
- Smaller codebase - More maintainable kernel implementation
nftables Components
1. Tables
- Containers for chains
- Defined by family (ip, ip6, inet, arp, bridge, netdev)
2. Chains
- Lists of rules
- Can be base chains (hooked to netfilter) or regular chains
3. Rules
- Match criteria and verdict (accept, drop, reject, etc.)
- Processed sequentially within chains
4. Sets and Maps
- Named collections for efficient matching
- Dictionaries for mapping values
Family Types
# ip - IPv4 packets
# ip6 - IPv6 packets
# inet - Both IPv4 and IPv6 (dual-stack)
# arp - ARP packets
# bridge - Bridge traffic
# netdev - Ingress traffic (early filtering)
Hook Types
Base chain hooks:
ingress - Very early stage (netdev family only)
prerouting - Before routing decision
input - For local process
forward - For routed packets
output - From local process
postrouting - After routing decision
Prerequisites
Before configuring nftables, ensure you have:
- Linux system with kernel 3.13+ (4.x+ recommended)
- Root or sudo access
- nftables package installed
- Basic understanding of packet filtering concepts
- Knowledge of your network topology
- SSH or console access (critical for recovery)
Installation
Debian/Ubuntu:
sudo apt update
sudo apt install nftables -y
RHEL/CentOS/Rocky Linux 8+:
sudo dnf install nftables -y
Enable nftables service:
sudo systemctl enable nftables
sudo systemctl start nftables
Verify Installation
# Check version
nft --version
# List current ruleset
sudo nft list ruleset
# Check service status
sudo systemctl status nftables
Basic nftables Commands
Viewing Rulesets
# List entire ruleset
sudo nft list ruleset
# List specific table
sudo nft list table inet filter
# List specific chain
sudo nft list chain inet filter input
# List with handles (for rule deletion)
sudo nft -a list ruleset
Creating Tables and Chains
# Create table
sudo nft add table inet filter
# Create base chain
sudo nft add chain inet filter input '{ type filter hook input priority 0; policy accept; }'
# Create regular chain
sudo nft add chain inet filter custom_chain
Adding Rules
# Add rule to end of chain
sudo nft add rule inet filter input tcp dport 22 accept
# Insert rule at beginning
sudo nft insert rule inet filter input tcp dport 22 accept
# Add rule at specific position
sudo nft insert rule inet filter input position 5 tcp dport 22 accept
Deleting Rules
# Delete rule by handle
sudo nft delete rule inet filter input handle 5
# Delete entire chain
sudo nft delete chain inet filter input
# Delete entire table
sudo nft delete table inet filter
# Flush all rules in chain
sudo nft flush chain inet filter input
# Flush all rules in table
sudo nft flush table inet filter
# Flush entire ruleset
sudo nft flush ruleset
Basic Firewall Configuration
Simple Firewall Example
#!/usr/sbin/nft -f
# Flush existing ruleset
flush ruleset
# Create inet table (handles both IPv4 and IPv6)
table inet filter {
# Input chain
chain input {
type filter hook input priority 0; policy drop;
# Allow loopback
iif lo accept
# Allow established and related connections
ct state established,related accept
# Drop invalid packets
ct state invalid drop
# Allow SSH
tcp dport 22 accept
# Allow HTTP and HTTPS
tcp dport { 80, 443 } accept
# Allow ping
icmp type echo-request limit rate 1/second accept
icmpv6 type echo-request limit rate 1/second accept
# Log dropped packets
limit rate 5/minute log prefix "nftables input drop: "
}
# Forward chain (for routers)
chain forward {
type filter hook forward priority 0; policy drop;
}
# Output chain
chain output {
type filter hook output priority 0; policy accept;
}
}
Save as /etc/nftables.conf and apply:
sudo nft -f /etc/nftables.conf
sudo systemctl reload nftables
Web Server Configuration
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
# Loopback
iif lo accept
# Established connections
ct state established,related accept
ct state invalid drop
# SSH from specific subnet
ip saddr 192.168.1.0/24 tcp dport 22 accept
# Web services
tcp dport { 80, 443 } accept
# ICMP
ip protocol icmp icmp type { echo-request, echo-reply } limit rate 1/second accept
ip6 nexthdr icmpv6 icmpv6 type { echo-request, echo-reply } limit rate 1/second accept
# Log and drop
limit rate 5/minute log prefix "nftables drop: "
}
chain forward {
type filter hook forward priority 0; policy drop;
}
chain output {
type filter hook output priority 0; policy accept;
}
}
Advanced Features
Sets for Efficient Matching
Named sets:
# Create table and chain
nft add table inet filter
nft add chain inet filter input '{ type filter hook input priority 0; policy drop; }'
# Create set of allowed IPs
nft add set inet filter allowed_ips '{ type ipv4_addr; }'
# Add elements to set
nft add element inet filter allowed_ips { 192.168.1.10, 192.168.1.20, 192.168.1.30 }
# Use set in rule
nft add rule inet filter input ip saddr @allowed_ips accept
Inline anonymous sets:
# Allow multiple ports
nft add rule inet filter input tcp dport { 22, 80, 443, 8080 } accept
# Block multiple IPs
nft add rule inet filter input ip saddr { 10.0.0.1, 10.0.0.2 } drop
Intervals in sets:
# Create set with interval support
nft add set inet filter blocked_ranges '{ type ipv4_addr; flags interval; }'
# Add IP ranges
nft add element inet filter blocked_ranges { 192.168.1.1-192.168.1.50, 10.0.0.0/8 }
# Use in rule
nft add rule inet filter input ip saddr @blocked_ranges drop
Maps (Dictionaries)
Port mapping:
# Create map
nft add map inet nat portmap '{ type inet_service : ipv4_addr; }'
# Add port to IP mappings
nft add element inet nat portmap { 80 : 192.168.1.10, 443 : 192.168.1.20 }
# Use in DNAT rule
nft add rule inet nat prerouting dnat to tcp dport map @portmap
Verdict maps:
# Create verdict map
nft add map inet filter ip_actions '{ type ipv4_addr : verdict; }'
# Define actions per IP
nft add element inet filter ip_actions { 192.168.1.10 : accept, 192.168.1.20 : drop }
# Use in rule
nft add rule inet filter input ip saddr vmap @ip_actions
Variables
#!/usr/sbin/nft -f
# Define variables
define WAN_IF = eth0
define LAN_IF = eth1
define SSH_PORT = 22
define WEB_PORTS = { 80, 443 }
define TRUSTED_IPS = { 192.168.1.10, 192.168.1.20 }
flush ruleset
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
# Use variables
iif $LAN_IF accept
iif $WAN_IF ip saddr $TRUSTED_IPS tcp dport $SSH_PORT accept
tcp dport $WEB_PORTS accept
}
}
Concatenations
# Match source IP and port combination
nft add rule inet filter input ip saddr . tcp sport { 192.168.1.10 . 1234, 192.168.1.20 . 5678 } accept
# Use in set
nft add set inet filter ssh_access '{ type ipv4_addr . inet_service; }'
nft add element inet filter ssh_access { 192.168.1.10 . 22, 192.168.1.20 . 22 }
nft add rule inet filter input ip saddr . tcp dport @ssh_access accept
Network Address Translation (NAT)
Masquerading (SNAT)
#!/usr/sbin/nft -f
flush ruleset
table inet nat {
chain postrouting {
type nat hook postrouting priority 100; policy accept;
# Masquerade outgoing traffic on WAN interface
oif eth0 masquerade
}
}
table inet filter {
chain forward {
type filter hook forward priority 0; policy drop;
# Allow forwarding from LAN to WAN
iif eth1 oif eth0 accept
# Allow established connections back
ct state established,related accept
}
}
Enable IP forwarding:
sudo sysctl -w net.ipv4.ip_forward=1
echo "net.ipv4.ip_forward=1" | sudo tee -a /etc/sysctl.conf
Static Source NAT
table inet nat {
chain postrouting {
type nat hook postrouting priority 100; policy accept;
# SNAT outgoing traffic to specific IP
oif eth0 snat to 203.0.113.50
}
}
Destination NAT (Port Forwarding)
table inet nat {
chain prerouting {
type nat hook prerouting priority -100; policy accept;
# Forward port 80 to internal server
iif eth0 tcp dport 80 dnat to 192.168.1.100:80
# Forward port 443 to internal server
iif eth0 tcp dport 443 dnat to 192.168.1.100:443
# Forward SSH on non-standard port
iif eth0 tcp dport 2222 dnat to 192.168.1.10:22
}
}
table inet filter {
chain forward {
type filter hook forward priority 0; policy drop;
# Allow forwarded connections
ct state established,related accept
iif eth0 oif eth1 tcp dport { 80, 443 } accept
iif eth0 oif eth1 ip daddr 192.168.1.10 tcp dport 22 accept
}
}
Load Balancing with NAT
table inet nat {
chain prerouting {
type nat hook prerouting priority -100; policy accept;
# Round-robin load balancing
tcp dport 80 dnat to numgen inc mod 3 map { \
0 : 192.168.1.10, \
1 : 192.168.1.11, \
2 : 192.168.1.12 \
}
}
}
Logging and Monitoring
Basic Logging
# Log dropped packets
nft add rule inet filter input limit rate 5/minute log prefix \"nftables drop: \" drop
# Log specific service access
nft add rule inet filter input tcp dport 22 log prefix \"SSH access: \" accept
# Log with log level
nft add rule inet filter input log prefix \"INPUT: \" level info
Advanced Logging
table inet filter {
# Logging chain
chain log_and_drop {
limit rate 5/minute log prefix "nftables denied: " level warning
drop
}
chain input {
type filter hook input priority 0; policy drop;
# ... accept rules ...
# Jump to logging chain for everything else
jump log_and_drop
}
}
Connection Tracking Monitoring
# View conntrack table
sudo conntrack -L
# Statistics
sudo conntrack -S
# Watch real-time connections
sudo conntrack -E
View nftables Statistics
# Show rule counters
sudo nft list ruleset -a
# Monitor specific chain
watch -n 1 'sudo nft list chain inet filter input'
Security Hardening
Complete Hardened Firewall
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
# Sets for organization
set allowed_ssh_ips {
type ipv4_addr
flags interval
elements = { 192.168.1.0/24, 10.0.0.100 }
}
set blocked_ips {
type ipv4_addr
flags interval
}
# Rate limiting chain
chain ssh_ratelimit {
# Allow 3 connections per minute per IP
ct state new limit rate over 3/minute drop
accept
}
# Input chain
chain input {
type filter hook input priority 0; policy drop;
# Loopback
iif lo accept
# Blocked IPs
ip saddr @blocked_ips drop
# Connection tracking
ct state invalid drop
ct state established,related accept
# Anti-spoofing (adjust for your network)
iif eth0 ip saddr { 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 } drop
# SSH with rate limiting from trusted IPs
ip saddr @allowed_ssh_ips tcp dport 22 jump ssh_ratelimit
# Web services
tcp dport { 80, 443 } ct state new accept
# ICMP rate limiting
ip protocol icmp icmp type echo-request limit rate 1/second accept
ip6 nexthdr icmpv6 icmpv6 type echo-request limit rate 1/second accept
# Port scan detection
tcp flags & (fin|syn|rst|psh|ack|urg) == fin|syn log prefix "Port scan SYN-FIN: " drop
tcp flags & (fin|syn|rst|psh|ack|urg) == 0 log prefix "Port scan NULL: " drop
tcp flags & (fin|syn|rst|psh|ack|urg) == fin|psh|urg log prefix "Port scan XMAS: " drop
# Log remaining drops
limit rate 5/minute log prefix "nftables input drop: " level warning
}
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;
}
}
DDoS Protection
table inet filter {
# SYN flood protection
chain input {
type filter hook input priority 0; policy drop;
# Limit new TCP connections
tcp flags syn tcp dport { 80, 443 } limit rate over 25/second drop
# Continue with other rules
# ...
}
}
Migration from iptables
Automatic Translation
# Translate iptables rules to nftables
sudo iptables-save > /tmp/iptables.rules
sudo iptables-restore-translate -f /tmp/iptables.rules > /tmp/nftables.nft
# Review translated rules
cat /tmp/nftables.nft
# Apply if satisfactory
sudo nft -f /tmp/nftables.nft
Side-by-Side Comparison
iptables:
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -s 192.168.1.0/24 -j ACCEPT
nftables:
nft add rule inet filter input tcp dport 22 accept
nft add rule inet filter input ct state established,related accept
nft add rule inet filter input ip saddr 192.168.1.0/24 accept
Coexistence
nftables and iptables can coexist, but it's not recommended long-term:
# Disable iptables backend if using nftables
sudo systemctl disable iptables
sudo systemctl disable ip6tables
# Enable nftables
sudo systemctl enable nftables
Configuration Management
Configuration File Structure
Main configuration file: /etc/nftables.conf
#!/usr/sbin/nft -f
# Include other files
include "/etc/nftables.d/*.nft"
flush ruleset
# Main configuration
table inet filter {
# ...
}
Modular organization:
# /etc/nftables.d/00-defines.nft
define WAN_IF = eth0
define LAN_IF = eth1
define SSH_PORT = 22
# /etc/nftables.d/10-filter-table.nft
table inet filter {
# ...
}
# /etc/nftables.d/20-nat-table.nft
table inet nat {
# ...
}
Atomic Rule Updates
# Save current ruleset
sudo nft list ruleset > /tmp/ruleset-backup.nft
# Test new configuration
sudo nft -c -f /etc/nftables.conf
# Apply atomically
sudo nft -f /etc/nftables.conf
# Rollback if needed
sudo nft -f /tmp/ruleset-backup.nft
Backup and Restore
#!/bin/bash
# backup-nftables.sh
BACKUP_DIR="/root/nftables-backups"
mkdir -p $BACKUP_DIR
# Save current ruleset
nft list ruleset > $BACKUP_DIR/nftables-$(date +%Y%m%d-%H%M%S).nft
# Keep only last 30 days
find $BACKUP_DIR -name "nftables-*.nft" -mtime +30 -delete
echo "nftables backup completed"
Troubleshooting
Common Issues
Rules not loading:
# Check syntax
sudo nft -c -f /etc/nftables.conf
# View detailed errors
sudo nft -f /etc/nftables.conf 2>&1
Locked out via SSH:
# Prevention: Test with timeout
#!/bin/bash
nft -f /etc/nftables.conf
sleep 60
nft flush ruleset
# If SSH works within 60 seconds, Ctrl+C and save permanently
Performance issues:
# Check conntrack table size
cat /proc/sys/net/netfilter/nf_conntrack_count
cat /proc/sys/net/netfilter/nf_conntrack_max
# Increase if needed
sudo sysctl -w net.netfilter.nf_conntrack_max=262144
Debugging
Enable tracing:
# Add trace rule
sudo nft add rule inet filter input tcp dport 80 meta nftrace set 1
# Monitor trace
sudo nft monitor trace
Check rule matching:
# View rule counters
sudo nft list chain inet filter input
# Reset counters
sudo nft reset counters inet filter input
Best Practices
1. Use inet Family for Dual-Stack
# Instead of separate ip and ip6 tables
table inet filter {
# Handles both IPv4 and IPv6
}
2. Organize with Sets and Maps
# Better than many individual rules
set web_servers {
type ipv4_addr
elements = { 192.168.1.10, 192.168.1.11, 192.168.1.12 }
}
3. Use Variables for Maintainability
define ADMIN_IPS = { 192.168.1.5, 10.0.0.100 }
define SSH_PORT = 22
# Use throughout configuration
ip saddr $ADMIN_IPS tcp dport $SSH_PORT accept
4. Implement Logging Strategically
# Log only what's needed, with rate limiting
limit rate 5/minute log prefix "Blocked: "
5. Test Before Production
# Validate syntax
nft -c -f /etc/nftables.conf
# Test in staging environment
# Use timeout rollback for production
6. Document Your Rules
# Use comments
nft add rule inet filter input tcp dport 22 accept comment "SSH access"
# Maintain external documentation
Conclusion
nftables represents the future of packet filtering on Linux, offering significant improvements over iptables in performance, syntax clarity, and feature richness. Its unified framework, powerful set and map capabilities, and atomic updates make it ideal for modern infrastructure ranging from single servers to complex enterprise networks.
Key takeaways:
- Unified framework handles IPv4, IPv6, and bridge filtering in one tool
- Simplified syntax improves readability and maintainability
- Sets and maps enable efficient, scalable rule matching
- inet family simplifies dual-stack configurations
- Atomic updates ensure consistent rule application
- Better performance through optimized kernel communication
- Migration tools ease transition from iptables
As Linux distributions continue adopting nftables as the default firewall framework, mastering its capabilities becomes essential for system administrators and security professionals. Whether deploying new systems or migrating from iptables, the flexibility and power of nftables provide robust packet filtering for contemporary network security requirements.
For advanced scenarios, explore integration with fail2ban for dynamic blocking, automation with Ansible for infrastructure-scale deployment, and nftables' netdev family for high-performance ingress filtering.


