# The recidive jail: ban repeat offenders

> Set up the fail2ban recidive jail to ban repeat offenders on all ports for a week or forever, make it read the journal, and fix recidive not working.
>
> Source: https://fail2ban.dev/config/recidive/ · Updated 2026-10-25 · Checked against fail2ban 1.1.1
> fail2ban.dev is an independent guide, not the official fail2ban project.

The **recidive** jail bans IP addresses that other jails keep banning. It reads fail2ban's own log (`/var/log/fail2ban.log`), counts the `Ban` lines per IP, and when an address is banned 5 times within a day it blocks that address on **all ports for one week**. Enable it with `[recidive]` and `enabled = true` in `/etc/fail2ban/jail.local`. For permanent bans, set `bantime = -1`.

```ini
[recidive]
enabled = true
```
sudo fail2ban-client reload
sudo fail2ban-client status recidive
```

A normal jail like `sshd` bans an attacker for an hour, on the SSH port. Persistent bots wait out the ban and come back, or move on to your mail or web ports. recidive ("relapse into crime") catches that pattern:

Every ban fail2ban makes is written to its log as a `NOTICE` line:

```log
```
2. When an IP reaches `maxretry` bans within `findtime`, recidive bans it with `banaction_allports`, so every port is closed, not only the one it attacked.

You can see recidive counting in the log:

```log
```

From the stock `jail.conf`:

```ini
[recidive]
logpath  = /var/log/fail2ban.log
banaction = %(banaction_allports)s
bantime  = 1w
findtime = 1d
```
|---|---|---|
| `logpath` | `/var/log/fail2ban.log` | Reads fail2ban's own log file |
| `banaction` | `%(banaction_allports)s` | All-ports ban: `nftables[type=allports]` with the 1.1.1 Debian paths, `iptables-allports` in older versions |
| `bantime` | `1w` | One-week ban |
| `findtime` | `1d` | Bans counted over the last 24 hours |
| `maxretry` | `5` (inherited from `[DEFAULT]`) | 5 bans in a day trigger recidive |
| `filter` | `recidive` | `filter.d/recidive.conf`, also has `journalmatch = _SYSTEMD_UNIT=fail2ban.service` |

## recidive example: a stricter setup

Five bans a day is lenient. With `bantime = 1h` in your other jails, this catches IPs banned three times in a day and locks them out for a month:

```ini
[recidive]
enabled  = true
maxretry = 3
findtime = 1d
bantime  = 4w
```

```ini
enabled         = true
bantime         = 1w
bantime.maxtime = 12w
```

To ban repeat offenders forever:

```ini
[recidive]
enabled  = true
maxretry = 3
findtime = 1d
bantime  = -1
```

- IPs get reassigned. A permanently banned cloud or mobile address may belong to a real customer next month. A 4-week or 12-week ban is usually enough.
- The firewall set only grows. nftables and ipset sets handle many thousands of entries without trouble; individual iptables rules (`iptables-allports`) slow down with very large lists.
- Remove a permanent ban with `sudo fail2ban-client set recidive unbanip <IP>`, or `sudo fail2ban-client unban <IP>` to clear it from every jail ([unban an IP](/unban-ip/)).

## Requirements: log level and log target

recidive only works if fail2ban's `Ban` lines reach the place it reads. Two settings in `fail2ban.conf` / `fail2ban.local` control that ([fail2ban.conf reference](/config/fail2ban-conf/)).

### loglevel must be NOTICE, INFO or DEBUG

Ban lines are logged at `NOTICE`. With `loglevel = WARNING` or `ERROR`, they're never written and recidive never matches anything. The default `INFO` is fine.

Avoid `DEBUG` while recidive is enabled. The stock `jail.conf` warns that debug output can make fail2ban read and log its own lines in a loop.

```text
$ sudo fail2ban-client get loglevel
Current logging level is 'INFO'
```

```text
$ sudo fail2ban-client get logtarget
Current logging target is:
`- /var/log/fail2ban.log
```
- **systemd journal** (`logtarget = SYSTEMD-JOURNAL`, or `STDOUT`/`STDERR` under systemd): there's no file to read. Tell recidive to read the journal:

