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.

Updated 6 min readChecked against fail2ban 1.1.1

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.

/etc/fail2ban/jail.local
[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:

output
$ sudo fail2ban-client get sshd actions
The jail sshd has the following actions:
nftables

Debian 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:

bash
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 reject

and 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-table and the base chain f2b-chain. IPv4 bans go into addr-set-<jail>, IPv6 bans into addr6-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 an accept in another table.
  • flags interval (fail2ban 1.1.1, addr_options) lets a set hold CIDR ranges as well as single addresses.
  • reject by default. Set blocktype to drop silently instead: banaction = nftables[blocktype=drop].
  • The table only exists after the first ban. nft list table inet f2b-table reports 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:

/etc/fail2ban/jail.local
[recidive]
enabled   = true
banaction = %(banaction_allports)s
protocol  = all

With 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#

bash
# 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.42

The set's elements = { ... } line holds the banned addresses. fail2ban's own view is the authoritative one, with expiry times:

output
$ sudo fail2ban-client get sshd banip --with-time
203.0.113.42 	2026-10-25 18:36:10 + 3600 = 2026-10-25 19:36:10

More 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#

  1. Set the new action in /etc/fail2ban/jail.local (the block at the top of this page). Remove any per-jail banaction = iptables... lines, or they override the default.
  2. Restart fail2ban: sudo systemctl restart fail2ban. On stop, the running server removes its f2b-<jail> iptables chains with the old action. On start, it re-applies current bans from its database into the new nftables sets.
  3. Check nothing is left in iptables: sudo iptables -S | grep f2b- and sudo ip6tables -S | grep f2b- should print nothing. Delete leftovers by hand if fail2ban crashed earlier.
  4. Check the new sets: sudo nft list table inet f2b-table after the first ban, or ban a test address: sudo fail2ban-client set sshd banip 192.0.2.15 (prints 1), 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:

bash
sudo apt install nftables      # Debian, Ubuntu
sudo dnf install nftables      # Fedora, RHEL family

Minimal 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.conf starts with flush ruleset, which also deletes fail2ban's table. fail2ban doesn't notice. Restart fail2ban after reloading nftables, or replace flush ruleset with commands that flush only your own tables.
  • Banned but still connecting. Docker published ports and traffic routed to VMs go through forward, not input, so the ban never matches. See banned but not blocked and the Docker guide.
  • Running inside a container. fail2ban needs CAP_NET_ADMIN and host networking to change the host's nftables.
  • SELinux denials (RHEL family, execmem) affected the nftables action before 1.1.1, which replaced its grep -P calls.

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.