← Back

2026

Linux iptables: How Packet Filtering Actually Works

A practical breakdown of iptables. Tables, chains, targets and the default policy that ties it all together.

Every Linux server you touch has a firewall built directly into the kernel. It has been there since the early 2000s. Most people interact with it indirectly through tools like ufw or cloud security groups without ever knowing what is underneath. That underlying system is called netfilter. The tool you use to configure it is iptables.

Understanding iptables properly changes how you think about network traffic on Linux. Once you see the mental model it is hard to unsee. Things that once felt like magic become straightforward steps through a chain of logic. Port forwarding, NAT, firewall rules all click into place.

What iptables actually does

Every packet that enters or leaves your machine passes through the netfilter system in the kernel. iptables is your interface for telling that system what to do with those packets.

Think of it as a bouncer checking IDs at the door. Each packet arrives with a source address, a destination address, a protocol and a port number. The bouncer checks the list and decides whether to let it through, redirect it somewhere else, or turn it away entirely.

The three tables

iptables organises its rules into tables. Each table has a specific purpose. You specify which table you are working with using the -t flag. If you omit it, you are working with filter by default.

filterdefault

The table you will use for almost everything. Decides whether to accept or drop packets. All your everyday firewall rules live here.

natrouting

Handles Network Address Translation. Rewrites the source or destination address of a packet. Port forwarding lives in this table.

mangleadvanced

For specialized packet alteration like tweaking TTL values or marking packets for routing decisions. Rarely needed unless you are doing something non-standard.

Chains: where rules actually live

Within each table there are chains. A chain is an ordered list of rules. When a packet arrives it walks down the chain from top to bottom until it hits a rule that matches. The filter table has three built-in chains:

INPUT

Packets destined for the local machine itself. Anything arriving at a port your system is listening on passes through here.

OUTPUT

Packets originating from the local machine. Outbound traffic from your own processes goes through this chain.

FORWARD

Packets passing through the machine on their way somewhere else. This chain matters when your box is acting as a router.

Anatomy of a rule

Here is a basic rule that allows incoming SSH traffic:

terminal
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
Part
Meaning
-A INPUT
Append a rule to the INPUT chain
-p tcp
Match TCP packets only
--dport 22
Match packets destined for port 22 (SSH)
-j ACCEPT
Jump target. Tells iptables what to do when the rule matches

To drop traffic instead of accepting it, swap ACCEPT for DROP. The rest of the rule stays the same.

Jump targets

The -j flag specifies what happens when a rule matches. These are the four you will encounter most often:

ACCEPT

Let the packet through. The most common target for rules you actually want to allow.

DROP

Silently discard the packet. The sender gets no response and has no way to tell whether the port is closed or just filtered.

REJECT

Discard the packet but send an error back to the sender. More polite than DROP. Useful on internal networks.

LOG

Write a message to the kernel log without stopping the packet. Chain LOG before a DROP to get visibility into what is being blocked.

The default policy

Each chain has a policy that fires when no rule matches. You set it with -P:

terminal
iptables -P INPUT DROPiptables -P FORWARD DROPiptables -P OUTPUT ACCEPT

Setting INPUT to DROP builds a whitelist firewall. Only traffic you explicitly allow gets through. Everything else is silently discarded. This is the right default for most servers.

Watch out for this

If you set a restrictive INPUT policy on a remote server before allowing established connections you will lock yourself out. Always add the established connection rule first:

iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

A minimal working ruleset

Here is a sensible starting point for a server that runs a web application. Apply these rules in order. Chains are evaluated top to bottom so the sequence matters.

terminal
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

Allow established connections back in

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

Allow SSH

iptables -A INPUT -p tcp --dport 80 -j ACCEPT

Allow HTTP

iptables -A INPUT -p tcp --dport 443 -j ACCEPT

Allow HTTPS

iptables -A INPUT -i lo -j ACCEPT

Allow loopback interface

iptables -P INPUT DROP

Drop everything else by default

Viewing your rules

iptables -L -v -n

List all rules with packet counts. -n skips DNS lookups so it is faster.

iptables -L INPUT -v -n

List rules for a specific chain only.

iptables-save

Print the full ruleset as commands. Useful for reviewing or backing up.

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

Show rules with line numbers. Needed when you want to delete a specific rule.

To delete a specific rule, first list with --line-numbers then run iptables -D INPUT 3 to remove line 3 from the INPUT chain. To flush all rules at once use iptables -F.

Making rules survive a reboot

This is the thing that catches most people the first time. iptables rules are not persistent by default. When the machine reboots the rules are gone.

Save your ruleset

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

Dumps the current rules to a file you can restore from.

Restore from a file

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

Loads a saved ruleset back into the kernel.

On Debian-based systems the iptables-persistent package handles automatic restore on boot. On RHEL-based systems iptables-services does the same job. Either way you will need to explicitly save your rules after any change. They are not auto-saved.

A note on nftables

iptables has been around since the late 1990s. Its successor nftables is now the default on most modern distributions. On a fresh Debian 12 or Ubuntu 22.04 install you may find that the iptables command is actually a compatibility shim wrapping nftables underneath.

The mental model is identical. Tables, chains, rules and targets all carry over. The syntax changes but the concepts do not. Learning iptables first gives you a solid foundation that makes nftables straightforward to pick up when you get there.