Cheat Sheet

iptables Commands

iptables is the classic Linux firewall tool, still everywhere even though nftables is its successor. This sheet covers listing and adding rules, opening and blocking ports, rule order, NAT and port forwarding, ipset, IPv6, Docker, making rules survive a reboot, and the DROP policy that locks you out of SSH.

Last updated September 11, 2026

Listing Rules

Everything needs root. skips DNS lookups, adds packet counters, gives you the numbers used for deleting.

CommandWhat 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)
Check whether an exact rule exists; exit code 0 means yes
Zero the packet counters, leaving the rules alone
Find rules touching port 22
Watch packet counters move, live

SSH Lockout

This is the one that costs you the server. with no rule allowing port 22 kills the session you are typing in, not just the next login, because the policy applies to the packets carrying your keystrokes. The same thing happens when you flush a ruleset while the policy is already DROP, since removes the SSH allow rule and leaves the DROP in place. Add the allow rules first, set the policy last.

sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
sudo iptables -A INPUT -i lo -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT
sudo iptables -L INPUT -n -v --line-numbers   # confirm the SSH rule is live
# only now, once port 22 is open:
sudo iptables -P INPUT DROP
CommandWhat it does
Deadman switch: opens the firewall in 5 minutes if you lose the session
Find the pending job; cancels it
Apply a saved ruleset and roll it back in 30 seconds unless you confirm
from a second terminalTest with a fresh connection and keep the old one open
What to run from the provider's console if you are already locked out

Set up the deadman switch before the first DROP, not after, and cancel it once a new login works. If sshd listens on a non-standard port, open that port instead and check it from outside with the SSH port checker. A DROP policy shows up as a timeout (Network error: Connection timed out), a REJECT rule as connection refused. The nftables and UFW sheets have the same trap in their own syntax, and the firewall rule generator writes a ruleset that already has the SSH rule in it.

Allowing Traffic

Order matters: rules match top down, first match wins. Allow SSH before anything drops.

CommandWhat 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)
Allow a port range
Only on one interface
Allow ping
Match a MAC address (same LAN only, and easy to spoof)
Attach a comment that shows up in the listing

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 DROP

The firewall rule generator builds rule sets like this from a checklist.

Blocking Traffic

CommandWhat 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
Unblock: delete the rule that blocked it

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. For blocklists of more than a handful of addresses, or a whole country, see IP Sets below.

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.

CommandWhat 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
Wait up to 5 seconds for the xtables lock instead of failing

Rule numbers shift after every delete. Deleting several by number goes highest first, or re-list between deletes.

Rule Order

Inside a chain the first matching rule wins and evaluation stops, so a rule you append below an existing ACCEPT or DROP never runs. Chains run in a fixed order too, which is why NAT happens before your filter rules ever see the packet.

PathOrder of chains
Arriving for this box then
Routed through this box then then
Sent by this box then
Within one hook then then then

Two consequences worth remembering: a DNAT rewrite in PREROUTING happens first, so a FORWARD rule has to match the internal address, not the public one; and a packet accepted in INPUT never reaches FORWARD, which is why Docker's published ports are invisible to your INPUT rules.

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 / chainWhat it is for
tableAccept or drop traffic; the default table
tableRewrite addresses and ports (MASQUERADE, DNAT)
tableAlter packet headers (TTL, marks); rarely needed
tableRuns before conntrack; where and go
chainTraffic addressed to this machine
chainTraffic this machine sends
chainTraffic routed through this machine (routers, Docker)
chainBefore routing; where DNAT and port forwards go
chainAfter routing; where MASQUERADE goes
Create a chain of your own
Send matching traffic into it
Delete it once it is empty and nothing jumps to it

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 -F

Keep 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, and the deadman switch in SSH Lockout above costs nothing.

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
CommandWhat it does
Share this box's IP with everything it routes (the home router rule)
Masquerade one subnet only (typical VPN setup)
Source NAT to a fixed address; cheaper than MASQUERADE on a static IP
Redirect a port on this box
Forward a port to an internal host
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 MASQUERADE

A forward that does nothing is almost always left at 0, a FORWARD chain with no matching ACCEPT, or a test run from the box itself: locally generated traffic never passes through PREROUTING, so test from another host or with the SSH port checker.

Saving Rules

Rules live in kernel memory and are gone after a reboot unless something restores them.

CommandWhat it does
Dump current rules to a file
Load them back, replacing everything
Merge a file into the running rules instead of replacing them
Keep the packet counters in the dump
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)

IP Sets

keeps thousands of addresses in one kernel hash, so a blocklist costs you one rule instead of one rule per address, and matching stays fast as it grows.

sudo apt install ipset
sudo ipset create blocklist hash:net
sudo ipset add blocklist 198.51.100.0/24
sudo iptables -I INPUT -m set --match-set blocklist src -j DROP
CommandWhat it does
Show a set and everything in it
Add an address that expires on its own
Remove one
Ask whether an address is in the set
A set of single addresses rather than networks
Allow only what is in a set
Sets are saved separately from iptables rules
Load them back; do this before the iptables rules
Delete a set; the rules referencing it have to go first

Country blocking works the same way: build a set from an aggregated zone file and point one DROP rule at it. The module from xtables-addons does it without a set, but the database needs updating by hand and it is a per-packet lookup. nftables has sets built into the ruleset, with no second tool to install or save; see the nftables cheat sheet.

Logging and Rate Limiting

CommandWhat 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
Trace a packet through every table and chain ( first)
The addresses fail2ban is blocking right now
fail2ban's own view of the same bans

