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#

  1. Overview
  2. Architecture
  3. Installation
  4. Basic Configuration
  5. Match Modules
  6. NAT and Masquerading
  7. Rule Management
  8. Saving and Restoring Rules
  9. Testing and Verification
  10. Troubleshooting
  11. See Also
  12. 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-nft compatibility 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:

TablePurposeBuilt-in Chains
filterPacket filtering (default table)INPUT, FORWARD, OUTPUT
natNetwork address translationPREROUTING, INPUT, OUTPUT, POSTROUTING
mangleSpecialized packet alteration (TOS, TTL, mark)PREROUTING, INPUT, FORWARD, OUTPUT, POSTROUTING
rawExemptions from connection trackingPREROUTING, 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:

TargetDescription
ACCEPTAllow the packet through
DROPSilently discard the packet
REJECTDiscard and send an ICMP error to the sender
LOGLog packet details, then continue to the next rule
RETURNStop processing in the current chain, return to the calling chain
DNATDestination NAT (nat table, PREROUTING/OUTPUT)
SNATSource NAT (nat table, POSTROUTING)
MASQUERADEDynamic source NAT (nat table, POSTROUTING)
REDIRECTRedirect to local port (nat table, PREROUTING/OUTPUT)

2.4. Connection Tracking States#

The conntrack module tracks connection states, enabling stateful filtering:

StateDescription
NEWFirst packet of a new connection
ESTABLISHEDPacket belongs to an already established connection
RELATEDPacket is related to an existing connection (e.g., ICMP error, FTP data channel)
INVALIDPacket does not match any known connection
UNTRACKEDPacket 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:

FlagDescription
-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 iptables

Check which backend is active:

# Shows "nf_tables" for iptables-nft or "legacy" for native iptables
iptables -V

4. 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 -Z

4.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 ACCEPT

Warning: 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 ACCEPT

4.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 ACCEPT

4.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-unreachable

5. 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 ACCEPT

5.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 ACCEPT

5.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 ACCEPT

5.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 ACCEPT

5.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 --system

6.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 ACCEPT

6.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 ACCEPT

6.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.5

7. 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 -S

7.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 3

7.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 ACCEPT

8. 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.v4

8.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.service

8.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.service

8.4. Manual Save/Restore#

Works on any distribution:

# Save
sudo iptables-save > /root/iptables-backup.rules

# Restore
sudo iptables-restore < /root/iptables-backup.rules

9. 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 -L

10. Troubleshooting#

IssueCauseSolution
Rules lost after rebootRules not saved to diskInstall persistence package for your distro (see section 8)
Locked out of remote serverINPUT policy set to DROP before SSH allow ruleUse out-of-band console; add SSH rule before setting policy
Port open but service unreachableService not listening or binding to wrong addressCheck ss -tlnp to verify the service is listening
Masquerading not workingIP forwarding disabledSet net.ipv4.ip_forward=1 via sysctl
Rules not matching (zero counters)Rules in wrong order or wrong chainCheck rule order with iptables -L --line-numbers -n; rules are evaluated top-down
Docker overrides iptables rulesDocker manages its own FORWARD and nat chainsUse DOCKER-USER chain for custom rules; do not flush all chains blindly
-m state deprecated warningsOld state module used instead of conntrackReplace -m state --state with -m conntrack --ctstate
Duplicate rules accumulatingScript appends rules without flushing firstFlush chains at the start of your script, or check before adding
IPv6 traffic bypasses rulesOnly ipv4 rules configuredUse ip6tables for IPv6 rules, or use nftables inet family for dual-stack

11. See Also#

  • NFTables
  • FirewallD
  • UFW

12. Sources#