# fail2ban backends and the systemd journal

> How fail2ban backends work: auto, pyinotify, polling and systemd with journalmatch. When to read the journal, python3-systemd, Debian 12 and Docker logs.
>
> Source: https://fail2ban.dev/config/backend/ · Updated 2026-10-25 · Checked against fail2ban 1.1.1
> fail2ban.dev is an independent guide, not the official fail2ban project.

The `backend` option tells a fail2ban jail how to read logs. **`auto`** (the default) watches the files in `logpath` with pyinotify, or polling if pyinotify isn't installed. **`systemd`** reads the systemd journal instead, using the filter's `journalmatch` and ignoring `logpath`. Use `backend = systemd` for services that log only to the journal, such as sshd on Debian 12 and later, which has no `/var/log/auth.log`. It needs the `python3-systemd` package.

```ini
[sshd]
enabled = true
backend = systemd       ; read sshd messages from the journal

[nginx-http-auth]
enabled = true
backend = auto          ; read /var/log/nginx/*error.log
```

| Backend | Reads | Needs | Use it for |
|---|---|---|---|
| `auto` | Log files (`logpath`) | nothing | The default. Tries pyinotify, then polling. Since 1.1.1 it can switch to `systemd` when no log file exists (see below). |
| `pyinotify` | Log files | `python3-pyinotify` | Event-driven file watching on Linux; lowest latency and CPU for busy logs. |
| `polling` | Log files | nothing | Checks files for changes every couple of seconds. Works everywhere, including network filesystems and containers where inotify misbehaves. |
| `systemd` | The systemd journal | `python3-systemd` | Services that log to journald only. `logpath` is ignored. |

If the backend you ask for can't load, fail2ban falls back down the list: `pyinotify` falls back to `polling` with a warning. `systemd` has nothing to fall back to, so a missing `python3-systemd` stops the jail:

```text
ERROR   NOK: ("Failed to initialize any backend for Jail 'sshd'",)
```

Use `backend = systemd` when the service's messages exist **only** in the journal:

- **Debian 12 and later**: no rsyslog by default, so no `/var/log/auth.log`. The sshd jail fails with "Have not found any log file for sshd jail" until you switch it to `systemd`.
- **[Fedora, RHEL 9+](/install/rhel/), [Arch](/install/arch/), openSUSE**: `paths-*.conf` already sets `sshd_backend = systemd` (and for postfix, dovecot and FTP servers), so those jails read the journal out of the box.
- **Services you run as systemd units** that log to stdout: many Go, Node and Python apps.

Keep file backends for services that write their own log files: nginx, Apache, Nextcloud, Vaultwarden, mail servers configured to log to files. They don't show those lines in the journal.

You can mix both freely; `backend` is per jail. The trap is setting `backend = systemd` in `[DEFAULT]`: every file-based jail then ignores its `logpath` and silently sees nothing. Ubuntu 24.04's `jail.d/defaults-debian.conf` does exactly that, so on Ubuntu 24.04 set `backend = auto` on (or above) your file-based jails.

## Install python3-systemd


#### Ubuntu / Debian
```bash
```
```bash
```
```bash
```

```text
$ sudo fail2ban-client status sshd
Status for the jail: sshd
|- Filter
|  |- Currently failed:	2
|  |- Total failed:	6
|  `- File list:	/var/log/auth.log
`- Actions
   |- Currently banned:	1
   |- Total banned:	1
   `- Banned IP list:	203.0.113.42
