iptables & nftables Cheatsheet

Matches and Modules

Use this iptables & nftables reference while you build software engineering projects, review code, or refresh the syntax you reach for most.

The match modules worth knowing

-m <module> loads an extension that adds its own options. Some are implied: -p tcp loads the tcp module automatically, which is why --dport works without an explicit -m tcp.

ModuleMatches onTypical use
tcp / udpports, flagsthe bulk of every ruleset
conntrackflow statestateful accept, INVALID drops
multiportup to 15 ports at onceshorter rulesets
iprangean arbitrary address rangeranges that are not a clean CIDR
limitpacket rate, token bucketlog throttling, ICMP limits
hashlimitrate per source or destinationper-client abuse control
recentrecent-address listSSH brute-force slowdown
connlimitconcurrent connections per hostconnection flood limits
stateolder spelling of conntracklegacy rules, still works
macsource MAC addressLAN-only, MACs do not cross routers
ownerlocal uid, gidegress control in OUTPUT
commentnothing, adds a labeldocumenting rules
timetime of day, weekdaymaintenance windows
setan ipsetlarge address lists
addrtypeLOCAL, BROADCAST, MULTICASTanti-spoofing, martians
ttlIP TTL valuedetecting oddities

Ports and TCP flags

sudo iptables -A INPUT -p tcp --dport 443 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 6000:6010 -j ACCEPT       # inclusive range
sudo iptables -A INPUT -p tcp --sport 1024: -j ACCEPT           # 1024 and up
sudo iptables -A INPUT -p tcp -m multiport --dports 80,443,8080 -j ACCEPT
sudo iptables -A INPUT -p tcp -m multiport --ports 22,80 -j ACCEPT  # src or dst
sudo iptables -A INPUT -p udp --dport 53 -j ACCEPT
Flag optionMeaning
--dport, --sportsingle port, a:b range, or :b / a: open ended
--synshorthand for --tcp-flags SYN,RST,ACK,FIN SYN
--tcp-flags <examine> <set>check the first list, require exactly the second set
--tcp-option <n>a specific TCP option is present
# only connection openers, replies handled by the state rule
sudo iptables -A INPUT -p tcp --syn --dport 22 -j ACCEPT

# drop the classic scan and malformed-flag patterns
sudo iptables -A INPUT -p tcp --tcp-flags ALL NONE -j DROP           # null scan
sudo iptables -A INPUT -p tcp --tcp-flags ALL ALL -j DROP            # xmas
sudo iptables -A INPUT -p tcp --tcp-flags SYN,FIN SYN,FIN -j DROP
sudo iptables -A INPUT -p tcp --tcp-flags SYN,RST SYN,RST -j DROP
sudo iptables -A INPUT -p tcp --tcp-flags FIN,RST FIN,RST -j DROP

Connection state

sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
sudo iptables -A INPUT -m conntrack --ctstate INVALID -j DROP
sudo iptables -A INPUT -m conntrack --ctstate NEW -p tcp --dport 22 -j ACCEPT
sudo iptables -A INPUT -m conntrack --ctstate NEW ! --syn -p tcp -j DROP
sudo iptables -A INPUT -m conntrack --ctstate DNAT -j ACCEPT   # was DNATed
OptionMatches
--ctstateNEW, ESTABLISHED, RELATED, INVALID, UNTRACKED, SNAT, DNAT
--ctprotothe tracked protocol
--ctorigsrc, --ctorigdstthe original addresses before NAT
--ctrepldstthe reply-direction destination
--ctdirORIGINAL or REPLY direction

RELATED depends on protocol helper modules. Active FTP, TFTP, and SIP need nf_conntrack_ftp and friends loaded, and on modern kernels helpers must be enabled explicitly:

sudo modprobe nf_conntrack_ftp
sudo sysctl -w net.netfilter.nf_conntrack_helper=1
sudo iptables -A INPUT -p tcp -m conntrack --ctstate RELATED \
  -m helper --helper ftp -j ACCEPT

Rate limiting per source

limit is global, one bucket for the whole rule. hashlimit and recent track per address, which is what abuse control needs.

