Linux iptables Firewall Basics
Learn iptables tables, chains, and rules. Build a working host firewall, understand NAT, and avoid the most common mistakes that lock you out.
What you'll learn
- ✓How tables, chains, and rules fit together
- ✓The five built-in chains and when each fires
- ✓Writing a default-deny host firewall
- ✓Setting up NAT with MASQUERADE
- ✓How to debug rules without locking yourself out
Prerequisites
- •Familiar with shell
- •Root or sudo access
What and Why
iptables is the user-space tool that configures the Linux kernel’s netfilter packet filtering subsystem. Every packet entering, leaving, or passing through a Linux box can be inspected, modified, or dropped according to rules you define. Even though nftables is the successor, iptables is still everywhere: container runtimes, Kubernetes kube-proxy, VPN tooling, and most cloud images.
The mental model takes a few minutes to learn, after which iptables becomes a precise scalpel rather than a black box.
Mental Model
There are tables, chains, and rules.
Tables are categories of work: filter decides allow or drop, nat rewrites addresses or ports, mangle edits headers, raw opts out of connection tracking. Chains are the points in the packet path where rules fire: INPUT (packet destined for this host), OUTPUT (originated locally), FORWARD (routed through), PREROUTING and POSTROUTING (NAT hooks). Rules are predicates with an action like ACCEPT, DROP, REJECT, or SNAT.
incoming
|
v
[PREROUTING] -- NAT/mangle/raw
|
v
route decision
/ \
v v
local forward
| |
v v
[INPUT] [FORWARD]
| |
v v
process [POSTROUTING]
| |
v v
[OUTPUT] outgoing NIC
|
v
[POSTROUTING]
|
v
outgoing NIC
Hands-on Example
Start from a clean slate and build a default-deny host firewall:
sudo iptables -F # flush rules
sudo iptables -P INPUT DROP # default deny
sudo iptables -P FORWARD DROP
sudo iptables -P OUTPUT ACCEPT
# allow loopback
sudo iptables -A INPUT -i lo -j ACCEPT
# allow established connections back in
sudo iptables -A INPUT -m conntrack \
--ctstate ESTABLISHED,RELATED -j ACCEPT
# allow SSH
sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT
# allow HTTP/HTTPS
sudo iptables -A INPUT -p tcp -m multiport --dports 80,443 -j ACCEPT
# allow ICMP echo
sudo iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT
Inspect what is loaded:
sudo iptables -L -v -n --line-numbers
For NAT, suppose you want to expose container traffic on 10.0.0.0/24 to the internet through eth0:
sudo sysctl -w net.ipv4.ip_forward=1
sudo iptables -t nat -A POSTROUTING -s 10.0.0.0/24 -o eth0 -j MASQUERADE
sudo iptables -A FORWARD -s 10.0.0.0/24 -j ACCEPT
sudo iptables -A FORWARD -d 10.0.0.0/24 \
-m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
To make the rules survive a reboot:
sudo iptables-save | sudo tee /etc/iptables/rules.v4
packet enters chain
|
v
rule 1 match? --yes--> action (terminal?)
|no |
v v
rule 2 match? --yes--> action
|no
v
...
|
v
chain policy (default)
Common Pitfalls
Locking yourself out over SSH. If you change the default policy of INPUT to DROP before adding the SSH allow rule, your current session might survive (because of conntrack) but the next one will not. Always add allow rules first, change policy last.
Forgetting the conntrack rule for established connections. Without it, your outbound DNS query goes out fine but the reply gets dropped on the way back.
Confusing DROP and REJECT. DROP silently discards; REJECT sends an ICMP unreachable. For internet-facing services, DROP is stealthier; for internal networks, REJECT gives faster client failures.
Order matters. Rules are evaluated top to bottom and the first match terminates (for terminal targets). A blanket ACCEPT at the top makes every rule below it dead code.
Mixing iptables and nftables on the same box can lead to confusing behavior, especially on newer distros where iptables is a translation layer.
Practical Tips
Always test risky rule sets with a safety net. Schedule a flush via at before applying, so if you get locked out the rules disappear in 5 minutes:
echo "iptables -F; iptables -P INPUT ACCEPT" | sudo at now + 5 minutes
Use comments to document why a rule exists:
sudo iptables -A INPUT -p tcp --dport 9100 -s 10.0.0.0/8 \
-m comment --comment "prometheus node-exporter" -j ACCEPT
Prefer named chains for grouping related rules. Create a chain with iptables -N MYAPP, fill it, and jump to it from INPUT. It keeps the rule set readable.
Log selectively. Add -j LOG --log-prefix "FW-DROP " before a drop rule to capture suspicious traffic in dmesg without flooding it.
Wrap-up
iptables is a chain of pattern-action rules over tables that hook into the kernel packet path. With a handful of rules you can build a default-deny host firewall and a working NAT gateway. Lead with allow rules, lean on conntrack, and never change a default policy without a safety net. Once you understand the chain order, the same mental model applies to nftables, kube-proxy rules, and most container networking.
Related articles
- Linux Linux Network Namespaces Tutorial
Hands-on guide to Linux network namespaces. Create isolated network stacks, connect them with veth pairs, and understand how containers get networking.
- Linux Linux Networking with ip and ss: The Modern Toolkit
Replace ifconfig and netstat with ip and ss. Learn to inspect interfaces, routes, and sockets on modern Linux with clear examples.
- Linux Linux SSH Port Forwarding Tutorial
Learn local, remote, and dynamic SSH port forwarding with practical examples. Tunnel databases through bastions, expose local apps, and build SOCKS proxies safely.
- Linux Linux Networking Commands: ip, ss, curl, dig
A practical guide to modern Linux networking tools. Learn ip, ss, curl, and dig to diagnose connectivity, inspect sockets, and resolve DNS issues fast.