Home DevOps iptables: Linux Firewall Configuration and Management — Rules That Don't Burn You at 3 AM
Advanced 6 min · July 18, 2026

iptables: Linux Firewall Configuration and Management — Rules That Don't Burn You at 3 AM

Master iptables Linux firewall configuration and management with production-tested rules, debugging techniques, and real incident stories from 15+ years in the trenches..

N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Written from production experience, not tutorials.

Follow
Production
production tested
July 18, 2026
last updated
2,466
articles · all by Naren
Before you start⏱ 35 min
  • Basic Linux command-line proficiency
  • Understanding of TCP/IP and network layers
  • Familiarity with netfilter kernel subsystem
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer

Use iptables -L -n -v to list current rules with packet/byte counts. To block all incoming traffic except SSH on port 22: iptables -P INPUT DROP; iptables -A INPUT -p tcp --dport 22 -j ACCEPT; iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT. Always test with a backup SSH session open.

✦ Definition~90s read
What is iptables?

iptables is the user-space utility program that allows a system administrator to configure the IP packet filter rules of the Linux kernel firewall, implemented as different Netfilter modules. It manages rules in tables (filter, nat, mangle, raw, security) containing chains of rules that match packets and specify actions.

Imagine your server is a nightclub.
Plain-English First

Imagine your server is a nightclub. iptables is the bouncer at the door. You give the bouncer a list of rules: 'Let in anyone on the VIP list (port 22), but block everyone else unless they're already inside (established connections).' The bouncer checks each person (packet) against the list, top to bottom, and the first match decides their fate. If no rule matches, the bouncer follows the default policy — either let everyone in (ACCEPT) or turn everyone away (DROP).

I've seen a single misplaced iptables rule take down a production payment gateway at 2 AM on a Saturday. The rule was meant to block a malicious IP, but a missing -s flag made it block all traffic. The on-call engineer spent 45 minutes SSH'd into the DR site, sweating, because the primary's iptables had locked out even the monitoring system. That's the power of iptables: one line can save your bacon or deep-fry it. This isn't a tutorial for hobbyists — it's a survival guide for engineers who manage firewalls in production, where a typo means a P0 incident.

Netfilter Architecture: The Kernel's Packet Processing Pipeline

Before you touch iptables, you need to understand netfilter — the kernel framework that iptables talks to. Netfilter hooks into five key points in the network stack: PREROUTING, INPUT, FORWARD, OUTPUT, and POSTROUTING. Each hook corresponds to a chain in iptables. When a packet arrives, it traverses these hooks in order. The filter table's INPUT chain runs at the INPUT hook, deciding whether to deliver the packet locally. The nat table's PREROUTING chain runs at the PREROUTING hook, before routing decisions. Understanding this flow is critical: a rule in the wrong table or chain won't work as expected. For example, DNAT (destination NAT) must be in the PREROUTING chain of the nat table, not in the filter table. I've seen engineers waste hours trying to DNAT in the FORWARD chain — it doesn't work because FORWARD runs after routing.

netfilter_hooks.devopsDEVOPS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// io.thecodeforge — DevOps tutorial

# Show the packet flow through netfilter hooks
# This is a conceptual diagram, not runnable code

# Packet flow for incoming packet destined to local process:
# NIC -> PREROUTING (raw, mangle, nat) -> Routing Decision -> INPUT (mangle, filter) -> Local Process

# Packet flow for forwarded packet:
# NIC -> PREROUTING (raw, mangle, nat) -> Routing Decision -> FORWARD (mangle, filter) -> POSTROUTING (mangle, nat) -> NIC

# Packet flow for locally generated outgoing packet:
# Local Process -> Routing Decision -> OUTPUT (raw, mangle, nat, filter) -> POSTROUTING (mangle, nat) -> NIC

# Verify current hook traversal with trace: iptables -t raw -A PREROUTING -j TRACE
# Then check kernel log: dmesg -w | grep TRACE
Output
No output — conceptual diagram.
🔥Senior Shortcut:
Use iptables -t raw -A PREROUTING -j TRACE to trace packet flow through all chains. Then watch dmesg -w | grep TRACE. This is invaluable for debugging why a rule isn't matching.