Logged packets land in the kernel log: or . fail2ban inserts its bans into its own chain at the top of INPUT, so its rules are why a chain sometimes has entries you did not add.

IPv6

is a second, independent firewall with the same syntax. Locking down IPv4 on a service that also listens on IPv6 closes half the door.

CommandWhat it does
List the IPv6 rules
Allow SSH over IPv6
Required: IPv6 breaks without ICMPv6 (neighbour discovery, path MTU)
Same lockout risk as IPv4; open 22 first
Saved separately from
Whether sshd is listening on IPv6 at all ( or )
Turn IPv6 off rather than firewall it

nftables' family covers both stacks in one rule, which is the main day-to-day reason to move.

Docker

Docker writes its own chains and publishes container ports through FORWARD and the DOCKER chain, bypassing INPUT entirely. A container run with is reachable from the internet even when your INPUT rules say only 22 is open.

CommandWhat it does
The port publishing rules Docker wrote
The chain Docker leaves for you; it runs before Docker's own rules
Block outside traffic to every published port
Publish to localhost only, then reverse proxy in front
Quick check whether Docker is managing rules on this box
Rebuilds Docker's chains; rules you added inside them are lost, DOCKER-USER survives

Setting in stops the bypass but breaks container networking in other ways, so almost nobody keeps it. Full container commands are on the Docker cheat sheet.

Common Errors

MessageCause
Not installed (minimal Debian, containers), or is off your PATH: use
Every iptables command needs root, listing included
Two writers at once, usually Docker or fail2ban; add
Missing kernel module, or a chain name typed with the wrong case
needs or earlier in the same rule
on a chain with fewer rules; list with first
, or you are in a container without
Rules exist in the other backend; see nftables and Front Ends below
One bad line, and the whole table is rejected: restore is atomic per table
Rule added but nothing is blockedAn earlier ACCEPT already matched, or the traffic is Docker's and never reaches INPUT
DNAT or MASQUERADE does nothing is 0, no FORWARD ACCEPT matches, or you are testing from the box itself
Rules gone after a rebootNothing saved them; see Saving Rules

nftables and Front Ends

nftables is the successor; new setups should use it or a front end rather than raw iptables.

CommandWhat 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
The other backend's rules, which cannot see
Debian/Ubuntu: switch between iptables-nft and iptables-legacy

Convert whole rule sets with the iptables to nftables converter, or write one rule for iptables, nft, UFW, and firewalld at once with the firewall rule generator. The nftables cheat sheet has the native syntax side by side with these commands. 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.
  • contents are saved by , not by . Restoring rules without restoring the sets leaves you with rules that match nothing, or that refuse to load at all.

iptables FAQ

Do iptables rules persist after a reboot?

No. The running ruleset lives in kernel memory and is empty again at the next boot unless a package or a unit restores it. On Debian and Ubuntu, sudo apt install iptables-persistent saves whatever is loaded during the install and reloads it at boot, and after every later change you have to run sudo netfilter-persistent save yourself. On RHEL, Rocky, and Fedora the equivalent is iptables-services, enabled with systemctl enable iptables and saved with sudo service iptables save. The trap is saving too early: the save command writes the ruleset that is live right now, so a half-built one, or one you have not tested with a fresh SSH login, is the one that comes back after a reboot. IPv6 is a separate save (ip6tables-save), and ipset contents are a third.

Where are iptables rules stored?

The live rules are not in a file at all, they are in the kernel, which is why sudo iptables -S is the only reliable way to read them. The file a distro restores at boot is /etc/iptables/rules.v4 and rules.v6 on Debian and Ubuntu with iptables-persistent, and /etc/sysconfig/iptables and ip6tables on RHEL, Rocky, and Fedora. Neither file updates itself. A rule you add on the command line is live and absent from the file, and a line you edit into the file changes nothing until iptables-restore reads it, so the two drift apart silently. If ufw or firewalld is managing the box, the real source is their config (/etc/ufw/*.rules) and the iptables rules you see are generated output.

Is iptables deprecated?

The command is not going away, but the machinery under it already changed. nftables replaced iptables in the kernel from 3.13, and on Debian 10, Ubuntu 20.04, RHEL 8 and anything newer the iptables binary is usually iptables-nft, a shim that translates your commands into nftables rules. Check with sudo iptables -V: a version ending in (nf_tables) is the shim, (legacy) is the old xtables path. Existing scripts keep working, so there is no rush, and iptables will outlive most of the servers running it. New rulesets are worth writing in native nft syntax, or handing to ufw or firewalld, because the shim cannot express everything nftables can.

Is iptables running?

It is not a service you start, so there is nothing to be running. The rules sit in the kernel and are enforced from the moment they are added. What you actually want to know is whether any rules exist: sudo iptables -L -n -v shows them with counters, and chains that are empty with policy ACCEPT mean the firewall is doing nothing. The packet counters tell you whether a rule is being hit at all, which is the fastest way to prove a rule is in the wrong place. On RHEL there is an iptables.service, but it only loads the saved file at boot, so systemctl status iptables showing inactive does not mean your rules are gone.

Can ufw and iptables work together?

They share one ruleset, so they cannot be kept apart. ufw generates iptables rules from its own config, and a rule you add by hand sits in the same chains, usually below ufw's, where it never gets reached, and it disappears the next time ufw reloads. If you need something ufw's syntax cannot express, such as NAT or a DOCKER-USER filter, put it in /etc/ufw/before.rules or after.rules so ufw applies it as part of its own set. The same applies to firewalld. Pick one manager per box and let it own the rules; sudo iptables -S and sudo nft list ruleset show you everything that is really loaded, whoever wrote it.

Related cheat sheets