# fail2ban with nftables

> fail2ban's nftables action explained: the inet f2b-table, addr-set sets, multiport vs allports, listing bans with nft, IPv6, Debian 12/13 and nft not found.
>
> Source: https://fail2ban.dev/guides/nftables/ · Updated 2026-10-25 · Checked against fail2ban 1.1.1
> fail2ban.dev is an independent guide, not the official fail2ban project.

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`.

```ini
[DEFAULT]
banaction          = nftables
banaction_allports = nftables[type=allports]
```

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

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

## What the nftables action creates

From [action.d/nftables.conf](/actions/#action-nftables), on the jail's **first ban** (not at startup) fail2ban runs the equivalent of:

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

- **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`:

```ini
[recidive]
enabled   = true
banaction = %(banaction_allports)s
protocol  = all
```

## View bans with nft

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

```text
$ 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
```

## 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](/config/whitelist/) 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](/guides/iptables/).

## 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 dnf install nftables      # Fedora, RHEL family
```

- **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](/troubleshooting/not-banning/#part-2-the-ip-is-banned-but-still-connects) and the [Docker guide](/guides/docker/).
- **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.