Tables and Chains: Where Rules Live and Why It Matters

iptables has five tables: filter (default), nat, mangle, raw, and security. Each table has built-in chains. The filter table has INPUT, FORWARD, and OUTPUT chains. The nat table has PREROUTING, OUTPUT, and POSTROUTING. Mangle has all five. Raw has PREROUTING and OUTPUT. Security is for SELinux. Most people only use filter, but ignoring the others leads to brittle setups. For example, rate-limiting SYN packets should be done in the mangle table's PREROUTING chain, not filter's INPUT, because mangle runs before the first routing decision and can drop packets before they consume connection tracking resources. I once saw a DDoS knock over a server because the rate limit was in filter INPUT — the connection tracking table filled up before the rule was evaluated. Move it to mangle PREROUTING and the server survived.

list_tables.devopsDEVOPS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// io.thecodeforge — DevOps tutorial

# List all rules in all tables
for table in filter nat mangle raw security; do
  echo "=== Table: $table ==="
  iptables -t $table -L -n -v --line-numbers 2>/dev/null || echo "Table $table not available"
done

# Example output for filter table (abbreviated):
# === Table: filter ===
# Chain INPUT (policy DROP 0 packets, 0 bytes)
# num   pkts bytes target     prot opt in     out     source               destination
# 1        0     0 ACCEPT     all  --  lo     *       0.0.0.0/0            0.0.0.0/0
# 2      100  5000 ACCEPT     all  --  *      *       0.0.0.0/0            0.0.0.0/0            state ESTABLISHED,RELATED
# 3       10   600 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:22
# Chain FORWARD (policy DROP 0 packets, 0 bytes)
# Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
Output
=== Table: filter ===
Chain INPUT (policy DROP 0 packets, 0 bytes)
num pkts bytes target prot opt in out source destination
1 0 0 ACCEPT all -- lo * 0.0.0.0/0 0.0.0.0/0
2 100 5000 ACCEPT all -- * * 0.0.0.0/0 0.0.0.0/0 state ESTABLISHED,RELATED
3 10 600 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:22
Chain FORWARD (policy DROP 0 packets, 0 bytes)
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
=== Table: nat ===
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
=== Table: mangle ===
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
=== Table: raw ===
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
=== Table: security ===
Table security not available
⚠ Production Trap:
Never put connection tracking (conntrack) rules in the filter table. Use the raw table with -j NOTRACK to bypass conntrack for high-traffic ports. Otherwise, conntrack table exhaustion will drop valid packets silently.

Rule Syntax: The Devil in the Flags

An iptables rule is a chain of matches and one target. The basic structure: iptables -A <chain> <matches> -j <target>. Matches include -s (source), -d (destination), -p (protocol), --dport (destination port), -i (input interface), -o (output interface), -m state (connection state), -m limit (rate limit), and many more. The target can be ACCEPT, DROP, REJECT, LOG, RETURN, or a user-defined chain. The order of matches matters: put the most specific matches first to short-circuit evaluation. For example, -m state --state ESTABLISHED,RELATED -j ACCEPT should be near the top to allow return traffic. A common mistake is putting -j DROP before allowing established connections — that kills all return traffic. Also, remember that -s and -d can be IP addresses or CIDR ranges. Never use hostnames; iptables resolves them at rule insertion time, and if DNS fails, the rule won't apply.

basic_rules.devopsDEVOPS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// io.thecodeforge — DevOps tutorial

# Allow all loopback traffic (essential for local services)
iptables -A INPUT -i lo -j ACCEPT

# Allow established and related connections (so return traffic works)
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# Allow SSH from specific management subnet
iptables -A INPUT -p tcp -s 10.0.0.0/8 --dport 22 -j ACCEPT

# Allow HTTP/HTTPS from anywhere
iptables -A INPUT -p tcp -m multiport --dports 80,443 -j ACCEPT

# Log and drop everything else (log first, then drop)
iptables -A INPUT -j LOG --log-prefix "iptables-dropped: " --log-level 4
iptables -A INPUT -j DROP

# Set default policies (should be done after rules)
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT

