nftables for iptables Users
Introduction¶
nftables is the successor to the xtables family (iptables, ip6tables,
arptables, ebtables). One tool, one syntax, atomic ruleset reloads, native
sets/maps, and better performance. Modern distros use it as the default backend
(firewalld, ufw, and even iptables via iptables-nft shims sit on top).
The model¶
- Family —
inet(IPv4+IPv6 together — use this),ip,ip6,arp,bridge,netdev. - Table — a container you name. No built-in tables; you create them.
- Chain — either a base chain (attached to a Netfilter hook with a priority and a policy) or a regular chain (jumped to, like a function).
- Rule — match + statement (
accept,drop,jump,counter,log, ...).
Hooks: prerouting, input, forward, output, postrouting. Priority
numbers order multiple base chains on the same hook (lower runs first;
filter = 0, nat around -100/100, mangle = -150).
A complete server ruleset¶
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
set ssh_allow {
type ipv4_addr
flags interval
elements = { 203.0.113.0/24, 198.51.100.10 }
}
chain input {
type filter hook input priority filter; policy drop;
ct state established,related accept
ct state invalid drop
iif "lo" accept
# ICMP / ICMPv6 — needed, don't blanket-drop
ip protocol icmp icmp type { echo-request, destination-unreachable, time-exceeded } accept
ip6 nexthdr icmpv6 icmpv6 type { echo-request, nd-neighbor-solicit, nd-neighbor-advert,
nd-router-advert, packet-too-big, destination-unreachable, time-exceeded } accept
tcp dport 22 ip saddr @ssh_allow ct state new limit rate 5/minute accept
tcp dport { 80, 443 } accept
# log the rest before the policy drop
limit rate 5/minute log prefix "nft-drop-in " level info
}
chain forward {
type filter hook forward priority filter; policy drop;
ct state established,related accept
# iifname "internal" oifname "wan" accept # if this box routes
}
chain output {
type filter hook output priority filter; policy accept;
}
}
# NAT example (a gateway)
table inet nat {
chain postrouting {
type nat hook postrouting priority srcnat; policy accept;
oifname "wan" masquerade
}
chain prerouting {
type nat hook prerouting priority dstnat; policy accept;
iifname "wan" tcp dport 8080 dnat ip to 10.0.0.5:80
}
}
nft -f /etc/nftables.conf # atomic load (flush + apply as one transaction)
systemctl enable --now nftables
Concept mapping¶
| iptables | nftables |
|---|---|
-A INPUT -j ACCEPT |
rule in chain input |
-m state --state ESTABLISHED,RELATED |
ct state established,related |
-p tcp --dport 22 |
tcp dport 22 |
-s 10.0.0.0/8 |
ip saddr 10.0.0.0/8 |
| ipset | native set { ... } |
| separate iptables + ip6tables | one inet table |
-j LOG --log-prefix |
log prefix "..." |
iptables-save / -restore |
nft list ruleset / nft -f |
Sets and maps¶
# a set you update at runtime (fail2ban-style)
set blacklist { type ipv4_addr; flags dynamic, timeout; }
# add: nft add element inet filter blacklist { 198.51.100.66 timeout 1h }
# verdict map: port -> action
map port_action { type inet_service : verdict; elements = { 22 : jump ssh_chain, 80 : accept } }
tcp dport vmap @port_action
Maps and vmap replace long chains of rules with a hash lookup — much faster at
scale.
Verification and troubleshooting¶
nft list ruleset # everything
nft list table inet filter
nft -a list chain inet filter input # -a shows handles (for delete)
nft monitor trace # live rule tracing (add `meta nftrace set 1` to a rule)
nft list counters
- Rules loaded but not matching — wrong family (
iptable won't see IPv6; useinet), wrong hook/priority (anat preroutingat the wrong priority runs after filter), or an earlieraccept/dropshort-circuited.nft monitor traceshows the path. nft -ffails halfway — it shouldn't; nft applies atomically. A syntax error aborts the whole file with a line number and the old ruleset stays.- iptables and nftables rules both present —
iptables-legacyvsiptables-nft, plus firewalld/docker.iptables -Vshows which backend.nft list rulesetshows everything regardless. Don't mix hand-written nft with firewalld-managed tables. - Locked out —
policy dropwith noestablishedaccept, or the SSH rule is after the log/drop. Order: established → lo → allow rules → log → (policy). ct statenot working — conntrack module; on a pure filtering box it's fine, butct stateneedsnf_conntrackloaded (it is, if any rule uses it).- Ruleset not persisting —
/etc/nftables.confnot enabled via thenftables.service, or the distro expects it elsewhere.systemctl status nftables.
Related tools and reading¶
- On-site: Port Checker, Subnet Calculator.
- Related posts: firewalld: zones and rich rules, sysctl network tuning for servers.
Stuck on something this site can't fix?Reach out to Prabath directly on LinkedIn.More in Linux & Sysadmin
resolv.conf and systemd-resolved
On modern distros /etc/resolv.conf points at a stub resolver managed by systemd-resolved. Editing it directly usually does nothing — here's the real model.
October 24, 2026Tuning Open File Limits on Linux
Four different limits control how many files a process can open. 'Too many open files' means you're hitting one specific one — here's how to find and raise it.
October 24, 2026ss and conntrack for Connection Debugging
ss shows you every socket and its send/receive queues; conntrack shows what the firewall is tracking. Together they explain most 'connection refused' and 'no buffer space' problems.
October 24, 2026