# fail2ban logs: location, levels and how to read them

> Where fail2ban logs (/var/log/fail2ban.log or the journal), how to read Found, Ban and Unban lines, change the log level, rotate logs and grep for top banned IPs.
>
> Source: https://fail2ban.dev/logs/ · Updated 2026-10-25 · Checked against fail2ban 1.1.1
> fail2ban.dev is an independent guide, not the official fail2ban project.

fail2ban logs to `/var/log/fail2ban.log` by default on Debian, Ubuntu and most other distributions. Read it with `sudo tail -f /var/log/fail2ban.log`. Service start-up errors go to the systemd journal instead: `sudo journalctl -u fail2ban`. To confirm where your install writes, run `sudo fail2ban-client get logtarget`; the log level (default `INFO`) is shown by `sudo fail2ban-client get loglevel`.

```text
$ sudo fail2ban-client get logtarget
Current logging target is:
`- /var/log/fail2ban.log
$ sudo fail2ban-client get loglevel
Current logging level is 'INFO'
```
fail2ban **writes** its own log (this page). It **reads** the logs of other services — `/var/log/auth.log`, nginx's `error.log`, the systemd journal — to find failures. Those are set per jail with `logpath` or `backend = systemd`; see [reading service logs](#how-fail2ban-reads-service-logs) below.

## How to check fail2ban logs

```bash
sudo tail -f /var/log/fail2ban.log

# last 100 lines
sudo tail -n 100 /var/log/fail2ban.log

# service messages and start-up errors
sudo journalctl -u fail2ban -e

# both together if logtarget is the journal
sudo journalctl -u fail2ban -f
```

## Reading fail2ban log lines

```log
2026-10-25 18:36:10,239 fail2ban.filter         [1997]: INFO    [sshd] Found 198.51.100.7 - 2026-10-25 18:36:10
2026-10-25 18:36:11,027 fail2ban.actions        [1997]: NOTICE  [sshd] Ban 203.0.113.42
2026-10-25 18:36:11,033 fail2ban.filter         [1997]: INFO    [recidive] Found 203.0.113.42 - 2026-10-25 18:36:11
2026-10-25 19:36:11,031 fail2ban.actions        [1997]: NOTICE  [sshd] Unban 203.0.113.42
```

| Message | Meaning |
|---|---|
| `Found <IP> - <time>` | The filter matched a failure line. The time is the timestamp from the service's log. |
| `Ban <IP>` | The IP reached `maxretry` within `findtime` (or was banned by hand) and the action ran. |
| `Unban <IP>` | The ban expired or was lifted with `unbanip`. |
| `Restore Ban <IP>` | A ban re-applied from the database after a start or jail restart. |
| `Repeal Ban <IP>` | Firewall rule removed because the jail stopped; the ban itself is kept. |
| `<IP> already banned` | A failure from an address that's already blocked — often a sign the firewall isn't actually dropping its traffic. |

Note the `[recidive] Found` line: the [recidive jail](/config/recidive/) reads this very log and treats each `Ban` of another jail as a failure, so repeat offenders get a longer ban.

`Found` lines with no `Ban` after them are normal: the IP hasn't reached `maxretry` yet. Lots of `Found` and never a `Ban` points at `findtime`/`maxretry` or `ignoreip` — see [fail2ban not banning](/troubleshooting/not-banning/).

## Useful grep recipes

```bash
sudo grep '\] Ban ' /var/log/fail2ban.log

# bans today
sudo grep "^$(date +%F).*\] Ban " /var/log/fail2ban.log

# number of bans per jail
sudo grep -oP '\[\K[^]]+(?=\] Ban )' /var/log/fail2ban.log | sort | uniq -c | sort -rn

# top 20 banned IPs, including rotated and compressed logs
sudo zgrep -h '\] Ban ' /var/log/fail2ban.log* | awk '{print $NF}' | sort | uniq -c | sort -rn | head -20

# everything about one IP
sudo zgrep -h '203.0.113.42' /var/log/fail2ban.log*