# Verify: iptables -L -n -v
Output
Chain INPUT (policy DROP 0 packets, 0 bytes)
num pkts bytes target prot opt in out source destination
1 0 0 ACCEPT all -- lo * 0.0.0.0/0 0.0.0.0/0
2 0 0 ACCEPT all -- * * 0.0.0.0/0 0.0.0.0/0 state ESTABLISHED,RELATED
3 0 0 ACCEPT tcp -- * * 10.0.0.0/8 0.0.0.0/0 tcp dpt:22
4 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 multiport dports 80,443
5 0 0 LOG all -- * * 0.0.0.0/0 0.0.0.0/0 LOG flags 0 level 4 prefix "iptables-dropped: "
6 0 0 DROP all -- * * 0.0.0.0/0 0.0.0.0/0
Chain FORWARD (policy DROP 0 packets, 0 bytes)
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
⚠ Never Do This:
Don't set default policy to DROP before adding allow rules. If you do iptables -P INPUT DROP first, then add rules, there's a window where you're locked out. Always add rules first, then change policy.

Connection Tracking: The Hidden State Machine

Connection tracking (conntrack) is what makes stateful firewalling possible. The -m state module uses conntrack to classify packets as NEW, ESTABLISHED, RELATED, or INVALID. Without it, you'd have to open ephemeral ports for return traffic. Conntrack tracks all connections in a table in kernel memory. The table size is configurable via net.netfilter.nf_conntrack_max. Default is often 65536, which is too low for busy servers. When the table fills, new connections are dropped silently. I've seen this happen on a web server handling 10k concurrent connections — the symptom was intermittent timeouts. The fix: increase nf_conntrack_max and reduce nf_conntrack_tcp_timeout_established from the default 5 days to something sane like 1 hour. Also, use the raw table to bypass conntrack for high-traffic UDP services (like DNS) with -j NOTRACK.

conntrack_tuning.devopsDEVOPS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// io.thecodeforge — DevOps tutorial

# Check current conntrack usage
cat /proc/net/nf_conntrack | wc -l
sysctl net.netfilter.nf_conntrack_max
sysctl net.netfilter.nf_conntrack_count

# Increase conntrack table size (persist in /etc/sysctl.conf)
sysctl -w net.netfilter.nf_conntrack_max=262144

# Reduce established timeout from default 432000 seconds (5 days) to 3600 (1 hour)
sysctl -w net.netfilter.nf_conntrack_tcp_timeout_established=3600

# Bypass conntrack for DNS (UDP 53) using raw table
iptables -t raw -A PREROUTING -p udp --dport 53 -j NOTRACK
iptables -t raw -A OUTPUT -p udp --sport 53 -j NOTRACK

# Monitor conntrack in real time
conntrack -E -o timestamp
Output
262144
262144
(no output from conntrack -E until events occur)
🔥Interview Gold:
Question: 'How does iptables handle return traffic for a connection initiated from inside the network?' Answer: Conntrack marks the outgoing packet as NEW, creates an entry, and then allows the return packet because it matches ESTABLISHED state. The rule -m state --state ESTABLISHED,RELATED -j ACCEPT is what permits it.

NAT: Masquerading, DNAT, and the Routing Trap

Network Address Translation in iptables is done in the nat table. SNAT (source NAT) is used for masquerading — hiding internal IPs behind a single public IP. DNAT (destination NAT) is used for port forwarding. The classic setup: iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE for SNAT. For DNAT: iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j DNAT --to-destination 192.168.1.100:8080. A common mistake is forgetting to enable IP forwarding: sysctl net.ipv4.ip_forward=1. Without it, DNAT won't work. Also, DNAT only changes the destination — the return traffic must be un-DNATed automatically by conntrack. If you're doing DNAT to a different port, the return traffic's source port is the original port, not the translated one. This usually works, but can break with some protocols. For hairpin NAT (accessing a DNATed service from inside the same network), you need additional rules.

nat_rules.devopsDEVOPS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// io.thecodeforge — DevOps tutorial

# Enable IP forwarding (must be persistent in /etc/sysctl.conf)
sysctl -w net.ipv4.ip_forward=1

# SNAT: Masquerade all outgoing traffic on eth0 (typical for home router)
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

