iptables Command in Linux: Complete Guide to Parameters and Firewall Rules

iptables command in Linux and it perimeters

I’ve spent more late nights staring at iptables -L output than I care to admit, usually because a rule order was wrong and traffic was hitting a DROP before it ever reached the ACCEPT I’d written three lines down. iptables is the classic userspace tool for managing the Linux kernel’s netfilter packet filtering framework, and even though nftables is the modern successor, iptables commands are still everywhere — most distros ship an iptables-nft compatibility layer that translates iptables syntax into nftables rules under the hood, so learning iptables is still directly useful today.

This guide covers the full command structure: tables, chains, targets, matches, and how to build a firewall ruleset that actually behaves the way you expect.

How iptables Fits Into the Kernel

iptables itself is just a userspace configuration tool. The actual packet filtering happens inside the kernel’s netfilter subsystem, which defines a series of hook points in the network stack: PREROUTING, INPUT, FORWARD, OUTPUT, and POSTROUTING. Every packet that passes through the kernel’s networking code passes through one or more of these hooks, and each hook corresponds to a chain that iptables lets you attach rules to.

On modern kernels (post-3.13), the classic iptables binary is often really just a compatibility shim (iptables-nft) that translates your commands into nftables rules, while iptables-legacy still exists using the old x_tables kernel API directly. You can check which backend you’re on:

iptables --version
# iptables v1.8.10 (nf_tables)   <- this system is using the nft backend

Tables

iptables organizes rules into tables, each meant for a different kind of packet manipulation:

TablePurpose
filterDefault table — accept/drop/reject traffic (the one most people mean by “firewall rules”)
natNetwork Address Translation — SNAT, DNAT, MASQUERADE
manglePacket header modification (TTL, TOS, marking packets for routing)
rawConfiguring exceptions from connection tracking
securitySELinux-related packet marking (rarely used directly)

If you don’t specify -t, iptables assumes filter.

Chains

Each table has built-in chains tied to netfilter hook points:

  • PREROUTING — packets as they arrive, before routing decisions (nat, mangle, raw)
  • INPUT — packets destined for the local machine (filter, mangle)
  • FORWARD — packets being routed through this machine to somewhere else (filter, mangle)
  • OUTPUT — packets generated locally (filter, nat, mangle, raw)
  • POSTROUTING — packets about to leave the interface, after routing (nat, mangle)

You can also define your own custom chains for organization, which is genuinely useful once your ruleset grows past a dozen rules.

Basic Syntax

iptables [-t table] COMMAND CHAIN RULE-SPEC [-j TARGET]

Core Commands

iptables -A INPUT ...     # Append a rule to the end of a chain
iptables -I INPUT 1 ...   # Insert a rule at a specific position (here, position 1)
iptables -D INPUT 3       # Delete rule number 3 from INPUT
iptables -R INPUT 2 ...   # Replace rule number 2
iptables -L               # List rules
iptables -F               # Flush (delete) all rules in a chain (or all chains if none specified)
iptables -P INPUT DROP    # Set the default policy for a chain
iptables -N LOGGING       # Create a new custom chain
iptables -X LOGGING       # Delete a custom chain (must be empty and unreferenced)
iptables -Z               # Zero the packet/byte counters

Listing Rules Usefully

iptables -L -n -v --line-numbers
  • -n — numeric output (skip DNS/service-name resolution, much faster and clearer)
  • -v — verbose (shows packet/byte counters and interface names)
  • --line-numbers — shows rule numbers, essential for using -D or -I precisely

Example output:

Chain INPUT (policy DROP 152 packets, 9120 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0 ACCEPT     0    --  lo     *       0.0.0.0/0            0.0.0.0/0
2       48  3840 ACCEPT     0    --  *      *       0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
3        0     0 ACCEPT     6    --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:22

Match Criteria (Rule Specifications)

Protocol and Ports

iptables -A INPUT -p tcp --dport 22 -j ACCEPT
iptables -A INPUT -p tcp --sport 1024:65535 -j ACCEPT
iptables -A INPUT -p udp --dport 53 -j ACCEPT
iptables -A INPUT -p tcp -m multiport --dports 80,443,8080 -j ACCEPT

Source / Destination

iptables -A INPUT -s 192.168.1.0/24 -j ACCEPT
iptables -A INPUT -s 10.0.0.5 -d 10.0.0.10 -j ACCEPT
iptables -A INPUT ! -s 192.168.1.0/24 -j DROP   # negation with !

Interfaces

iptables -A INPUT -i eth0 -j ACCEPT     # incoming interface
iptables -A OUTPUT -o eth1 -j ACCEPT    # outgoing interface

Connection State (via conntrack)

iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -m conntrack --ctstate NEW -p tcp --dport 22 -j ACCEPT
iptables -A INPUT -m conntrack --ctstate INVALID -j DROP

This is arguably the single most important rule pattern in any stateful firewall — accept ESTABLISHED/RELATED traffic early so you don’t have to write separate rules for return traffic on every connection.

ICMP

iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT
iptables -A INPUT -p icmp --icmp-type echo-reply -j ACCEPT
iptables -A INPUT -p icmp --icmp-type destination-unreachable -j ACCEPT

Rate Limiting

iptables -A INPUT -p tcp --dport 22 -m limit --limit 5/minute --limit-burst 10 -j ACCEPT

Useful as a crude brute-force mitigation on SSH before you set up something more robust like fail2ban.

Targets

-j ACCEPT     # allow the packet
-j DROP       # silently discard the packet
-j REJECT     # discard and send back an error (ICMP unreachable or TCP RST)
-j LOG        # log the packet via the kernel log, then continue processing (does not stop the chain)
-j SNAT       # source NAT (nat table, POSTROUTING)
-j DNAT       # destination NAT (nat table, PREROUTING)
-j MASQUERADE # dynamic SNAT for interfaces with changing IPs (nat table, POSTROUTING)
-j RETURN     # stop processing this chain, return to the calling chain

DROP vs REJECT is a real design decision: DROP makes port scans slower (the scanner waits for a timeout) but can look like network trouble to legitimate clients; REJECT responds immediately with a clear “connection refused,” which is friendlier for internal networks but reveals a host is alive.

Logging example:

iptables -A INPUT -p tcp --dport 22 -j LOG --log-prefix "SSH-ATTEMPT: " --log-level 4
iptables -A INPUT -p tcp --dport 22 -j ACCEPT

LOG doesn’t terminate rule processing, so you always need a following ACCEPT/DROP/REJECT rule to actually decide the packet’s fate.

Building a Complete Basic Firewall

This is the pattern I reach for on nearly every fresh server:

# Flush existing rules
iptables -F
iptables -X

# Set default policies: deny everything unless explicitly allowed
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT

# Allow loopback traffic
iptables -A INPUT -i lo -j ACCEPT

# Allow established/related connections
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# Allow SSH, HTTP, HTTPS
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT

# Allow ping
iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT

# Log and drop everything else
iptables -A INPUT -j LOG --log-prefix "IPT-DROPPED: "
iptables -A INPUT -j DROP

I verified this exact ruleset builds correctly, applies in order, and lists with proper line numbers on a live Ubuntu 24.04 system running the nf_tables iptables backend.

Saving and Restoring Rules

iptables rules live only in kernel memory — they vanish on reboot unless you persist them.

iptables-save > /etc/iptables/rules.v4
iptables-restore < /etc/iptables/rules.v4

Debian/Ubuntu — install iptables-persistent to auto-load rules at boot:

sudo apt install iptables-persistent
sudo netfilter-persistent save
sudo netfilter-persistent reload

RHEL/Fedora — the iptables-services package provides a systemd unit:

sudo dnf install iptables-services
sudo systemctl enable iptables
sudo service iptables save

Note that RHEL 8+ and Fedora default to firewalld as the front-end firewall manager, which itself manages nftables/iptables rules underneath. Running raw iptables alongside firewalld on the same box is a common source of confusing, conflicting rules — pick one management layer.

Comparing iptables, nftables, ufw, and firewalld

  • iptables — the classic tool, extremely well documented, works everywhere, syntax gets verbose for complex rulesets.
  • nftables — the modern kernel framework and its own nft command, more efficient rule evaluation, unified IPv4/IPv6 syntax, but a different (and to many, less familiar) syntax.
  • ufw — a friendly wrapper around iptables/nftables aimed at simplicity, standard on Ubuntu desktop and common on Ubuntu servers.
  • firewalld — a dynamic, zone-based firewall manager standard on RHEL/Fedora/CentOS, backed by nftables on modern versions.

For a single, simple server, ufw or firewalld will get you there faster. For fine-grained control, scripting, and understanding exactly what’s happening at the packet level, raw iptables/nft is worth knowing properly.

How Rule Evaluation Actually Works Internally

A detail that clears up a lot of confusion: for every packet, netfilter walks the relevant chain top to bottom, evaluating each rule’s match criteria in order. The moment a rule matches and its target is a “terminating” target (ACCEPT, DROP, REJECT), evaluation of that chain stops immediately — later rules in the same chain are never even consulted for that packet. Non-terminating targets like LOG don’t stop evaluation; the packet continues to the next rule after being logged.

This is why rule order is just as important as rule content. A DROP rule sitting above an ACCEPT rule for the same traffic wins, full stop, regardless of how correct the ACCEPT rule looks in isolation. When debugging “my rule isn’t working,” the very first thing to check is always whether something earlier in the chain already terminated processing for that packet.

iptables -L INPUT -n -v --line-numbers

The pkts and bytes counters shown with -v are genuinely useful here — a rule with a zero packet count after traffic you expect to match it has definitely not been the one handling that traffic, telling you the match is happening somewhere else (or not at all).

The Extension/Match Module System

Everything after -m in a rule invokes a kernel module that extends what iptables can match on beyond basic protocol/port/address. This is a genuinely extensible system, and knowing a few of the more useful matches beyond conntrack opens up a lot of capability:

# Match based on time of day
iptables -A INPUT -m time --timestart 09:00 --timestop 18:00 -p tcp --dport 8080 -j ACCEPT

# Match based on how many connections a single source IP currently has open
iptables -A INPUT -p tcp --syn -m connlimit --connlimit-above 20 -j DROP

# Match a specific TCP flag combination directly (rather than via conntrack state)
iptables -A INPUT -p tcp --tcp-flags SYN,ACK,FIN,RST SYN -j ACCEPT

# Match based on packet size (unusual, but useful for spotting certain attack patterns)
iptables -A INPUT -p udp -m length --length 1400:65535 -j DROP

# Match a whole address list maintained dynamically (used heavily by fail2ban)
iptables -A INPUT -m set --match-set blocklist src -j DROP

That last example, -m set, pairs with ipset — a companion tool for managing large, efficiently-indexed lists of addresses that would be painfully slow to represent as individual iptables rules:

sudo apt install ipset
ipset create blocklist hash:ip
ipset add blocklist 203.0.113.99
iptables -I INPUT -m set --match-set blocklist src -j DROP

Tools like fail2ban commonly use ipset under the hood for exactly this reason — checking membership in a hash set scales far better than iptables evaluating hundreds of individual per-IP rules linearly.

Handling Fragmented Packets and Common Evasion Patterns

A subtlety worth knowing: by default, only the first fragment of a fragmented packet carries full Layer 4 header information (source/destination port), which means match criteria like --dport may not apply as expected to later fragments of the same original packet. This has historically been used as a firewall evasion technique.

# Explicitly handle fragments — a conservative default is to drop them outright on inbound
# unless you have a specific documented need for fragmented traffic
iptables -A INPUT -f -j DROP

Most modern conntrack implementations reassemble fragments before your filter rules ever see them, largely neutralizing this as a practical evasion vector on current kernels — but it’s still worth knowing the underlying mechanism, especially if you’re working with an older kernel or reviewing an inherited ruleset that includes explicit fragment handling.

A Deeper Look at REJECT Response Types

REJECT isn’t a single behavior — it supports different rejection messages via --reject-with, which matters for how a blocked connection actually appears to the other end:

iptables -A INPUT -p tcp --dport 8080 -j REJECT --reject-with tcp-reset
iptables -A INPUT -p udp --dport 8080 -j REJECT --reject-with icmp-port-unreachable
iptables -A INPUT -j REJECT --reject-with icmp-host-prohibited

tcp-reset is often the most honest choice for TCP services you’re intentionally blocking (rather than one that doesn’t exist) — it tells the connecting client immediately and unambiguously that the connection was refused, rather than leaving them to wait for a timeout as DROP would.

Auditing an Existing Ruleset You Didn’t Write

A situation that comes up constantly in practice: inheriting a server with an iptables ruleset nobody documented. A methodical read-through:

# Get the full picture in one readable pass
iptables-save

# Check for any custom chains and where they're referenced from
iptables -L -n | grep "^Chain"

# Check what's actually being hit (non-zero counters) vs dead rules
iptables -L -n -v | awk '$1 != "0" {print}'

Rules with zero packet counts after the system has been running for a while under normal traffic are worth scrutinizing — they might be defending against a threat that no longer applies, referencing an IP that’s since changed, or simply dead weight left over from a service that was decommissioned.

Comparing Rule-Writing Approaches: iptables vs nft Syntax Side by Side

Since the underlying nf_tables engine is what’s actually running under iptables-nft on modern systems anyway, it’s worth seeing the same rule expressed both ways, since you’ll likely encounter both in documentation and inherited configs:

# iptables syntax
iptables -A INPUT -p tcp --dport 22 -j ACCEPT

# equivalent nft syntax
nft add rule inet filter input tcp dport 22 accept
# iptables syntax
iptables -A INPUT -s 192.168.1.0/24 -j ACCEPT

# equivalent nft syntax
nft add rule inet filter input ip saddr 192.168.1.0/24 accept

nftables syntax reads more like a sentence and unifies IPv4/IPv6 handling into a single rule set (inet family) rather than requiring separate iptables/ip6tables invocations — a genuine ergonomic improvement, though the iptables syntax remains far more commonly documented and searched for troubleshooting help online, which is a big part of why it persists.

Troubleshooting

Rule added but traffic still blocked — check rule order with --line-numbers. iptables evaluates top to bottom and stops at the first match; a DROP earlier in the chain wins even if you added an ACCEPT after it.

Changes vanish after reboot — rules aren’t persisted; see the save/restore section above.

Locked yourself out over SSH — always test firewall changes with a scheduled rollback if you’re on a remote box:

(sleep 60 && iptables -F && iptables -P INPUT ACCEPT) &

This gives you a 60-second window to fix a mistake before the rules auto-flush.

Conflicts with firewalld/ufw — check if another firewall manager is active and disable one of them:

sudo systemctl status firewalld
sudo systemctl status ufw

Security Implications

A default-deny INPUT policy (iptables -P INPUT DROP) combined with explicit ACCEPT rules for only the services you actually run is the single biggest security improvement most servers can make in five minutes. Pair it with conntrack state matching so you’re not accidentally blocking legitimate return traffic, and log dropped packets during the first few days after a ruleset change so you can catch anything you forgot to allow before it becomes a support ticket.

Summary

iptables gives you direct control over the kernel’s packet filtering at the table/chain/rule level. The mental model — tables organize by purpose, chains map to netfilter hook points, rules match criteria and dispatch to a target — is what makes the syntax click. Default-deny plus explicit allows, conntrack-based state tracking, and persisted rules across reboots covers the vast majority of real-world firewall needs.

References

Total
2
Shares

Leave a Reply

Previous Post
how to enable packet filtering in Linux

How to Enable Packet Filtering in Linux: Complete Network Security Configuration Guide

Next Post
Non-Technical Aspects of Security Audit: Enhancing Organizational Cybersecurity

Non-Technical Aspects of Security Audit: Enhancing Organizational Cybersecurity

Related Posts