Introduction
Linux firewalls form the cornerstone of network security for systems ranging from personal computers to enterprise infrastructure. For decades, iptables has been the go-to tool for packet filtering and network address translation on Linux. However, the landscape is evolving - nftables represents the modern successor with improved performance, cleaner syntax, and enhanced capabilities. Understanding both tools prepares you for both legacy systems and contemporary deployments.
This comprehensive guide covers packet filtering fundamentals, NAT configuration, rule management, and the transition from iptables to nftables. Whether you’re securing a single server or managing complex network infrastructure, these skills are essential for Linux professionals in 2026.
Understanding Packet Filtering
How Packet Filtering Works
Linux firewalls operate at various layers of the network stack, examining packets as they flow through the kernel. When a packet arrives, the firewall evaluates it against a set of rules organized into chains. Each rule specifies matching criteria (source IP, destination IP, port, protocol, etc.) and an action (accept, drop, reject, or modify).
The firewall processes rules sequentially, applying the first matching rule’s action. If no rules match, the chain’s default policy determines the outcome. This deterministic processing enables precise control over network traffic.
Tables and Chains
Linux firewalls use tables to organize rules by function, with each table containing specific chains:
filter Table: The primary table for packet filtering
- INPUT: Packets destined for local socket
- OUTPUT: Locally generated packets
- FORWARD: Packets being routed through the system
nat Table: Network Address Translation
- PREROUTING: Packets before routing decision
- OUTPUT: Locally generated packets
- POSTROUTING: Packets after routing decision
mangle Table: Packet modification
- PREROUTING, INPUT, FORWARD, OUTPUT, POSTROUTING
raw Table: Connection tracking exemptions
- PREROUTING, OUTPUT
Understanding this hierarchy is crucial - rules in different tables process packets at different stages, affecting how modifications and filtering interact.
iptables: The Classic Approach
Basic iptables Syntax
The iptables command follows this pattern:
iptables -A CHAIN -p PROTOCOL --dport PORT -j ACTION
Key flags:
-A: Append rule to chain-I: Insert rule (at position)-D: Delete rule-L: List rules-p: Protocol (tcp, udp, icmp)--dport: Destination port--sport: Source port-s: Source address-d: Destination address-j: Jump to target (ACCEPT, DROP, REJECT)
Building a Basic Firewall
Let’s construct a comprehensive firewall:
#!/bin/bash
# basic-firewall.sh - Basic iptables firewall script
# Flush existing rules
iptables -F
iptables -X
iptables -t nat -F
iptables -t nat -X
iptables -t mangle -F
iptables -t mangle -X
# Set default policies - deny incoming, allow outgoing
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
# Allow loopback traffic
iptables -A INPUT -i lo -j ACCEPT
# Allow established and related connections
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# Allow SSH (be careful - change port if non-standard)
iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT
# Allow HTTP and HTTPS
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
# Allow ping (ICMP) - optional, often disabled
iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT
# Log dropped packets
iptables -A INPUT -m limit --limit 5/min -j LOG --log-prefix "iptables-dropped: " --log-level 4
# Save rules
iptables-save > /etc/iptables/rules.v4
Connection Tracking
The connection tracking module (conntrack) is fundamental to stateful firewalls:
# Allow all established connections
$ iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# Allow new connections only on specific ports
$ iptables -A INPUT -p tcp -m conntrack --ctstate NEW --dport 80 -j ACCEPT
$ iptables -A INPUT -p tcp -m conntrack --ctstate NEW --dport 443 -j ACCEPT
# Check connection tracking table
$ conntrack -L
$ conntrack -L -p tcp --dport 80
# Clear connection tracking (use with caution)
$ conntrack -F
Connection tracking maintains state for TCP connections and UDP “connections,” allowing the firewall to intelligently handle return traffic without explicit rules.
Port Scanning Protection
Detect and block port scans:
# Limit new connections per source
$ iptables -A INPUT -p tcp --tcp-flags ALL NONE -m limit --limit 5/min -j LOG --log-prefix "NULL scan: "
$ iptables -A INPUT -p tcp --tcp-flags ALL NONE -j DROP
$ iptables -A INPUT -p tcp --tcp-flags ALL ALL -m limit --limit 5/min -j LOG --log-prefix "XMAS scan: "
$ iptables -A INPUT -p tcp --tcp-flags ALL ALL -j DROP
# SYN flood protection
$ iptables -A INPUT -p tcp --syn -m conntrack --ctstate NEW -m recent --set
$ iptables -A INPUT -p tcp --syn -m conntrack --ctstate NEW -m recent --update --seconds 60 --hitcount 10 -j DROP
# Rate limiting
$ iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -m recent --set
$ iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -m recent --update --seconds 60 --hitcount 4 -j DROP
Network Address Translation (NAT)
NAT allows multiple devices to share a single public IP address:
# Enable IP forwarding
$ echo 1 > /proc/sys/net/ipv4/ip_forward
# Masquerading (NAT for outbound traffic)
$ iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
# Port forwarding (forward port 8080 to internal host)
$ iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 8080 -j DNAT --to-destination 192.168.1.100:80
# Redirect port (redirect incoming HTTP to local proxy)
$ iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 3128
# Source NAT (change source of outbound traffic)
$ iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j SNAT --to-source 203.0.113.10
Managing iptables Rules
# List rules with line numbers
$ iptables -L -n -v --line-numbers
# Delete specific rule
$ iptables -D INPUT 5
# Replace rule
$ iptables -R INPUT 3 -p tcp --dport 22 -j ACCEPT
# Save and restore
$ iptables-save > /etc/iptables/rules.v4
$ iptables-restore < /etc/iptables/rules.v4
# Persistent rules (Debian/Ubuntu)
$ apt install iptables-persistent
$ iptables-save > /etc/iptables/rules.v4
$ ip6tables-save > /etc/iptables/rules.v6
nftables: The Modern Successor
Why nftables?
nftables addresses several iptables limitations:
- Single framework for IPv4, IPv6, ARP, and bridge
- Faster packet classification with improved performance
- Cleaner, more consistent syntax
- Built-in atomic rule updates
- Better support for sets and maps
- Reduced kernel memory footprint
nftables Basics
The nft command structure differs from iptables:
# Basic syntax
nft add rule ip filter INPUT tcp dport 22 accept
nft add chain ip filter INPUT { policy drop \; }
Tables and Chains in nftables
Create tables and chains:
# Create filter table
nft add table ip filter
# Add chains with policies
nft add chain ip filter input '{ policy drop; }'
nft add chain ip filter forward '{ policy drop; }'
nft add chain ip filter output '{ policy accept; }'
# List all rules
nft list ruleset
# List specific table
nft list table ip filter
Building Rules with nftables
Construct comprehensive firewall rules:
#!/bin/bash
# nftables-basic.sh
# Flush existing rules
nft flush ruleset
# Create tables
nft add table ip filter
nft add table ip nat
nft add table ip6 filter
# Filter table chains
nft add chain ip filter input '{ policy drop; }'
nft add chain ip filter forward '{ policy drop; }'
nft add chain ip filter output '{ policy accept; }'
# NAT table chains
nft add chain ip nat prerouting '{ type nat hook prerouting priority dstnat; }'
nft add chain ip nat postrouting '{ type nat hook postrouting priority srcnat; }'
# Loopback
nft add rule ip filter input iif lo accept
# Established connections
nft add rule ip filter input ct state established,related accept
# SSH
nft add rule ip filter input tcp dport 22 ct state new accept
# HTTP/HTTPS
nft add rule ip filter input tcp dport { 80, 443 } ct state new accept
# Logging dropped packets
nft add rule ip filter input counter drop
# Save rules
nft list ruleset > /etc/nftables.conf
Using Sets and Maps
nftables powerful features include sets and maps:
# Create set of blocked IPs
$ nft add set ip filter blocklist '{ type ipv4_addr; flags dynamic; }'
# Add IP to blocklist
$ nft add element ip filter blocklist { 192.0.2.100 }
# Create map for port forwarding
$ nft add map ip nat portmap '{ type ipv4_addr . inet_service : ipv4_addr . inet_service; }'
# Use map in rule
$ nft add rule ip nat prerouting dnat to ip saddr . tcp dport map @portmap
nftables for NAT
# Masquerading
$ nft add rule ip nat postrouting oifname "eth0" masquerade
# Port forwarding
$ nft add rule ip nat prerouting iifname "eth0" tcp dport 8080 dnat to 192.168.1.100:80
# Source NAT
$ nft add rule ip nat postrouting saddr 192.168.1.0/24 oifname "eth0" snat to 203.0.113.10
Dynamic Blocklist Example
Implement dynamic blocking:
# Create dynamic set
$ nft add set ip filter attackers '{ type ipv4_addr; flags dynamic, timeout; }'
# Block repeated offenders
$ nft add rule ip filter input tcp dport 22 ct state new meter ssh-meter { ip saddr limit rate 10/minute } add @attackers { ip saddr timeout 1h } drop
# Allow legitimate traffic through blocklist
$ nft add rule ip filter input ip saddr @attackers drop
Comparison: iptables vs nftables
Syntax Comparison
| Task | iptables | nftables |
|---|---|---|
| List rules | iptables -L |
nft list ruleset |
| Accept TCP | -p tcp -j ACCEPT |
tcp dport 80 accept |
| Source NAT | -t nat -A POSTROUTING -j SNAT |
nat postrouting snat |
| Block IP | -A INPUT -s 1.2.3.4 -j DROP |
add rule ip filter input ip saddr 1.2.3.4 drop |
Migration from iptables
Debian and RHEL-based systems provide migration tools:
# Debian/Ubuntu - convert iptables rules to nftables
$ iptables-restore-translate -n < rules.v4 > rules.nft
# RHEL - use firewalld which supports nftables
$ firewall-cmd --permanent --zone=public --add-service=http
$ firewall-cmd --permanent --zone=public --add-service=https
$ firewall-cmd --reload
# Check backend
$ firewall-cmd --info-zone public
Advanced Techniques
IPv6 Firewall
Protect IPv6 with matching rules:
# Basic IPv6 protection
$ ip6tables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
$ ip6tables -A INPUT -i lo -j ACCEPT
$ ip6tables -A INPUT -p ipv6-icmp -j ACCEPT
$ ip6tables -A INPUT -p tcp --dport 22 -j ACCEPT
# Or use nftables with ip6 tables
$ nft add table ip6 filter
$ nft add chain ip6 filter input '{ policy drop; }'
$ nft add rule ip6 filter input iif lo accept
$ nft add rule ip6 filter input ct state established,related accept
Container Networking
Firewall rules for Docker:
# Allow Docker bridge networking
$ iptables -A FORWARD -i docker0 -o eth0 -j ACCEPT
$ iptables -A FORWARD -i eth0 -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
Docker’s internal rules to prevent interference (add to /etc/docker/daemon.json):
{
"iptables": true,
"ip-forward": false,
"userland-proxy": false
}
Fail2ban Integration
Fail2ban works with both frameworks:
# Check fail2ban status
$ fail2ban-client status
$ fail2ban-client status sshd
# Manual IP unban
$ fail2ban-client set sshd unbanip 192.168.1.100
Custom jail configuration for nftables (add to /etc/fail2ban/jail.local):
[nginx-noscript]
enabled = true
filter = nginx-noscript
action = nftables[name=nginx, port=http, chain=input]
logpath = /var/log/nginx/access.log
maxretry = 10
Troubleshooting Firewall Issues
Debugging Commands
# Check what rules exist
$ nft list ruleset
$ iptables -L -n -v --line-numbers
# Monitor packets in real-time
$ nft monitor
$ tcpdump -i eth0 port 80
# Check connection states
$ conntrack -L
$ conntrack -L -p tcp --dport 80
# Test connectivity
$ nc -zv example.com 80
$ telnet example.com 80
# Check listening ports
$ ss -tulpn
$ netstat -tulpn
Common Issues
Can’t connect after firewall enable
# Check if SSH is allowed
$ iptables -L INPUT -n | grep 22
$ nft list chain ip filter input
# Temporarily allow SSH
$ iptables -I INPUT -p tcp --dport 22 -j ACCEPT
# Check if service is running
$ systemctl status sshd
$ ss -tlnp | grep :22
Port forwarding not working
# Enable IP forwarding
$ cat /proc/sys/net/ipv4/ip_forward
$ echo 1 > /proc/sys/net/ipv4/ip_forward
Make it permanent in /etc/sysctl.conf:
net.ipv4.ip_forward = 1
# Check NAT rules
$ nft list table ip nat
$ iptables -t nat -L -n -v
Best Practices
Rule Organization
- Use separate chains for logical groups
- Place most specific rules first
- Default deny policy
- Log before dropping
- Document all rules
Performance
- Use connection tracking wisely
- Avoid unnecessary logging
- Use hashlimit for rate limiting
- Consider hardware offload
Security
- Regular rule audits
- Monitor logs for attacks
- Implement IDS/IPS
- Keep firewall updated
- Test changes before deployment
Maintenance
- Version control firewall rules
- Document rule changes
- Regular backups
- Automated deployment
- Disaster recovery plan
Conclusion
Mastering Linux firewalls requires understanding both iptables and nftables, as legacy systems continue running iptables while new deployments increasingly adopt nftables. The fundamental concepts - stateful inspection, packet filtering, NAT, and connection tracking - transfer between both tools.
Start with simple rules and gradually add complexity. Always maintain console access when testing firewall changes. Use scripts to manage rules and implement version control. With practice, you’ll be able to implement sophisticated network security policies that protect your infrastructure while enabling legitimate traffic.
Whether you choose iptables for compatibility or nftables for modern features, the principles remain the same: default deny, allow explicitly, log suspicious activity, and continuously monitor and refine your rules.
Comments