# DNAT: Forward incoming HTTP to internal server 192.168.1.100:8080
iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j DNAT --to-destination 192.168.1.100:8080

# Allow forwarded traffic through filter table (FORWARD chain)
iptables -A FORWARD -i eth0 -o eth1 -p tcp --dport 8080 -d 192.168.1.100 -j ACCEPT
iptables -A FORWARD -i eth1 -o eth0 -m state --state ESTABLISHED,RELATED -j ACCEPT

# Hairpin NAT: Allow internal clients to access the DNATed service using the public IP
iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -d 192.168.1.100 -p tcp --dport 8080 -j SNAT --to-source 192.168.1.1

# Verify NAT rules
iptables -t nat -L -n -v
Output
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
num pkts bytes target prot opt in out source destination
1 0 0 DNAT tcp -- eth0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 to:192.168.1.100:8080
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
num pkts bytes target prot opt in out source destination
1 0 0 MASQUERADE all -- * eth0 0.0.0.0/0 0.0.0.0/0
2 0 0 SNAT all -- * * 192.168.1.0/24 192.168.1.100 tcp dpt:8080 to:192.168.1.1
⚠ The Classic Bug:
Forgetting to add FORWARD rules when using DNAT. The nat table changes the packet, but the filter table's FORWARD chain still evaluates it. If FORWARD policy is DROP and no ACCEPT rule exists, the packet is dropped. Always add FORWARD rules for DNATed traffic.

Rate Limiting and DDoS Mitigation with iptables

iptables can do basic rate limiting using the limit and connlimit modules. The limit module matches at a certain rate (e.g., 10/second) and can be used to throttle ICMP or SYN packets. The connlimit module limits the number of concurrent connections per IP. For SYN flood mitigation, use -m limit --limit 10/s --limit-burst 20 in the mangle table's PREROUTING chain. This drops excess SYN packets before they reach conntrack. For per-IP connection limits: -m connlimit --connlimit-above 100 --connlimit-mask 32 -j DROP. This is useful for preventing a single client from exhausting server resources. However, iptables is not a full DDoS mitigation solution — it's a first line of defense. For large attacks, you need a dedicated appliance or cloud-based protection. Also, be careful with connlimit on NATed clients; they all share the same public IP, so you might block legitimate users.

rate_limiting.devopsDEVOPS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// io.thecodeforge — DevOps tutorial

# Rate limit incoming SSH connections to prevent brute force
# Allow 3 new connections per minute, burst of 5
iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m limit --limit 3/minute --limit-burst 5 -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -m state --state NEW -j DROP

# Limit SYN packets to 100 per second (place in mangle PREROUTING)
iptables -t mangle -A PREROUTING -p tcp --syn -m limit --limit 100/s --limit-burst 200 -j ACCEPT
iptables -t mangle -A PREROUTING -p tcp --syn -j DROP

# Limit concurrent connections per IP to 50 for HTTP
iptables -A INPUT -p tcp --dport 80 -m connlimit --connlimit-above 50 --connlimit-mask 32 -j REJECT --reject-with tcp-reset

# Limit total connections to 1000
iptables -A INPUT -p tcp --dport 80 -m connlimit --connlimit-above 1000 -j DROP

# Log dropped SYN packets
iptables -A INPUT -p tcp --syn -j LOG --log-prefix "SYN-DROP: " --log-level 4
Output
No output — rules are silent until triggered.
🔥Senior Shortcut:
Use iptables -A INPUT -p tcp --syn -m limit --limit 1/s -j LOG --log-prefix "SYN: " to log SYN packets at a low rate without flooding logs. Then analyze with grep SYN: /var/log/kern.log.

Logging and Debugging: Seeing What the Firewall Sees

The LOG target sends packet info to the kernel log (viewable via dmesg or /var/log/kern.log). Use --log-prefix to tag entries for easy grepping. The ULOG target sends logs to userspace via netlink, which is more efficient. For debugging, use the TRACE target in the raw table to trace packet traversal through all chains. This is invaluable when a rule isn't matching as expected. Also, iptables -L -n -v shows packet and byte counters per rule. If a rule's counter isn't incrementing, the packet isn't reaching that rule — check previous rules or chain order. Another trick: insert a temporary LOG rule at the top of a chain to see if packets arrive. Remember to remove it afterwards.

