iptables & nftables Cheatsheet
nftables
Use this iptables & nftables reference while you build software engineering projects, review code, or refresh the syntax you reach for most.
Why nftables replaced iptables
| Concern | iptables | nftables |
|---|---|---|
| Commands | iptables, ip6tables, arptables, ebtables | one, nft |
| IPv4 and IPv6 | two separate rulesets | one inet family covers both |
| Multiple criteria | one rule per port, per address | native sets and maps in a single rule |
| Updating rules | rule by rule, non-atomic | nft -f replaces a whole ruleset atomically |
| Built-in chains | fixed, always present, always evaluated | you create only the chains you need |
| Counters | on every rule, always | opt in per rule |
nft --version sudo nft list ruleset # the whole thing, including iptables-nft rules sudo nft list tables sudo nft list table inet filter sudo nft -f /etc/nftables.conf # load a file, atomically sudo nft -c -f /etc/nftables.conf # check syntax without loading sudo nft flush ruleset # remove everything
Families, tables, and chains
sudo nft add table inet filter sudo nft add chain inet filter input \ '{ type filter hook input priority 0 ; policy drop ; }' sudo nft add rule inet filter input iif lo accept sudo nft add rule inet filter input ct state established,related accept sudo nft add rule inet filter input tcp dport 22 accept
| Family | Covers |
|---|---|
ip | IPv4 only |
ip6 | IPv6 only |
inet | both, one ruleset, the usual choice |
arp | ARP, replaces arptables |
bridge | bridged frames, replaces ebtables |
netdev | earliest possible hook, per interface, for very cheap drops |
| Hook | Equivalent iptables chain |
|---|---|
prerouting | PREROUTING |
input | INPUT |
forward | FORWARD |
output | OUTPUT |
postrouting | POSTROUTING |
ingress | earlier than prerouting, netdev and inet |
| Priority name | Value | Replaces |
|---|---|---|
raw | -300 | the raw table |
mangle | -150 | the mangle table |
dstnat | -100 | nat PREROUTING |
filter | 0 | the filter table |
security | 50 | the security table |
srcnat | 100 | nat POSTROUTING |
Chain types are filter, nat, and route. A NAT chain must use the nat type
and a dstnat or srcnat priority, and unlike iptables there is no shared table
you have to share with everyone else's rules.
Translating from iptables
iptables-translate prints the nftables equivalent of a command without running
anything, which is the fastest way to learn the syntax.
iptables-translate -A INPUT -p tcp --dport 22 -j ACCEPT # nft add rule ip filter INPUT tcp dport 22 counter accept iptables-restore-translate -f /etc/iptables/rules.v4 > /etc/nftables.conf
| iptables | nftables |
|---|---|
-p tcp --dport 22 | tcp dport 22 |
-s 10.0.0.0/8 | ip saddr 10.0.0.0/8 |
-d 192.168.1.5 | ip daddr 192.168.1.5 |
-i eth0 | iif eth0 (or iifname "eth0" for a name match) |
-o eth0 | oif eth0 |
-m conntrack --ctstate NEW | ct state new |
-m multiport --dports 80,443 | tcp dport { 80, 443 } |
-j ACCEPT / DROP | accept / drop |
-j REJECT --reject-with tcp-reset | reject with tcp reset |
-j LOG --log-prefix "x: " | log prefix "x: " |
-j SNAT --to-source 203.0.113.1 | snat to 203.0.113.1 |
-j MASQUERADE | masquerade |
-j DNAT --to-destination 10.0.0.5:80 | dnat to 10.0.0.5:80 |
-m limit --limit 5/min | limit rate 5/minute |
-m comment --comment "x" | comment "x" |
! -s 10.0.0.0/8 | ip saddr != 10.0.0.0/8 |
Note iif matches the interface index and iifname matches the name. An
index is faster but breaks if the interface is recreated, so use iifname for
anything that can come and go, such as a VPN or container interface.
A complete host ruleset
#!/usr/sbin/nft -f flush ruleset table inet filter { set admin_hosts { type ipv4_addr flags interval elements = { 10.0.0.0/24, 192.0.2.10 } } chain input { type filter hook input priority filter ; policy drop ; iif lo accept ct state established,related accept ct state invalid drop ip protocol icmp icmp type { echo-request, destination-unreachable, \ time-exceeded } limit rate 5/second accept ip6 nexthdr icmpv6 accept tcp dport 22 ip saddr @admin_hosts accept comment "ssh, admin nets only" tcp dport { 80, 443 } accept comment "public web" limit rate 5/minute log prefix "nft-drop: " level info reject with icmpx type admin-prohibited } chain forward { type filter hook forward priority filter ; policy drop ; } chain output { type filter hook output priority filter ; policy accept ; } }
sudo nft -c -f /etc/nftables.conf && sudo nft -f /etc/nftables.conf sudo systemctl enable --now nftables
Do not drop all of ICMPv6. IPv6 depends on neighbour discovery and router
advertisements, so a blanket ip6 nexthdr icmpv6 drop breaks address resolution
and the host loses connectivity on that protocol entirely.
NAT and forwarding in nftables
table inet nat {
chain prerouting {
type nat hook prerouting priority dstnat ; policy accept ;
iifname "wan0" tcp dport 443 dnat ip to 10.0.0.20:443
iifname "wan0" tcp dport 8080 redirect to :80
}
chain postrouting {
type nat hook postrouting priority srcnat ; policy accept ;
oifname "wan0" ip saddr 10.0.0.0/24 masquerade
oifname "wan0" ip saddr 10.0.1.0/24 snat to 203.0.113.10
}
}sudo sysctl -w net.ipv4.ip_forward=1
sudo nft list table inet natThe same rule as iptables applies: the forward chain sees the post-DNAT destination, so filter on the internal address.
Sets, maps, and counters
Sets are the feature that shortens real rulesets the most. One lookup replaces hundreds of rules.
# a named set, with entry expiry sudo nft add set inet filter blocklist \ '{ type ipv4_addr ; flags interval, timeout ; timeout 1h ; }' sudo nft add element inet filter blocklist '{ 203.0.113.0/24, 198.51.100.7 }' sudo nft add rule inet filter input ip saddr @blocklist drop sudo nft list set inet filter blocklist sudo nft delete element inet filter blocklist '{ 198.51.100.7 }'
# a map: port to backend, one rule instead of one per service table ip nat { map port_to_host { type inet_service : ipv4_addr elements = { 80 : 10.0.0.21, 443 : 10.0.0.22, 25 : 10.0.0.23 } } chain prerouting { type nat hook prerouting priority dstnat ; iifname "wan0" dnat ip to tcp dport map @port_to_host } }
# counters are opt in, and can be named and shared sudo nft add counter inet filter ssh_hits sudo nft add rule inet filter input tcp dport 22 counter name ssh_hits accept sudo nft list counters sudo nft reset counters sudo nft list ruleset -a # show handles, needed to delete a single rule sudo nft delete rule inet filter input handle 12 sudo nft monitor trace # live rule tracing, after marking with meta nftrace
| Feature | What it buys |
|---|---|
| Named set | update the list without touching the rules |
flags interval | the set may hold CIDR prefixes and ranges |
flags timeout | entries expire on their own, useful for temporary blocks |
| Map | a lookup that also produces a value, such as a NAT target |
| Verdict map | jump to different chains based on a match, no rule chain needed |
| Named counter | shared across rules, survives rule edits |
-a and handles | the only reliable way to delete one rule |