Guides
fail2ban with nftables
nftables is the default ban action on Debian 13, Ubuntu 24.04 and newer. fail2ban keeps its bans in its own table and named sets, which is fast and stays out of your firewall's way. Here's what it creates, how to inspect it, and how to switch from iptables.
To make fail2ban ban with nftables, set banaction = nftables and banaction_allports = nftables[type=allports] under [DEFAULT] in /etc/fail2ban/jail.local. On Debian 13 and Ubuntu 24.04 or newer, that's already the packaged default. fail2ban then creates a table inet f2b-table with one set per jail, addr-set-<jail>. List the bans with sudo nft list table inet f2b-table.
[DEFAULT]
banaction = nftables
banaction_allports = nftables[type=allports]Is nftables already your default?#
| Distribution | fail2ban | Default banaction |
|---|---|---|
| Debian 13 trixie | 1.1.0 | nftables (from jail.d/defaults-debian.conf) |
| Debian 12 bookworm | 1.0.2 | iptables-multiport (set it to nftables yourself) |
| Ubuntu 24.04 / 26.04 | 1.0.2 / 1.1.0 | nftables (from jail.d/defaults-debian.conf) |
| Ubuntu 22.04 | 0.11.2 | iptables-multiport |
| Fedora, RHEL/Rocky/Alma (EPEL) | 1.1.0 | firewalld rich rules (fail2ban-firewalld package) |
| Upstream 1.1.1 from source | 1.1.1 | nftables (in paths-debian.conf, which the stock config includes) |
Check what your jails actually use:
$ sudo fail2ban-client get sshd actions
The jail sshd has the following actions:
nftablesDebian 12 has nftables as its firewall framework but the fail2ban 1.0.2 package still defaults to iptables. The iptables commands go through the iptables-nft compatibility layer, so they work, but you get one rule per banned IP instead of a set. Switching is the two-line config above.
What the nftables action creates#
From action.d/nftables.conf, on the jail's first ban (not at startup) fail2ban runs the equivalent of:
nft add table inet f2b-table
nft -- add chain inet f2b-table f2b-chain { type filter hook input priority -1 \; }
nft add set inet f2b-table addr-set-sshd { type ipv4_addr\; flags interval\; }
nft add rule inet f2b-table f2b-chain tcp dport { ssh } ip saddr @addr-set-sshd rejectand for each ban, nft add element inet f2b-table addr-set-sshd { 203.0.113.42 }.
- One table, one chain, one set per jail and family. All jails share
inet f2b-tableand the base chainf2b-chain. IPv4 bans go intoaddr-set-<jail>, IPv6 bans intoaddr6-set-<jail>. - Hook
input, priority -1. The chain sees traffic addressed to this host and runs just before the usual filter chains at priority 0 (yours, firewalld's or UFW's). A ban can't be overridden by anacceptin another table. flags interval(fail2ban 1.1.1,addr_options) lets a set hold CIDR ranges as well as single addresses.rejectby default. Setblocktypeto drop silently instead:banaction = nftables[blocktype=drop].- The table only exists after the first ban.
nft list table inet f2b-tablereports an error on a fresh start. That's normal, not a failure.
When a jail stops, fail2ban deletes its rule and set, and deletes the table once no sets remain.
multiport vs allports#
type decides what the rule matches:
| Action | Rule it adds | Use for |
|---|---|---|
nftables / nftables[type=multiport] / nftables-multiport |
tcp dport { <port> } ip saddr @set reject |
Normal jails: ban only the attacked service |
nftables[type=allports] / nftables-allports |
meta l4proto { tcp } ip saddr @set reject |
recidive, scanners: ban every port |
allports still matches only the jail's protocol, which defaults to tcp. To block every protocol for repeat offenders, including UDP and ICMP, use protocol = all:
[recidive]
enabled = true
banaction = %(banaction_allports)s
protocol = allWith protocol = all the rule has no protocol match at all, just ip saddr @addr-set-recidive reject. For jails on UDP services, set protocol = udp (for example OpenVPN) or protocol = tcp,udp. The action adds one rule per protocol.
View bans with nft#
# whole table: chain, rules and every set with its elements
sudo nft list table inet f2b-table
# one jail's IPv4 and IPv6 sets
sudo nft list set inet f2b-table addr-set-sshd
sudo nft list set inet f2b-table addr6-set-sshd
# is one address banned anywhere?
sudo nft list table inet f2b-table | grep 203.0.113.42The set's elements = { ... } line holds the banned addresses. fail2ban's own view is the authoritative one, with expiry times:
$ sudo fail2ban-client get sshd banip --with-time
203.0.113.42 2026-10-25 18:36:10 + 3600 = 2026-10-25 19:36:10More listing options are on list banned IPs. Unban through fail2ban (fail2ban-client unban <IP>), not with nft delete element. Otherwise fail2ban still thinks the address is banned.
IPv6#
The table family is inet, so one table covers both IPv4 and IPv6. fail2ban picks the set by address family: addr-set-sshd for 203.0.113.42, addr6-set-sshd for 2001:db8::25. Nothing to configure, but check that the jail's log actually contains IPv6 addresses and that your ignoreip list includes your own IPv6 prefix.
Migrate from iptables to nftables#
- Set the new action in
/etc/fail2ban/jail.local(the block at the top of this page). Remove any per-jailbanaction = iptables...lines, or they override the default. - Restart fail2ban:
sudo systemctl restart fail2ban. On stop, the running server removes itsf2b-<jail>iptables chains with the old action. On start, it re-applies current bans from its database into the new nftables sets. - Check nothing is left in iptables:
sudo iptables -S | grep f2b-andsudo ip6tables -S | grep f2b-should print nothing. Delete leftovers by hand if fail2ban crashed earlier. - Check the new sets:
sudo nft list table inet f2b-tableafter the first ban, or ban a test address:sudo fail2ban-client set sshd banip 192.0.2.15(prints1), then unban it.
If you also keep custom actions derived from iptables (before = iptables.conf), check them. 1.1.1 rewrote the iptables action and some tags changed. See the iptables guide.
nftables vs iptables for fail2ban#
| Feature | nftables action | iptables(-multiport) action |
|---|---|---|
| Banned IPs stored as | Elements of a named set | One rule per IP in chain f2b-<jail> |
| Lookup cost with many bans | Constant (set lookup) | Grows with every ban (rules evaluated in order) |
| IPv4 + IPv6 | One inet table |
Separate iptables and ip6tables chains |
| Coexists with firewalld/UFW | Own table, priority -1 | Inserts jumps into INPUT |
| Extra package | nftables (nft) |
iptables (and ipset for the ipset variant) |
With a few dozen bans you won't notice a difference. With thousands, iptables chains get slow and nftables sets don't. iptables-ipset closes most of that gap if you must stay on iptables.
"nft not found" and other errors#
If the nft binary is missing, bans fail. /var/log/fail2ban.log shows the failed command ending in -- returned 127, followed by HINT on 127: "Command not found". Install it:
sudo apt install nftables # Debian, Ubuntu
sudo dnf install nftables # Fedora, RHEL familyMinimal cloud images, containers and some appliance builds (Proxmox among them) ship without nft. fail2ban's package only recommends it. Then restart fail2ban. Other checks:
- Bans disappear after
systemctl restart nftables. Debian's default/etc/nftables.confstarts withflush ruleset, which also deletes fail2ban's table. fail2ban doesn't notice. Restart fail2ban after reloading nftables, or replaceflush rulesetwith commands that flush only your own tables. - Banned but still connecting. Docker published ports and traffic routed to VMs go through
forward, notinput, so the ban never matches. See banned but not blocked and the Docker guide. - Running inside a container. fail2ban needs
CAP_NET_ADMINand host networking to change the host's nftables. - SELinux denials (RHEL family,
execmem) affected the nftables action before 1.1.1, which replaced itsgrep -Pcalls.
FAQ#
Does fail2ban work with nftables?#
Yes. The nftables action is built in and is the default on Debian 13 and Ubuntu 24.04+. It stores bans in sets inside its own inet f2b-table table and blocks them with one rule per jail. Enable it with banaction = nftables and banaction_allports = nftables[type=allports].
How do I see fail2ban bans in nftables?#
Run sudo nft list table inet f2b-table to see the chain, rules and every jail's set, or sudo nft list set inet f2b-table addr-set-sshd for one jail. IPv6 bans are in addr6-set-<jail>. The table only appears after the first ban.
How do I use nftables with fail2ban on Debian 12?#
Debian 12's fail2ban 1.0.2 defaults to iptables-multiport. Add banaction = nftables and banaction_allports = nftables[type=allports] to [DEFAULT] in /etc/fail2ban/jail.local, make sure the nftables package is installed, and restart fail2ban. Also set backend = systemd for sshd, since Debian 12 has no auth.log.
Why does fail2ban say nft not found?#
The nftables package isn't installed, or nft isn't in the PATH of the fail2ban service. The log shows the command -- returned 127 with a "Command not found" hint. Install it with apt install nftables or dnf install nftables, then restart fail2ban.
Spotted something wrong or out of date? Tell us — every page is checked against the current fail2ban release.