debugging.devopsDEVOPS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// io.thecodeforge — DevOps tutorial

# Add a LOG rule at the top of INPUT to see all incoming packets
iptables -I INPUT 1 -j LOG --log-prefix "INPUT-PACKET: " --log-level 4

# Watch kernel log in real time
dmesg -w | grep INPUT-PACKET

# Use TRACE to follow packet through chains
iptables -t raw -A PREROUTING -p tcp --dport 80 -j TRACE
# Then trigger a packet and check dmesg
curl http://localhost:80
dmesg | grep TRACE

# Remove TRACE rule after debugging
iptables -t raw -D PREROUTING -p tcp --dport 80 -j TRACE

# Check rule counters
iptables -L -n -v --line-numbers

# Reset counters for a specific chain
iptables -Z INPUT
Output
[12345.678901] INPUT-PACKET: IN=eth0 OUT= MAC=... SRC=10.0.0.2 DST=10.0.0.1 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=12345 DF PROTO=TCP SPT=54321 DPT=80 WINDOW=65535 RES=0x00 SYN URGP=0
[12345.678902] TRACE: raw:PREROUTING:rule:1 IN=eth0 OUT= ...
[12345.678903] TRACE: mangle:PREROUTING:rule:1 IN=eth0 OUT= ...
[12345.678904] TRACE: nat:PREROUTING:rule:1 IN=eth0 OUT= ...
[12345.678905] TRACE: filter:INPUT:rule:1 IN=eth0 OUT= ...
⚠ Production Trap:
Don't leave LOG rules in production for long. They can generate massive log volumes and slow down packet processing. Always remove debug rules after troubleshooting.

Persistence: Making Rules Survive Reboot

iptables rules are ephemeral — they vanish on reboot. To make them persistent, save them to a file and restore on boot. On Debian/Ubuntu, use iptables-save > /etc/iptables/rules.v4 and install iptables-persistent. On RHEL/CentOS, use service iptables save which writes to /etc/sysconfig/iptables. The modern way is to use iptables-restore with a systemd service. Never rely on startup scripts that re-run iptables commands — they're fragile. Instead, maintain a single rules file versioned in your config management. Also, always test a new ruleset by saving the current one first: iptables-save > /root/iptables.backup.$(date +%s). If something breaks, restore with iptables-restore < /root/iptables.backup....

persistence.devopsDEVOPS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// io.thecodeforge — DevOps tutorial

# Save current rules to file
iptables-save > /etc/iptables/rules.v4

# Restore rules from file
iptables-restore < /etc/iptables/rules.v4

# Create a systemd service to restore rules on boot (if not using iptables-persistent)
cat > /etc/systemd/system/iptables-restore.service <<EOF
[Unit]
Description=Restore iptables rules
Before=network-pre.target
Wants=network-pre.target

[Service]
Type=oneshot
ExecStart=/sbin/iptables-restore /etc/iptables/rules.v4
ExecReload=/sbin/iptables-restore /etc/iptables/rules.v4
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target
EOF

systemctl enable iptables-restore
systemctl start iptables-restore

# Backup current rules before any change
iptables-save > /root/iptables.backup.$(date +%s)

# Atomic reload: flush and restore in one command
iptables-restore <<< "$(iptables-save | sed 's/^-A/-I/') "  # This is dangerous; better to use a file
Output
No output on success.
🔥Senior Shortcut:
Use iptables-apply (from iptables-persistent) to safely apply new rules with a timeout. If you lose connectivity, the old rules are restored automatically after the timeout. Example: iptables-apply -t 60 newrules.v4.

Performance: How Rules Affect Throughput

iptables rules are evaluated in linear order. The more rules, the slower the packet processing. For high-throughput environments (10Gbps+), minimize the number of rules and use the most specific matches first. Use iptables -L -n -v to identify rules with high packet counts — those are your hot paths. Consider moving frequently matched rules to the top. Also, use the -m set module with ipset for large IP blocklists — it uses hash tables and is O(1) instead of O(n). For connection tracking, ensure nf_conntrack_max is adequate and timeouts are tuned. On multi-core systems, iptables uses a single lock, which can become a bottleneck. Newer nftables (the successor to iptables) has better concurrency. If you're pushing serious traffic, consider migrating to nftables or using XDP/BPF for packet filtering.