# errors and warnings only
sudo grep -E 'ERROR|WARNING' /var/log/fail2ban.log
```

## fail2ban log level

The log level is set in `fail2ban.local` and can be changed live:

| Level | What you see |
|---|---|
| `CRITICAL`, `ERROR` | Failures only |
| `WARNING` | Plus warnings (missing logs, DNS lookups with `usedns = warn`) |
| `NOTICE` | Plus `Ban`, `Unban`, `Restore Ban` |
| `INFO` (default) | Plus `Found`, jail start/stop, monitored files |
| `DEBUG` | Plus each processed line and action command — very verbose |
| `TRACEDEBUG`, `HEAVYDEBUG` | Internal tracing for developers |

Change it until the next reload:

```bash
# …reproduce the problem…
sudo fail2ban-client set loglevel INFO
```

```ini
[DEFAULT]
loglevel = NOTICE
```

## Log targets: file, journal, syslog

`logtarget` in `fail2ban.local` decides where the log goes:

| Value | Destination |
|---|---|
| `/var/log/fail2ban.log` | A file (the default) |
| `SYSTEMD-JOURNAL` | The journal, via the python-systemd module (`journalctl -t fail2ban`) |
| `SYSOUT` | Standard output without timestamps — the journal adds its own when run under systemd |
| `STDOUT`, `STDERR` | Standard output / error with timestamps |
| `SYSLOG` | The local syslog daemon (facility `daemon`; `SYSLOG[facility=local0]` to change it) |

```ini
[DEFAULT]
logtarget = SYSTEMD-JOURNAL
```

## fail2ban journalctl commands

```bash
sudo journalctl -u fail2ban -b         # since last boot
sudo journalctl -u fail2ban --since "1 hour ago"
sudo journalctl -t fail2ban -f         # fail2ban's own log when logtarget = SYSTEMD-JOURNAL
```

## Log rotation

Debian and Ubuntu packages install `/etc/logrotate.d/fail2ban`, which rotates `/var/log/fail2ban.log` weekly, keeps four compressed copies and runs `fail2ban-client flushlogs` afterwards so fail2ban reopens the new file. Check yours:

```bash
```

```text
/var/log/fail2ban.log {
    weekly
    rotate 4
    compress
    delaycompress
    missingok
    notifempty
    postrotate
        fail2ban-client flushlogs >/dev/null || true
    endscript
}
```

## fail2ban logs in Docker

Containerised fail2ban usually logs to stdout or to a file in its mounted config volume. Try:

```bash
docker exec fail2ban fail2ban-client get logtarget
```

## How fail2ban reads service logs

Each jail watches either log files or the systemd journal:

```ini
[nginx-http-auth]
enabled = true
logpath = /var/log/nginx/error.log

[sshd]
enabled = true
backend = systemd
```
- **systemd backend** reads the journal using the filter's `journalmatch`; `logpath` is ignored. Check with `sudo fail2ban-client get <jail> journalmatch`.

On 1.1.1, `backend = auto` switches to the journal automatically when no file matches `logpath` and the filter has a `journalmatch`. Debian 12 needs `backend = systemd` for sshd because it has no `/var/log/auth.log`. Details on [backends and the systemd journal](/config/backend/) and [no log file found for jail](/troubleshooting/no-log-file/).

## FAQ

### Where is the fail2ban log file?

`/var/log/fail2ban.log` on Debian, Ubuntu and most distributions, as set by `logtarget` in `/etc/fail2ban/fail2ban.conf`. Run `sudo fail2ban-client get logtarget` to see the active target. If it says `SYSTEMD-JOURNAL` or `SYSLOG`, read the log with `journalctl -t fail2ban` or in your syslog files.

### How do I check fail2ban logs?

Run `sudo tail -f /var/log/fail2ban.log` to watch live, or `sudo grep '\] Ban ' /var/log/fail2ban.log` for bans only. Start-up errors and crashes are in the journal: `sudo journalctl -u fail2ban -e`. Each `Found` line is a detected failure and each `Ban` line a block.

### How do I change the fail2ban log level?

Run `sudo fail2ban-client set loglevel DEBUG` for an immediate, temporary change. To keep it, add `loglevel = DEBUG` (or `NOTICE`, `WARNING`…) under `[DEFAULT]` in `/etc/fail2ban/fail2ban.local` and reload. The default is `INFO`.

### Why is fail2ban not logging?

Check `sudo fail2ban-client get logtarget` — the log may be going to the journal or syslog. If it's a file, make sure the service runs and the file is writable by root. After manual rotation without `fail2ban-client flushlogs`, fail2ban keeps writing to the old file; run that command or reload.

### Can fail2ban log to the systemd journal?

Yes. Set `logtarget = SYSTEMD-JOURNAL` in `fail2ban.local` (requires the python3-systemd package) and read it with `journalctl -t fail2ban`. Alternatively `logtarget = SYSOUT` sends plain output that systemd stores under `journalctl -u fail2ban`.