```ini
[recidive]
enabled = true
backend = systemd
```

## The dbpurgeage note in jail.conf

The stock `jail.conf` comment above `[recidive]` recommends raising `dbpurgeage` in `fail2ban.conf` (it suggests `648000`, 7.5 days) so ban records outlive recidive's long bans. In current versions an active ban stays in the database until it has expired, so recidive bans survive restarts even with the default `1d`. Raising `dbpurgeage` still helps if you combine recidive with `bantime.increment`, because it decides how long previous bans are remembered:

```ini
[Definition]
dbpurgeage = 7d
```

| Feature | recidive | bantime.increment |
|---|---|---|
| Ban scope | All ports | Ports of the jail that banned |
| Source of truth | fail2ban's log (file or journal) | fail2ban's database |
| Cross-service | Yes, counts bans from every jail | Only with `bantime.overalljails = true` |
| Escalation | One fixed long ban | Grows with every ban (1×, 2×, 4×…) |

They complement each other: increment makes each jail progressively harsher, recidive closes all ports on the worst offenders. The [incremental bans page](/config/bantime-increment/) has the formula and examples.

## recidive not working? Checklist

1. **Jail not enabled or not running.** `sudo fail2ban-client status` must list `recidive`.
2. **Wrong log source.** fail2ban logs to the journal but recidive reads a stale `/var/log/fail2ban.log` (or the other way round). Compare `fail2ban-client get logtarget` with `fail2ban-client get recidive logpath`; use `backend = systemd` for the journal.
3. **Log level too high.** `loglevel = WARNING` hides `Ban` lines.
4. **Thresholds never reached.** With `bantime = 1h` an IP can be banned at most about 24 times a day by one jail, but with `bantime = 1d` it can only be banned once a day, so `maxretry = 5` within `findtime = 1d` can never trigger. Keep `findtime` several times longer than your normal jails' `bantime`.
5. **Whitelisted IP.** `ignoreip` applies to recidive too.
6. **Banned but still connecting.** The all-ports action is in place but traffic bypasses it (Docker, a different firewall). See [banned but not blocked](/troubleshooting/not-banning/#part-2-the-ip-is-banned-but-still-connects).

Test the filter against your log; it should report matches for every IP that was banned:

```bash
```

## The recidive filter reference

{{component:filter-ref:recidive}}

## FAQ

### What is the recidive jail in fail2ban?

A stock jail that reads fail2ban's own log and bans IPs that other jails have banned repeatedly. By default an IP banned 5 times within a day (`findtime = 1d`) is banned on all ports for a week (`bantime = 1w`). It's disabled until you set `enabled = true` under `[recidive]` in `jail.local`.

### How do I enable recidive?

Add `[recidive]` with `enabled = true` to `/etc/fail2ban/jail.local` and run `sudo fail2ban-client reload`. If fail2ban logs to the systemd journal instead of `/var/log/fail2ban.log`, also set `backend = systemd`. Check with `sudo fail2ban-client status recidive`.

### Why is recidive not working?

Most often it reads the wrong place: fail2ban logs to the journal while recidive watches a file, or the reverse. Other causes are `loglevel` set above `NOTICE`, thresholds that can't be reached (findtime too short compared with your jails' bantime), and the jail not being enabled.

### How do I permanently ban repeat offenders with fail2ban?

Enable `[recidive]` with `bantime = -1`. IPs banned `maxretry` times within `findtime` by any jail are then blocked on all ports forever, and the ban survives restarts. Lift one with `sudo fail2ban-client set recidive unbanip <IP>`.

### Does recidive ban all ports?

Yes. It uses `banaction_allports` (for example `nftables[type=allports]` or `iptables-allports`), so the IP is blocked on every port, not only the service it attacked. If you change `banaction` in `[DEFAULT]`, change `banaction_allports` to the matching firewall too.