performance_check.devopsDEVOPS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// io.thecodeforge — DevOps tutorial

# Check rule hit counts to identify hot paths
iptables -L -n -v --line-numbers | head -20

# Example output showing a rule with high packet count:
# num   pkts bytes target     prot opt in     out     source               destination
# 1   500000  50M ACCEPT     all  --  *      *       0.0.0.0/0            0.0.0.0/0            state ESTABLISHED,RELATED
# 2   100000  10M ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:80

# Use ipset for large blocklists (e.g., 10,000 IPs)
ipset create blocklist hash:ip maxelem 100000
iptables -A INPUT -m set --match-set blocklist src -j DROP

# Benchmark packet processing (using perf or simply measure throughput)
# Example: send 1 million packets and measure CPU usage
# Not a command, but a methodology: use `sar -n DEV 1` to see pps (packets per second)
Output
num pkts bytes target prot opt in out source destination
1 500000 50M ACCEPT all -- * * 0.0.0.0/0 0.0.0.0/0 state ESTABLISHED,RELATED
2 100000 10M ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:80
3 0 0 DROP all -- * * 0.0.0.0/0 0.0.0.0/0
🔥Interview Gold:
Question: 'How does iptables performance degrade with rule count?' Answer: Linearly. Each packet traverses the chain until a match. For large rulesets, use ipset for O(1) lookups. Also, the iptables kernel lock can become a bottleneck on multi-core systems.

When Not to Use iptables: Modern Alternatives

iptables is legacy. The modern replacement is nftables, which offers better performance, atomic rule replacement, and a more consistent syntax. nftables also supports sets, maps, and concatenations natively. For container environments, consider using CNI plugins like Calico or Cilium that use eBPF for high-performance networking. For host-based firewalls, firewalld (which uses nftables under the hood) provides a dynamic zone-based interface. If you're starting a new project, use nftables. If you're maintaining legacy systems, iptables is still fine, but plan to migrate. The migration is straightforward: iptables-translate can convert rules. However, for simple setups, iptables is still widely used and understood. Don't rewrite a working system just because it's old — but don't start new projects with it.

migrate_to_nftables.devopsDEVOPS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// io.thecodeforge — DevOps tutorial

# Convert iptables rules to nftables format
iptables-save > /tmp/rules.v4
iptables-restore-translate -f /tmp/rules.v4 > /tmp/rules.nft

# Apply nftables rules
nft -f /tmp/rules.nft

# Example nftables equivalent of a simple iptables rule:
# iptables -A INPUT -p tcp --dport 22 -j ACCEPT
# nft add rule inet filter input tcp dport 22 accept

# List nftables rules
nft list ruleset

# Note: nftables uses a single ruleset with tables and chains, not multiple tables like iptables.
Output
table ip filter {
chain INPUT {
type filter hook input priority 0; policy drop;
tcp dport 22 accept
}
}
🔥Senior Shortcut:
Use iptables-translate to convert individual rules: iptables-translate -A INPUT -p tcp --dport 22 -j ACCEPT outputs nft add rule ip filter INPUT tcp dport 22 accept.

Production Checklist: Deploying iptables Safely

Before you deploy any iptables change in production, follow this checklist. First, ensure you have out-of-band access (IPMI, iDRAC, serial console) in case you lock yourself out. Second, save the current rules: iptables-save > /root/iptables.backup. Third, write your new rules in a file and test with iptables-restore --test < newrules.v4 to check for syntax errors. Fourth, apply with a timeout using iptables-apply -t 60 newrules.v4. Fifth, verify connectivity from another host: nc -zv <server> 22. Sixth, monitor logs for dropped packets: dmesg -w | grep iptables. Seventh, if everything works, make the rules persistent. Eighth, document the change in your runbook. Never make changes directly on a production server without a rollback plan. I've seen too many engineers panic when SSH drops — having a backup plan is non-negotiable.