```

## journalmatch: which journal entries a jail reads

A systemd jail doesn't read the whole journal. It reads only entries matching `journalmatch`, which comes from the filter and can be overridden in the jail:

```ini
journalmatch = _SYSTEMD_UNIT=sshd.service + _COMM=sshd + _COMM=sshd-session
```

- `FIELD=value` matches entries with that field value.
- Several terms separated by spaces must **all** match (AND), except that repeating the same field means either value.
- `+` separates alternatives (OR).

So the line above means "unit `sshd.service`, OR process name `sshd`, OR process name `sshd-session`". On Debian and Ubuntu the unit is called `ssh.service`, but the `_COMM=sshd` alternative still catches its messages. Debian 13 and Ubuntu 26.04 packages set `journalmatch = _SYSTEMD_UNIT=ssh.service + _COMM=sshd` for the sshd jail.

Check what a match returns before you use it, with the same expression in `journalctl`:

```bash
```

```ini
[myapp]
enabled      = true
filter       = myapp
backend      = systemd
journalmatch = _SYSTEMD_UNIT=myapp.service
port         = http,https
```

## systemd backend parameters

Pass options in brackets after `systemd`:

| Parameter | Meaning |
|---|---|
| `journalflags` | Which journals to open. `1` = local machine only (`LOCAL_ONLY`), `4` = system journal only (`SYSTEM_ONLY`, skips user-session journals) |
| `journalpath` | Read a journal directory instead of the default, e.g. a container's or remote machine's journal |
| `journalfiles` | Read specific journal files (comma-separated, globs allowed) |
| `namespace` | Read a journald namespace |
| `rotated` | 1.1.1+: also open rotated journal files. Default off |

`journalflags`, `journalpath`, `journalfiles` and `namespace` are mutually exclusive.

```ini
backend = systemd[journalpath=/var/log/journal/remote]
backend = systemd[rotated=on]
```

Since 1.1.1, fail2ban ignores rotated journal files (`*@*.journal`) by default and opens only the active ones. Older versions opened every journal file per jail, which on servers with many jails and a large journal caused "Too many open files" errors and high memory use. The trade-off: after a journal rotation, fail2ban can't look back further than the rotation, which only matters if your `findtime` is longer than the time between rotations. Set `rotated=on` if you need that.

## Automatic switch to systemd (1.1.1+)

Since 1.1.1 a jail with `backend = auto` switches to the journal on its own when:

1. no file matches its `logpath`,
2. the jail or its filter has a `journalmatch`, and
3. `systemd_if_nologs` isn't set to `false` (it defaults to `true`).

That's why a fresh Debian 13 sshd jail works without `/var/log/auth.log`. Jails whose filter has no `journalmatch` (most web application filters) still fail with "Have not found any log file". For those, either fix the `logpath` or add `skip_if_nologs = true` so fail2ban starts the other jails and skips this one with a warning. More on [the no log file error](/troubleshooting/no-log-file/).

## Reading logs from Docker containers

Two ways to feed container logs to a jail on the host:

- **journald log driver**: run the container with `--log-driver=journald` (or `"log-driver": "journald"` in `/etc/docker/daemon.json`). Each entry gets a `CONTAINER_NAME` field:

  ```ini
  [vaultwarden]
  enabled      = true
  backend      = systemd
  journalmatch = CONTAINER_NAME=vaultwarden
  ```

- **Log file on a volume**: let the app write its log to a mounted directory (for example `./data/vaultwarden.log`) and point `logpath` at the host path with `backend = auto` or `polling`. Polling is the safest choice for files on bind mounts.

Remember that bans on the host's INPUT chain don't stop traffic to published container ports; see the [Docker guide](/guides/docker/).

## Test a filter against the journal

`fail2ban-regex` reads the journal too. Use the filter name or file and `systemd-journal` as the log source:

```bash
sudo fail2ban-regex systemd-journal sshd

# your own match, including rotated journal files
sudo fail2ban-regex -m "_SYSTEMD_UNIT=myapp.service" "systemd-journal[rotated=on]" /etc/fail2ban/filter.d/myapp.conf
```

## fail2ban's own log in the journal

`journalctl -u fail2ban` shows fail2ban's own messages (start-up errors, bans) whenever it runs as a systemd service. Where fail2ban writes its log is a separate setting, `logtarget`, in [fail2ban.conf](/config/fail2ban-conf/). If you set it to the journal, the [recidive jail](/config/recidive/) must use `backend = systemd` too.

## FAQ

### What backend should fail2ban use?

Leave `backend = auto` for services that write log files (nginx, Apache, most web apps) and use `backend = systemd` for services that log only to the journal, such as sshd on Debian 12+, Fedora and RHEL. The backend is per jail, so mix them as needed; avoid setting `systemd` in `[DEFAULT]`.

### How do I make fail2ban read the systemd journal?

Install `python3-systemd`, set `backend = systemd` in the jail and remove any reliance on `logpath`. The jail reads journal entries matching the filter's `journalmatch`, for example `_SYSTEMD_UNIT=sshd.service + _COMM=sshd`. Check with `sudo fail2ban-client status <jail>`, which then shows `Journal matches`.

### What is journalmatch in fail2ban?

A systemd journal filter, written like `journalctl` matches, that selects which journal entries a jail reads. Stock filters define it (sshd uses `_SYSTEMD_UNIT=sshd.service + _COMM=sshd + _COMM=sshd-session`); you can override it per jail. It's only used with the `systemd` backend.

### Why does the systemd backend find no failures?

Usually the `journalmatch` doesn't match the unit name (for example `ssh.service` vs `sshd.service`), the service writes to a file instead of the journal, or fail2ban can't read the journal files. Test with `sudo fail2ban-regex systemd-journal <filter>` and compare with `journalctl` using the same match.

### What is the difference between pyinotify and polling?

pyinotify uses Linux inotify events and reacts as soon as a log file changes; polling checks the files for changes every couple of seconds. Both read the same files. `auto` uses pyinotify when the Python module is installed and polling otherwise. Prefer polling for files on network or container bind mounts.
