Listing Rules
Everything needs root. skips DNS lookups, adds packet counters, gives you the numbers used for deleting.
| Command | What it does |
|---|---|
| All rules in the filter table, numeric, with counters | |
| Same, with rule numbers for deleting | |
| One chain only | |
| Rules printed as the commands that made them | |
| Rules in the nat table (NAT rules never show in the default listing) | |
| Find rules touching port 22 | |
| Watch packet counters move, live |
Tables and Chains
A rule lives in a chain, a chain lives in a table. is the default, which is why NAT rules seem to disappear until you ask for their table.
| Table / chain | What it is for |
|---|---|
| table | Accept or drop traffic; the default table |
| table | Rewrite addresses and ports (MASQUERADE, DNAT) |
| table | Alter packet headers (TTL, marks); rarely needed |
| chain | Traffic addressed to this machine |
| chain | Traffic this machine sends |
| chain | Traffic routed through this machine (routers, Docker) |
| chain | Before routing; where DNAT and port forwards go |
| chain | After routing; where MASQUERADE goes |
Allowing Traffic
The order matters: rules match top down, first match wins. Allow SSH before anything drops.
| Command | What it does |
|---|---|
| Allow SSH | |
| Allow HTTP ( for HTTPS) | |
| Both in one rule | |
| Allow replies to connections this box started (put this near the top) | |
| Allow loopback (many things break without it) | |
| Allow everything from one IP | |
| Allow one subnet to reach Postgres | |
| Allow a UDP port (WireGuard here) |
A minimal safe INPUT policy, in the right order:
sudo iptables -A INPUT -i lo -j ACCEPT
sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT
sudo iptables -A INPUT -p tcp -m multiport --dports 80,443 -j ACCEPT
sudo iptables -P INPUT DROPThe firewall rule generator builds rule sets like this from a checklist.
Blocking Traffic
| Command | What it does |
|---|---|
| Block one IP | |
| Block a range | |
| Block it ahead of every allow rule | |
| Block a port silently | |
| Block a port with an immediate error | |
| Stop this box talking to an IP |
DROP discards the packet silently, so the client hangs until it times out; REJECT answers with an error, so the client fails fast with connection refused. DROP gives port scanners nothing, REJECT is kinder for services your own users hit. If SSH suddenly gets connection refused, look for a REJECT rule.
Adding and Deleting Rules
appends to the bottom, inserts at the top (or a position). Since first match wins, a rule appended after a DROP does nothing.
| Command | What it does |
|---|---|
| Append at the end of the chain | |
| Insert at position 1, the very top | |
| Insert at position 3 | |
| Delete by repeating the rule exactly | |
| Get the numbers, then... | |
| Delete rule number 3 | |
| Replace rule number 3 | |
| Set the chain policy, the fate of packets no rule matched |
Rule numbers shift after every delete. Deleting several by number goes highest first, or re-list between deletes.
Flushing Rules
deletes all rules but does not touch chain policies. If the policy is DROP, flushing locks you out of SSH instantly. Safe order:
# 1. Open the policies first, so an empty ruleset allows everything:
sudo iptables -P INPUT ACCEPT
sudo iptables -P FORWARD ACCEPT
sudo iptables -P OUTPUT ACCEPT
# 2. Now flush:
sudo iptables -F
sudo iptables -X # delete custom chains
sudo iptables -t nat -F # nat and mangle have their own rules
sudo iptables -t mangle -FKeep the current SSH session open and test a fresh login before closing it. When rebuilding, the first rule you add back is the SSH allow.
NAT and Port Forwarding
NAT rules live in the table and need forwarding enabled in the kernel first:
sudo sysctl -w net.ipv4.ip_forward=1
# permanent: echo 'net.ipv4.ip_forward=1' | sudo tee /etc/sysctl.d/99-forward.conf| Command | What it does |
|---|---|
| Share this box's IP with everything it routes (the home router rule) | |
| Masquerade one subnet only (typical VPN setup) | |
| Redirect a port on this box | |
| See what NAT is doing |
Forward port 80 arriving on this box to an internal server:
sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 10.0.0.5:80
sudo iptables -A FORWARD -p tcp -d 10.0.0.5 --dport 80 -m conntrack --ctstate NEW,ESTABLISHED,RELATED -j ACCEPT
sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADESaving Rules
Rules live in kernel memory and are gone after a reboot unless something restores them.
| Command | What it does |
|---|---|
| Dump current rules to a file | |
| Load them back | |
| Debian/Ubuntu: restores rules at boot | |
| Save current rules for that boot restore | |
| RHEL/CentOS with the iptables-services package | |
| IPv6 rules are saved separately | |
| Apply with an automatic rollback if you do not confirm (lockout insurance) |
Logging and Rate Limiting
| Command | What it does |
|---|---|
| Rate limit new SSH connections | |
| Track new SSH sources... | |
| ...and drop an IP with 5 attempts in a minute | |
| Log what falls through (place just before the end or a DROP) | |
| Same, without flooding the log |
Logged packets land in the kernel log: or .
nftables and Front Ends
nftables is the successor; new setups should use it or a front end rather than raw iptables.
| Command | What it does |
|---|---|
| in the output means your commands become nftables rules | |
| Show a rule in nft syntax | |
| See everything, including rules ufw, firewalld, and Docker added | |
| Debian/Ubuntu: switch between iptables-nft and iptables-legacy |
Convert whole rule sets with the iptables to nftables converter. If all you want is ports opened and closed on one server, UFW is the simpler front end and manages this layer for you.
Gotchas
- Mixing tools bites: ufw, firewalld, and Docker all write their own iptables or nftables rules, so a rule you add by hand can be shadowed by theirs or wiped on their reload. shows the whole truth; pick one manager per box.
- Rules are gone after a reboot unless you set up persistence. The classic failure: firewall works for months, a reboot silently drops it.
- without hides all NAT rules, and without it stalls on DNS lookups.
- iptables-legacy and iptables-nft keep separate rule sets. If rules seem to be missing, the other binary may own them: check and .
- Docker publishes container ports by writing its own FORWARD and DOCKER chain rules, bypassing your INPUT rules entirely. A "closed" port can still be reachable through Docker.
- IPv6 is a separate firewall ( or the nft family). Locking down IPv4 while the service also listens on IPv6 locks down half the door.