safe_deploy.devopsDEVOPS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// io.thecodeforge — DevOps tutorial

# Step 1: Backup current rules
iptables-save > /root/iptables.backup.$(date +%s)

# Step 2: Test new rules syntax
iptables-restore --test < /path/to/newrules.v4 || echo "Syntax error!"

# Step 3: Apply with timeout (60 seconds)
iptables-apply -t 60 /path/to/newrules.v4
# If you lose SSH, rules revert after 60 seconds

# Step 4: Verify connectivity from another host
ssh admin@monitoring-host "nc -zv $TARGET_SERVER 22"

# Step 5: Check logs for drops
dmesg | grep -i "iptables.*DROP" | tail -10

# Step 6: Make persistent
iptables-save > /etc/iptables/rules.v4

# Step 7: Document in runbook
echo "Deployed new iptables rules on $(date) by $USER" >> /var/log/iptables_changelog
Output
Rules will be reverted in 60 seconds if not confirmed.
Connection to TARGET_SERVER 22 port [tcp/ssh] succeeded!
(no dmesg output if no drops)
⚠ Production Trap:
Never run iptables -F (flush) without having a backup session or a fallback plan. Flushing all rules with a default DROP policy will instantly lock you out. Always flush specific chains or use -P to set policy to ACCEPT first.
● Production incidentPOST-MORTEMseverity: high

The Rule That Blocked the CEO's VPN

Symptom
CEO couldn't connect to corporate VPN from a hotel. All other users were fine. VPN logs showed 'connection timed out'.
Assumption
The CEO's client was misconfigured. IT spent an hour reinstalling VPN software.
Root cause
A junior engineer added a rule to block a known attacker IP: iptables -A INPUT -j DROP. Without -s flag, it matched ALL incoming packets, dropping them before the VPN allow rule (which was later in the chain). The rule order was wrong.
Fix
Deleted the bad rule: iptables -D INPUT -j DROP. Then added the correct rule with source IP: iptables -A INPUT -s 5.5.5.5 -j DROP. Also moved the DROP rule to the end of the chain.
Key lesson
  • Always specify source/destination when blocking.
  • And always insert DROP rules at the end of the chain, not the beginning.
