Legacy Linux packet filtering utility that configures Netfilter kernel rules; superseded by nftables but still widely encountered through compatibility layers.
Deprecation Notice: IPTables is the legacy firewall interface. The Linux kernel's Netfilter subsystem now uses nftables as the default framework. Most modern distributions (Debian 10+, RHEL/CentOS 8+, Fedora 18+, Arch Linux) ship nftables and provide
iptables-nft, a compatibility wrapper that translates iptables syntax into nftables rules. For new deployments, use nftables directly or a higher-level tool like firewalld. This guide remains useful for maintaining legacy systems and understanding the iptables compatibility layer.
Addresses below are RFC 5737 documentation ranges or placeholders - swap in your own.
Table of Contents#
- Overview
- Architecture
- 2.1 Tables
- 2.2 Chains
- 2.3 Targets (Actions)
- 2.4 Connection Tracking States
- 2.5 Rule Syntax
- Installation
- Basic Configuration
- Match Modules
- 5.1 conntrack (State Matching)
- 5.2 multiport
- 5.3 limit
- 5.4 iprange
- 5.5 comment
- NAT and Masquerading
- Rule Management
- 7.1 Listing Rules
- 7.2 Deleting Rules
- 7.3 Inserting Rules
- Saving and Restoring Rules
- 8.1 Debian/Ubuntu
- 8.2 RHEL/CentOS/Fedora
- 8.3 Arch Linux
- 8.4 Manual Save/Restore
- Testing and Verification
- Troubleshooting
- See Also
- Sources
1. Overview#
IPTables is a user-space utility that configures the IP packet filter rules of the Linux kernel firewall, implemented as Netfilter modules. Rules are organized into tables containing chains that define how to process network traffic at various points in the packet flow. While nftables has replaced it as the default framework, iptables remains relevant because:
- Many existing systems still run native iptables
- The
iptables-nftcompatibility layer translates iptables commands into nftables rules - Container runtimes (Docker, Podman) and Kubernetes still generate iptables rules
- Legacy documentation, scripts, and tutorials use iptables syntax
2. Architecture#
2.1. Tables#
IPTables organizes rules into four tables, each serving a different purpose:
| Table | Purpose | Built-in Chains |
|---|---|---|
| filter | Packet filtering (default table) | INPUT, FORWARD, OUTPUT |
| nat | Network address translation | PREROUTING, INPUT, OUTPUT, POSTROUTING |
| mangle | Specialized packet alteration (TOS, TTL, mark) | PREROUTING, INPUT, FORWARD, OUTPUT, POSTROUTING |
| raw | Exemptions from connection tracking | PREROUTING, OUTPUT |
2.2. Chains#
Chains are lists of rules checked sequentially. Each table has built-in chains corresponding to different points in the packet path:
- PREROUTING - before routing decision (nat, mangle, raw)
- INPUT - packets destined for the local system (filter, mangle, nat)
- FORWARD - packets being routed through the system (filter, mangle)
- OUTPUT - packets originating from the local system (filter, nat, mangle, raw)
- POSTROUTING - after routing decision, before leaving (nat, mangle)
Custom chains can be created for organizational purposes and jumped to from built-in chains.
2.3. Targets (Actions)#
Targets determine what happens when a packet matches a rule:
| Target | Description |
|---|---|
ACCEPT | Allow the packet through |
DROP | Silently discard the packet |
REJECT | Discard and send an ICMP error to the sender |
LOG | Log packet details, then continue to the next rule |
RETURN | Stop processing in the current chain, return to the calling chain |
DNAT | Destination NAT (nat table, PREROUTING/OUTPUT) |
SNAT | Source NAT (nat table, POSTROUTING) |
MASQUERADE | Dynamic source NAT (nat table, POSTROUTING) |
REDIRECT | Redirect to local port (nat table, PREROUTING/OUTPUT) |
2.4. Connection Tracking States#
The conntrack module tracks connection states, enabling stateful filtering:
| State | Description |
|---|---|
NEW | First packet of a new connection |
ESTABLISHED | Packet belongs to an already established connection |
RELATED | Packet is related to an existing connection (e.g., ICMP error, FTP data channel) |
INVALID | Packet does not match any known connection |
UNTRACKED | Packet was exempted from tracking (raw table) |
Stateful rules are the foundation of a secure firewall. Always allow ESTABLISHED,RELATED traffic early in the INPUT chain for performance and correctness.
2.5. Rule Syntax#
The general syntax for an iptables command:
iptables [-t <table>] -A <chain> [match options] -j <target>Common flags:
| Flag | Description |
|---|---|
-t <table> | Specify table (default: filter) |
-A <chain> | Append rule to chain |
-I <chain> [num] | Insert rule at position (default: 1) |
-D <chain> [rule/num] | Delete rule |
-p <protocol> | Match protocol (tcp, udp, icmp) |
-s <source> | Source IP address or CIDR |
-d <destination> | Destination IP address or CIDR |
--sport <port> | Source port |
--dport <port> | Destination port |
-i <interface> | Input interface |
-o <interface> | Output interface |
-m <module> | Load match module |
-j <target> | Jump to target |
3. Installation#
IPTables comes preinstalled on most Linux distributions. To install or verify:
# Debian/Ubuntu
sudo apt install iptables
# RHEL/CentOS/Fedora
sudo dnf install iptables iptables-services
# Arch Linux
sudo pacman -S iptables-nft # nftables compatibility layer (recommended)
# or
sudo pacman -S iptables # legacy iptablesCheck which backend is active:
# Shows "nf_tables" for iptables-nft or "legacy" for native iptables
iptables -V4. Basic Configuration#
4.1. Flushing Existing Rules#
Before applying a new ruleset, clear all existing rules:
# Flush all rules in all tables
iptables -F
iptables -t nat -F
iptables -t mangle -F
iptables -t raw -F
# Delete all custom chains
iptables -X
iptables -t nat -X
iptables -t mangle -X
# Zero all counters
iptables -Z4.2. Setting Default Policies#
Set secure defaults: drop all incoming and forwarded traffic, allow outgoing:
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPTWarning: Setting INPUT to DROP before allowing SSH will lock you out of a remote system. Always add an SSH allow rule first, or apply the policy and rules in a single script.
4.3. Essential Baseline Rules#
These rules should appear at the top of the INPUT chain on every system:
# Allow loopback traffic
iptables -A INPUT -i lo -j ACCEPT
# Allow established and related connections (stateful filtering)
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# Drop invalid packets
iptables -A INPUT -m conntrack --ctstate INVALID -j DROP
# Allow ICMP (ping) - optional but recommended for diagnostics
iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT4.4. Allowing Services#
# Allow SSH
iptables -A INPUT -p tcp --dport 22 -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 DNS
iptables -A INPUT -p tcp --dport 53 -j ACCEPT
iptables -A INPUT -p udp --dport 53 -j ACCEPT
# Allow from a specific subnet only
iptables -A INPUT -p tcp -s 192.0.2.0/24 --dport 22 -j ACCEPT4.5. Blocking Traffic#
# Block a specific IP
iptables -A INPUT -s <ip-address> -j DROP
# Block a subnet
iptables -A INPUT -s 198.51.100.0/24 -j DROP
# Reject (send ICMP port-unreachable) instead of silent drop
iptables -A INPUT -s <ip-address> -j REJECT --reject-with icmp-port-unreachable5. Match Modules#
Modules extend iptables matching capabilities. Load them with -m <module>.
5.1. conntrack (State Matching)#
The modern replacement for the older -m state module:
# Allow established and related traffic
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# Allow only new connections on SSH (combine with stateful baseline)
iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT5.2. multiport#
Match multiple ports in a single rule (up to 15 ports):
# Allow multiple TCP ports
iptables -A INPUT -p tcp -m multiport --dports 22,80,443,8080 -j ACCEPT
# Allow multiple UDP ports
iptables -A INPUT -p udp -m multiport --dports 53,123,500,4500 -j ACCEPT5.3. limit#
Rate-limit matching to prevent floods:
# Limit SSH connection attempts to 3 per minute
iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -m limit --limit 3/min --limit-burst 3 -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -j DROP
# Limit ICMP to 1 per second (burst of 4)
iptables -A INPUT -p icmp --icmp-type echo-request -m limit --limit 1/s --limit-burst 4 -j ACCEPT5.4. iprange#
Match a range of IP addresses:
# Allow a range of source IPs
iptables -A INPUT -m iprange --src-range 192.0.2.100-192.0.2.200 -p tcp --dport 22 -j ACCEPT5.5. comment#
Add comments to rules for documentation:
iptables -A INPUT -p tcp --dport 22 -j ACCEPT -m comment --comment "Allow SSH access"6. NAT and Masquerading#
NAT rules are added to the nat table. IP forwarding must be enabled:
# Enable IP forwarding (temporary)
sudo sysctl -w net.ipv4.ip_forward=1
# Enable IP forwarding (permanent)
echo "net.ipv4.ip_forward = 1" | sudo tee /etc/sysctl.d/99-forward.conf
sudo sysctl --system6.1. Masquerading#
Masquerading dynamically rewrites the source IP to the outgoing interface's address, suitable for dynamic IPs:
# Allow private network to access the internet via eth0
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
# Allow forwarded traffic
iptables -A FORWARD -i eth1 -o eth0 -j ACCEPT
iptables -A FORWARD -i eth0 -o eth1 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT6.2. Port Forwarding (DNAT)#
Redirect incoming traffic to an internal host:
# Forward port 80 to internal server 192.0.2.10:8080
iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 192.0.2.10:8080
# Allow the forwarded traffic through
iptables -A FORWARD -p tcp -d 192.0.2.10 --dport 8080 -m conntrack --ctstate NEW -j ACCEPT6.3. Source NAT (SNAT)#
For static IP addresses, SNAT is more efficient than masquerading:
# Rewrite source to a fixed IP
iptables -t nat -A POSTROUTING -o eth0 -j SNAT --to-source 203.0.113.57. Rule Management#
7.1. Listing Rules#
# List all rules with packet counters
iptables -L -v -n
# List with line numbers (needed for deletion by number)
iptables -L INPUT --line-numbers -n
# List a specific table
iptables -t nat -L -v -n
# Show rules in iptables-save format (most useful for scripting)
iptables -S7.2. Deleting Rules#
# Delete by exact rule specification
iptables -D INPUT -p tcp --dport 22 -j ACCEPT
# Delete by line number
iptables -L INPUT --line-numbers -n
iptables -D INPUT 37.3. Inserting Rules#
# Insert at the top of the INPUT chain (position 1)
iptables -I INPUT 1 -p tcp --dport 22 -j ACCEPT
# Insert at a specific position
iptables -I INPUT 3 -p tcp --dport 443 -j ACCEPT8. Saving and Restoring Rules#
IPTables rules are stored in kernel memory and lost on reboot. Persistence methods vary by distribution.
8.1. Debian/Ubuntu#
# Install the persistence package
sudo apt install iptables-persistent
# Save current rules
sudo iptables-save > /etc/iptables/rules.v4
sudo ip6tables-save > /etc/iptables/rules.v6
# Rules auto-load on boot via netfilter-persistent.service
# Manual restore:
sudo iptables-restore < /etc/iptables/rules.v48.2. RHEL/CentOS/Fedora#
# Using iptables-services
sudo dnf install iptables-services
sudo systemctl enable iptables
# Save
sudo service iptables save
# or
sudo iptables-save > /etc/sysconfig/iptables
# Rules auto-load on boot via iptables.service8.3. Arch Linux#
# Save rules
sudo iptables-save -f /etc/iptables/iptables.rules
# Enable auto-restore on boot
sudo systemctl enable iptables.service
# For ip6tables:
sudo ip6tables-save -f /etc/iptables/ip6tables.rules
sudo systemctl enable ip6tables.service8.4. Manual Save/Restore#
Works on any distribution:
# Save
sudo iptables-save > /root/iptables-backup.rules
# Restore
sudo iptables-restore < /root/iptables-backup.rules9. Testing and Verification#
# List all rules with counters (check packet/byte counts to verify hits)
iptables -L -v -n
# Watch counters in real time
watch -n 1 'iptables -L INPUT -v -n'
# Test port access from another host
nc -zv <server-ip> 22
# Trace packet path through iptables (requires raw table TRACE target)
iptables -t raw -A PREROUTING -p tcp --dport 22 -j TRACE
# Then check: journalctl -k | grep TRACE
# Check connection tracking table
conntrack -L10. Troubleshooting#
| Issue | Cause | Solution |
|---|---|---|
| Rules lost after reboot | Rules not saved to disk | Install persistence package for your distro (see section 8) |
| Locked out of remote server | INPUT policy set to DROP before SSH allow rule | Use out-of-band console; add SSH rule before setting policy |
| Port open but service unreachable | Service not listening or binding to wrong address | Check ss -tlnp to verify the service is listening |
| Masquerading not working | IP forwarding disabled | Set net.ipv4.ip_forward=1 via sysctl |
| Rules not matching (zero counters) | Rules in wrong order or wrong chain | Check rule order with iptables -L --line-numbers -n; rules are evaluated top-down |
| Docker overrides iptables rules | Docker manages its own FORWARD and nat chains | Use DOCKER-USER chain for custom rules; do not flush all chains blindly |
-m state deprecated warnings | Old state module used instead of conntrack | Replace -m state --state with -m conntrack --ctstate |
| Duplicate rules accumulating | Script appends rules without flushing first | Flush chains at the start of your script, or check before adding |
| IPv6 traffic bypasses rules | Only ipv4 rules configured | Use ip6tables for IPv6 rules, or use nftables inet family for dual-stack |
11. See Also#
- NFTables
- FirewallD
- UFW
12. Sources#
- Netfilter Project - iptables
- iptables-nft: The iptables Compatibility Layer
- Arch Wiki - iptables
man iptablesman iptables-extensionsman iptables-saveman iptables-restore