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.