iptables & nftables Cheatsheet
Concepts and Tables
Use this iptables & nftables reference while you build software engineering projects, review code, or refresh the syntax you reach for most.
Two commands, one kernel subsystem
Modern Linux filters packets with nftables in the kernel. The iptables
command you type is usually a compatibility front end that writes nftables rules.
| Command | What it talks to | Version string shows |
|---|---|---|
iptables-legacy | the old x_tables kernel API | (legacy) |
iptables-nft | nftables, through a translation layer | (nf_tables) |
nft | nftables directly, native syntax | nftables v1.x |
iptables --version # iptables v1.8.10 (nf_tables) sudo iptables-legacy -S # rules in the legacy tables, often empty sudo nft list ruleset # the real ruleset, including iptables-nft rules
Do not mix the two backends. Rules added with iptables-legacy are invisible
to nft list ruleset and to iptables-nft, and both sets are evaluated by the
kernel, so a "missing" rule that still blocks traffic is almost always the other
backend.
What ships where
| Distribution | Default packet filter | Default front end |
|---|---|---|
| RHEL / Rocky / Alma 8, 9, 10 | nftables | firewalld |
| Fedora 32+ | nftables | firewalld |
| CentOS / RHEL 7 (legacy) | legacy iptables | firewalld, iptables backend |
| Debian 10+ | iptables-nft is the default alternative | none, nft or iptables by hand |
| Ubuntu 20.04+ | iptables-nft | ufw installed, inactive by default on server |
| openSUSE Leap 15+ / SLES 15 | nftables | firewalld |
| Arch Linux | nftables (iptables-nft package) | none |
| Alpine Linux | iptables or nftables, both packaged | none, /etc/init.d scripts |
# which backend does /sbin/iptables actually point at, Debian and Ubuntu sudo update-alternatives --display iptables sudo update-alternatives --config iptables # switch legacy <-> nft # RHEL family, the service that owns the ruleset systemctl is-active firewalld nftables iptables
On any RHEL-family box, assume firewalld owns the ruleset. Writing raw
iptables rules there works until firewalld reloads and flushes them.
Tables
A table groups rules by the kind of work they do. Five exist, and most systems only use two.
| Table | Purpose | Common chains |
|---|---|---|
filter | accept or drop, the default table | INPUT, FORWARD, OUTPUT |
nat | rewrite addresses and ports | PREROUTING, INPUT, OUTPUT, POSTROUTING |
mangle | change packet fields (TTL, TOS, MARK) | all five |
raw | act before connection tracking (NOTRACK) | PREROUTING, OUTPUT |
security | SELinux packet labelling (SECMARK) | INPUT, FORWARD, OUTPUT |
sudo iptables -t filter -L -n # -t filter is the default, so -t is optional
sudo iptables -t nat -L -n -v
sudo iptables -t mangle -S
sudo iptables -t raw -SChains and hooks
A chain is a list of rules attached to a point in the kernel's packet path.
| Chain | Fires when |
|---|---|
PREROUTING | packet arrives, before the routing decision |
INPUT | packet is destined for this host |
FORWARD | packet is routed through this host to somewhere else |
OUTPUT | packet was generated locally |
POSTROUTING | packet is about to leave, after the routing decision |
┌─────────────┐ in ──> PREROUTING ──> routing? ├──> FORWARD ──> POSTROUTING ──> out (nat, mangle)└──┬──────────┘ (filter) (nat, mangle) │ local ▲ v │ INPUT OUTPUT (filter) (filter, nat) │ ▲ v │ local process ─────────────────────-┘
Two rules follow from the diagram and explain most confusion:
- Traffic to this host hits INPUT, traffic through it hits FORWARD.
Blocking a port in INPUT does nothing to traffic being routed or bridged.
- DNAT happens in PREROUTING, so by the time FORWARD and INPUT see the
packet its destination is already the new address. Filter on the real
target, not on the published one.
Rule evaluation
Rules in a chain are checked top to bottom. The first rule whose matches all succeed applies its target, and for terminating targets evaluation of that chain stops there.
| Target | Effect | Terminating |
|---|---|---|
ACCEPT | let the packet through this table's chain | yes |
DROP | discard silently, sender waits and times out | yes |
REJECT | discard and send an ICMP error or TCP reset | yes |
RETURN | leave a user chain, or apply the built-in policy | yes |
LOG | write to the kernel log, then keep going | no |
DNAT / SNAT / MASQUERADE / REDIRECT | rewrite addresses | yes, in nat |
MARK / TOS / TTL | change metadata, then keep going | no |
<user chain> | jump into another chain | no, returns if it falls through |
If no rule matches, the chain's policy applies. Policies exist only on
built-in chains, and the shipped default is ACCEPT everywhere.
sudo iptables -L INPUT -n | head -1 # Chain INPUT (policy ACCEPT) sudo iptables -P INPUT DROP # set the policy, see the warning below
Setting a
DROPpolicy over SSH locks you out the instant it takes effect unless an ACCEPT rule for your own session already exists above it. Add the allow rules first, verify them, then change the policy.
Connection tracking
conntrack is what makes a stateful firewall possible. The kernel remembers
flows, so replies to connections you allowed do not need their own rules.
| State | Meaning |
|---|---|
NEW | first packet of a flow conntrack has not seen before |
ESTABLISHED | part of a flow already seen in both directions |
RELATED | a new flow spawned by an existing one, such as an FTP data channel or an ICMP error |
INVALID | does not fit any known flow, often a late or malformed packet |
UNTRACKED | matched a NOTRACK rule in the raw table |
sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT sudo conntrack -L | head # live flow table, needs conntrack-tools cat /proc/sys/net/netfilter/nf_conntrack_count # current entries cat /proc/sys/net/netfilter/nf_conntrack_max # table size
That single ESTABLISHED,RELATED rule at the top of INPUT is the foundation of
every practical ruleset. Without it, a default-deny policy also blocks the
answers to your own outbound connections.