iptables & nftables
iptables is the traditional Linux firewall — it's been filtering packets for 20+ years. You've interacted with it every time Docker creates a container or you configure a firewall rule. nftables is its modern replacement: cleaner syntax, better performance, one tool instead of four. Both work through the same kernel mechanism: Netfilter hooks.
iptables — Tables, Chains, and Rules
How does iptables organize its rules?
iptables has 4 tables (filter, nat, mangle, raw), each with built-in chains. A chain is a list of rules. Each rule says: "if packet matches these conditions, take this action." Packets flow through chains in order. First matching rule wins. If no rule matches, the chain's default policy applies (ACCEPT or DROP).
Tables and their chains:
filter (default — used for basic firewall rules):
INPUT ← packets destined for this host
FORWARD ← packets passing through (router mode)
OUTPUT ← packets originated from this host
nat (Network Address Translation):
PREROUTING ← DNAT: change destination before routing
POSTROUTING ← SNAT/MASQUERADE: change source after routing
OUTPUT ← NAT for locally generated packets
mangle (packet modification — TTL, DSCP, marks):
PREROUTING, INPUT, FORWARD, OUTPUT, POSTROUTING
raw (connection tracking bypass):
PREROUTING, OUTPUT
Common iptables Commands
# View rules (with line numbers and packet counts)
iptables -L -v -n --line-numbers
iptables -t nat -L -v -n # NAT table
# Allow incoming SSH
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
# Block an IP
iptables -A INPUT -s 192.168.1.100 -j DROP
# Allow established connections (stateful firewall)
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
# Drop everything else (default deny at end of INPUT chain)
iptables -A INPUT -j DROP
# Delete a rule by line number
iptables -D INPUT 3
# Insert at specific position (insert before line 2)
iptables -I INPUT 2 -p tcp --dport 80 -j ACCEPT
# Set default policy
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
# Save rules (Debian/Ubuntu)
iptables-save > /etc/iptables/rules.v4
# Restore:
iptables-restore < /etc/iptables/rules.v4
NAT — Network Address Translation
# MASQUERADE: share one public IP (your router does this)
# Traffic from 192.168.0.0/24 going out eth0 gets source NAT'd
iptables -t nat -A POSTROUTING -s 192.168.0.0/24 -o eth0 -j MASQUERADE
echo 1 > /proc/sys/net/ipv4/ip_forward
# SNAT: static source NAT (you know the external IP)
iptables -t nat -A POSTROUTING -s 10.0.0.0/8 -o eth0 \
-j SNAT --to-source 203.0.113.10
# DNAT: port forwarding (expose internal service externally)
# Forward external port 8080 to internal host 10.0.0.5:80
iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 8080 \
-j DNAT --to-destination 10.0.0.5:80
# Docker uses this automatically for -p 8080:80 port mappings
nftables — The Modern Replacement
Why was nftables created if iptables works?
iptables has four separate tools (iptables, ip6tables, arptables, ebtables) for IPv4, IPv6, ARP, and bridge filtering. Rules can't be updated atomically — a window exists during rule changes where packets slip through. nftables unifies all four, supports atomic rule updates, has better performance (JIT compilation), and a cleaner syntax. Debian/Ubuntu now default to nftables under the hood even when you run `iptables` commands.
# nftables: create a simple firewall
nft add table inet filter
nft add chain inet filter input \
{ type filter hook input priority 0 \; policy drop \; }
nft add chain inet filter output \
{ type filter hook output priority 0 \; policy accept \; }
# Allow established + loopback
nft add rule inet filter input ct state established,related accept
nft add rule inet filter input iif lo accept
# Allow SSH
nft add rule inet filter input tcp dport 22 accept
# View rules
nft list ruleset
# nftables rule file (/etc/nftables.conf):
# table inet filter {
# chain input {
# type filter hook input priority 0; policy drop;
# ct state established,related accept
# iif "lo" accept
# tcp dport 22 accept
# }
# }
systemctl enable --now nftables
iptables vs nftables
| iptables | nftables | |
|---|---|---|
| IPv6 support | Separate ip6tables command | Unified (inet family) |
| Atomic updates | No — rules apply one at a time | Yes — transaction-based |
| Performance | Linear rule matching | Sets + maps for O(1) lookups |
| Syntax | Verbose, inconsistent | Clean, consistent |
| Tools needed | iptables, ip6tables, arptables, ebtables | Just nft |
| Distro support | Still default on RHEL 8-, older Debian | Default on Debian 10+, RHEL 9+ |
Frequently Asked Questions
What will I learn here?
This page covers the core concepts and techniques you need to understand the topic and progress confidently to the next lesson.
How should I use this page?
Start with the overview, then follow the section links to deepen your understanding. Use the table of contents on the right to jump to specific sections.
What should I read next?
Use the navigation below to continue to the next lesson or explore related topics.