iptables is the classic interface to netfilter, the Linux kernel's packet filtering subsystem. It works with chains of rules that are checked in order from top to bottom: as soon as a packet matches a rule, later rules in that chain are not checked.
The three main chains
Each chain is checked independently and does not affect the others' decisions: allowing something in INPUT does not automatically allow it in FORWARD. On a plain web server that does not forward someone else's traffic, the FORWARD chain can stay closed entirely — it is only needed when the server itself acts as a gateway or router for other machines.
| Chain | When it applies | Typical task |
|---|---|---|
| INPUT | Packets addressed to the server itself | Allow SSH, HTTP, HTTPS; block the rest |
| OUTPUT | Packets leaving the server | Usually left as ACCEPT unless the server proxies someone else's traffic |
| FORWARD | Transit packets passing through the server | Relevant for routers, VPN gateways and Docker hosts |
Basic iptables rules
#!/bin/bash
# Flush all rules
iptables -F
iptables -X
# Default policy: block incoming traffic
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
# Allow loopback
iptables -A INPUT -i lo -j ACCEPT
# Allow established connections
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
# SSH (replace 22 with your own port)
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
# HTTP and HTTPS
iptables -A INPUT -p tcp -m multiport --dports 80,443 -j ACCEPT
# ICMP (ping)
iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT
Common mistakes on first run
Forgetting the SSH rule before the DROP policy
If you run iptables -P INPUT DROP before the rule -A INPUT -p tcp --dport 22 -j ACCEPT is added, an active SSH session drops and a new one will not connect. On a VPS with no provider console this means a complete loss of access. The right order is to add every ACCEPT rule first and set the DROP policy as the very last command.
Forgetting to allow loopback
Without iptables -A INPUT -i lo -j ACCEPT under an INPUT DROP policy, local connections between processes on the server itself break — for example, PHP-FPM can no longer reach MySQL over 127.0.0.1, even though nobody touched the port from outside.
Protection against port scanning
# Block NMAP SYN scanning
iptables -A INPUT -p tcp --tcp-flags ALL NONE -j DROP
iptables -A INPUT -p tcp --tcp-flags ALL ALL -j DROP
# Limit new connections (SYN flood protection)
iptables -A INPUT -p tcp --syn -m limit --limit 1/s --limit-burst 3 -j ACCEPT
The rule with --state ESTABLISHED,RELATED from the basic set relies on the conntrack connection tracking module, which has a limit on table size. During mass port scanning or a flood of new connections the table can fill up, and the system log shows a message:
kernel: nf_conntrack: table full, dropping packet
After this message, new legitimate connections start being dropped too, not just the attacker's. The new-connection limit in the rule above reduces the risk of such an overflow, but for a genuinely large attack iptables alone is not enough — filtering needs to happen before the server, at the provider or CDN level.
Saving the rules
apt install iptables-persistent -y
netfilter-persistent save
# Rules are saved to /etc/iptables/rules.v4
When iptables alone is not enough
- You need filtering at the application level (analysing HTTP requests, not just ports and IPs) — iptables works at the L3/L4 level, L7 needs a WAF like ModSecurity.
- Many tables and chains with overlapping logic — modern distributions are gradually moving to nftables, which has more readable syntax and a single set of tables instead of separate utilities for IPv4 and IPv6.
- You need protection against password brute force based on failed login attempts — iptables does not read application logs, that requires Fail2ban on top of it.
Checking that the rules were applied
- Run
iptables -L -v -n— confirm the INPUT chain contains the expected rules and the default policy is indeed DROP. - From another device, check a closed port:
nc -zv YOUR_IP 3306should end in a refusal. - Reboot the server and run
iptables -L -v -nagain — if the rules are gone,netfilter-persistent savewas not run or theiptables-persistentpackage is not installed. - Check the packet counters in
iptables -L -v -n: rising numbers next to the SYN flood rule confirm the limit is actually filtering traffic, not just sitting unused in the config.
A simpler interface to the same rules is covered in UFW for Ubuntu and Debian, and automatic blocking based on logs is covered in setting up Fail2ban.