# global: at most 5 matches a second, burst of 10
sudo iptables -A INPUT -p icmp -m limit --limit 5/second --limit-burst 10 -j ACCEPT

# per source: 10 new HTTP connections a second per client IP
sudo iptables -A INPUT -p tcp --dport 80 -m conntrack --ctstate NEW \
  -m hashlimit --hashlimit-above 10/sec --hashlimit-burst 20 \
  --hashlimit-mode srcip --hashlimit-name http-flood -j DROP

# at most 20 concurrent connections per client
sudo iptables -A INPUT -p tcp --dport 80 \
  -m connlimit --connlimit-above 20 --connlimit-mask 32 -j REJECT

# per /24 rather than per host
sudo iptables -A INPUT -p tcp --dport 80 \
  -m connlimit --connlimit-above 200 --connlimit-mask 24 -j REJECT
OptionMeaning
--limit N/{second,minute,hour,day}average rate the rule may match
--limit-burst Nbucket size, matches allowed back to back
--hashlimit-modesrcip, dstip, srcport, dstport, comma-combined
--hashlimit-above / --hashlimit-uptomatch when over, or under, the rate
--connlimit-maskprefix length the count is grouped by, 32 is per host

The recent module

recent keeps named lists of addresses with timestamps, so one rule can record and another can act on that history.

# slow SSH brute force: 4 attempts per minute per source
sudo iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW \
  -m recent --set --name SSH --rsource
sudo iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW \
  -m recent --update --seconds 60 --hitcount 4 --name SSH --rsource \
  -j LOG --log-prefix "ssh-throttle: "
sudo iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW \
  -m recent --update --seconds 60 --hitcount 4 --name SSH --rsource -j DROP
OptionEffect
--setadd the address to the list, refreshing its timestamp
--updatematch if listed within --seconds, and refresh the timestamp
--rcheckmatch if listed, do not refresh
--removedrop the address from the list
--seconds Nonly consider hits within the window
--hitcount Nrequire at least N recorded hits
--rsource / --rdestkey the list on source or destination address
cat /proc/net/xt_recent/SSH          # inspect the list
echo / | sudo tee /proc/net/xt_recent/SSH   # clear it
echo +203.0.113.9 | sudo tee /proc/net/xt_recent/SSH   # add by hand

The list is capped by ip_pkt_list_tot (20 hits by default), so a --hitcount above that silently never fires. Load xt_recent with a larger ip_pkt_list_tot if you need a bigger window.

Large address lists with ipset

Thousands of -s rules are checked one at a time. An ipset is a hash the kernel looks up once, so one rule replaces the whole list.

sudo ipset create blocklist hash:net
sudo ipset add blocklist 203.0.113.0/24
sudo ipset add blocklist 198.51.100.7
sudo iptables -I INPUT -m set --match-set blocklist src -j DROP

sudo ipset create trusted hash:ip timeout 3600     # entries expire
sudo ipset add trusted 192.0.2.10 timeout 600
sudo ipset list blocklist
sudo ipset save > /etc/ipset.conf
sudo ipset restore < /etc/ipset.conf
Set typeHolds
hash:ipsingle addresses
hash:netCIDR prefixes
hash:ip,portaddress and port pairs
hash:macMAC addresses
bitmap:porta port range, very fast

Time windows and address types

# maintenance page reachable only during a window, UTC by default
sudo iptables -A INPUT -p tcp --dport 8080 -m time \
  --timestart 02:00 --timestop 04:00 --weekdays Sat,Sun -j ACCEPT

# use local time instead of UTC
sudo iptables -A INPUT -p tcp --dport 8080 -m time --kerneltz \
  --timestart 09:00 --timestop 17:00 -j ACCEPT

# martians and broadcast handling
sudo iptables -A INPUT -m addrtype --src-type BROADCAST -j DROP
sudo iptables -A INPUT -i wan0 -m addrtype --dst-type LOCAL -j ACCEPT

-m time compares against UTC unless --kerneltz is given, which is the single most common mistake with this module.