Production debug guideSystematic recovery paths for the failure modes engineers actually hit.3 entries
Symptom · 01
Cannot SSH into server after applying new rules
Fix
1. Use out-of-band console (IPMI/iDRAC) to access server. 2. Run iptables -L -n -v to see current rules. 3. If default policy is DROP and no SSH allow rule, add one: iptables -I INPUT 1 -p tcp --dport 22 -j ACCEPT. 4. If you have no console, ask a colleague to reboot the server (rules may revert if not persistent). 5. After regaining access, save working rules.
Symptom · 02
Application can't connect to local database
Fix
1. Check if loopback is allowed: iptables -L INPUT -n -v | grep lo. 2. If missing, add iptables -A INPUT -i lo -j ACCEPT. 3. Also check OUTPUT chain: iptables -L OUTPUT -n -v. 4. If OUTPUT policy is DROP, add allow rule for the database port.
Symptom · 03
High packet drop rate on a specific rule
Fix
1. Identify the rule with high drop count: iptables -L -n -v --line-numbers. 2. Check source IPs: iptables -L -n -v | grep DROP. 3. If legitimate traffic is being dropped, adjust the rule (e.g., increase rate limit, add source whitelist). 4. If malicious, consider adding to ipset blocklist.
★ iptables: Linux Firewall Configuration and Management Triage Cheat SheetFirst-response commands for when things go wrong — copy-paste ready.
SSH connection refused after iptables change
Immediate action
Check if SSH rule exists and is before any DROP rule
Commands
iptables -L INPUT -n -v --line-numbers | grep -E 'dpt:22|DROP'
iptables -I INPUT 1 -p tcp --dport 22 -j ACCEPT
Fix now
Add SSH allow rule at top of INPUT chain
Application timeout connecting to external service+
Immediate action
Check OUTPUT chain policy and rules
Commands
iptables -L OUTPUT -n -v
iptables -A OUTPUT -p tcp --dport 443 -j ACCEPT
Fix now
Add OUTPUT allow rule for required port
High CPU usage from kernel (softirq)+
Immediate action
Check conntrack table size and rule count
Commands
cat /proc/net/nf_conntrack | wc -l
sysctl net.netfilter.nf_conntrack_max
Fix now
Increase nf_conntrack_max and reduce timeouts
DNAT not working (port forwarding)+
Immediate action
Verify IP forwarding and FORWARD rules
Commands
sysctl net.ipv4.ip_forward
iptables -L FORWARD -n -v
Fix now
Enable IP forwarding and add FORWARD ACCEPT rules
Feature / Aspectiptablesnftables
SyntaxComplex, multiple tables with different optionsUnified, more consistent syntax
PerformanceLinear rule traversal, single lockSet-based lookups, better concurrency
Atomic updatesNo (flush and reload)Yes (atomic rule replacement)
Sets/mapsVia ipset external toolBuilt-in sets and maps
DebuggingTRACE target, LOG targetTrace infrastructure, monitor command
MaturityLegacy, widely deployedModern, actively developed
⚙ Quick Reference
10 commands from this guide
FileCommand / CodePurpose
list_tables.devopsfor table in filter nat mangle raw security; doTables and Chains
basic_rules.devopsiptables -A INPUT -i lo -j ACCEPTRule Syntax
conntrack_tuning.devopscat /proc/net/nf_conntrack | wc -lConnection Tracking
nat_rules.devopssysctl -w net.ipv4.ip_forward=1NAT
rate_limiting.devopsiptables -A INPUT -p tcp --dport 22 -m state --state NEW -m limit --limit 3/minu...Rate Limiting and DDoS Mitigation with iptables
debugging.devopsiptables -I INPUT 1 -j LOG --log-prefix "INPUT-PACKET: " --log-level 4Logging and Debugging
persistence.devopsiptables-save > /etc/iptables/rules.v4Persistence
performance_check.devopsiptables -L -n -v --line-numbers | head -20Performance
migrate_to_nftables.devopsiptables-save > /tmp/rules.v4When Not to Use iptables
safe_deploy.devopsiptables-save > /root/iptables.backup.$(date +%s)Production Checklist

Key takeaways

1
Always allow loopback and established connections first, then specific services, then drop everything else.
2
Use the raw table with NOTRACK to bypass conntrack for high-traffic UDP services to prevent table exhaustion.
3
Never make iptables changes without a backup session or a rollback plan (use iptables-apply with timeout).
4
iptables is legacy; for new projects, use nftables. But knowing iptables is still essential for maintaining existing infrastructure.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
How does iptables handle return traffic for a connection initiated from ...
Q02SENIOR
When would you choose iptables over nftables for a new production deploy...
Q03SENIOR
What happens when the conntrack table is full? How do you detect and mit...
Q04JUNIOR
Explain the difference between SNAT and MASQUERADE in iptables.
Q05SENIOR
A junior engineer added a rule `iptables -A INPUT -j DROP` and now SSH i...
Q06SENIOR
How would you design iptables rules for a multi-tenant SaaS platform whe...
Q01 of 06SENIOR

How does iptables handle return traffic for a connection initiated from inside the network? Explain the role of conntrack.

ANSWER
Conntrack tracks the outgoing packet as NEW, creates an entry, and then allows the return packet because it matches ESTABLISHED state. The rule -m state --state ESTABLISHED,RELATED -j ACCEPT is what permits it. Without conntrack, you'd need to open ephemeral ports.
FAQ · 4 QUESTIONS

Frequently Asked Questions

01
How do I save iptables rules permanently so they survive a reboot?
02
What's the difference between DROP and REJECT in iptables?
03
How do I block an IP address using iptables?
04
Why is my DNAT (port forwarding) not working even though I added the rule?
N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Written from production experience, not tutorials.

Follow
Verified
production tested
July 18, 2026
last updated
2,466
articles · all by Naren
🔥

That's Linux. Mark it forged?

6 min read · try the examples if you haven't

Previous
Grep, Awk, and Sed: Text Processing Power Tools
15 / 16 · Linux
Next
Systemd Units: Services, Timers, and Socket Activation