# fail2ban-regex: test a filter before you trust it

> How to use fail2ban-regex to test a fail2ban filter or regex against a log file or the systemd journal, read the results and debug missed lines, with examples.
>
> Source: https://fail2ban.dev/fail2ban-regex/ · Updated 2026-10-25 · Checked against fail2ban 1.1.1
> fail2ban.dev is an independent guide, not the official fail2ban project.

`fail2ban-regex` tests a fail2ban filter against a log without banning anything. The syntax is `fail2ban-regex <LOG> <REGEX|FILTER> [IGNOREREGEX]` — for example `sudo fail2ban-regex /var/log/auth.log sshd` tests the sshd jail's filter against your auth log, and `sudo fail2ban-regex systemd-journal sshd` tests it against the journal. The report shows how many lines matched each failregex, how many were missed, and which date format was detected.

```bash
sudo fail2ban-regex /var/log/auth.log sshd

# filter file
sudo fail2ban-regex /var/log/nginx/error.log /etc/fail2ban/filter.d/nginx-http-auth.conf

# inline regex
sudo fail2ban-regex /var/log/auth.log 'Failed password for .* from <HOST>'

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

```text
```

- a **file path** such as `/var/log/auth.log`;
- a **single log line** in quotes — handy for a quick check;
- **`systemd-journal`** to read the journal (needs the python3-systemd module), optionally with backend options like `systemd-journal[journalflags=1]`.

**`<REGEX>`** can be:

- a **jail or filter name**, optionally with options: `sshd`, `sshd[mode=aggressive]`. Since 1.1.0 a bare name loads the *jail* settings (its filter, `mode`, `journalmatch`…) when a jail of that name exists;
- a **filter file**: `sshd.conf`, `filter.d/sshd`, or a full path like `/etc/fail2ban/filter.d/sshd.conf`;
- a **regex string** with a `<HOST>` (or `<ADDR>`, `<F-ID>`) tag.

**`[IGNOREREGEX]`** is an optional ignoreregex string, used with an inline failregex.

Run it with `sudo` because log files and the journal are usually readable only by root or the `adm` group.

## Test a filter against a log file

```text
$ sudo fail2ban-regex /var/log/auth.log sshd

Running tests
=============

Use             jail : sshd
Use         maxlines : 1
Use      datepattern : {^LN-BEG} : Default Detectors
Use         log file : /var/log/auth.log
Use         encoding : utf-8


Results
=======

Prefregex: 8 total
|  ^(?P<mlfid>(?:\[\])?\s*(?:<[^.]+\.[^.]+>\s+)?(?:\S+\s+)?(?:kernel:\s?\[ *\d+\.\d+\]:?\s+)?(?:@vserver_\S+\s+)?(?:(?:(?:\[\d+\])?:\s+[\[\(]?sshd(?:-session)?(?:\(\S+\))?[\]\)]?:?|[\[\(]?sshd(?:-session)?(?:\(\S+\))?[\]\)]?:?(?:\[\d+\])?:?)\s+)?(?:\[ID \d+ \S+\]\s+)?)(?:(?:error|fatal): (?:PAM: )?)?(?P<content>.+)$
`-

Failregex: 6 total
|-  #) [# of hits] regular expression
|   4) [2] ^Failed (?:<F-NOFAIL>publickey</F-NOFAIL>|\S+) for (?P<cond_inv>invalid user )?<F-USER>(?P<cond_user>\S+)|(?(cond_inv)(?:(?! from ).)*?|[^:]+)</F-USER> from <HOST>(?: port \d+)?(?: ssh\d*)?(?(cond_user): |(?:(?:(?! from ).)*)$)
|   6) [1] ^[iI](?:llegal|nvalid) user <F-USER>.*?</F-USER> (?:from )?<HOST>(?: port \d+)?(?: \[preauth\])?\s*$
|  20) [2] ^<F-NOFAIL><F-MLFFORGET>(?:Connection (?:closed|reset)|Disconnect(?:ed|ing))</F-MLFFORGET></F-NOFAIL>(?: (?:by|from))?(?: (?:invalid|authenticating) user <F-USER>\S+|.*?</F-USER>)?(?: from)? <ADDR>(?: port \d+)?(?:: (?!Too many authentication failures)[^\[]+)?(?: \[preauth\])?\s*$
|  21) [1] ^<F-MLFFORGET><F-MLFGAINED>Accepted \w+</F-MLFGAINED></F-MLFFORGET> for <F-USER>\S+</F-USER> from <HOST>(?:\s|$)
`-

Ignoreregex: 0 total

Date template hits:
|- [# of hits] date format
|  [8] {^LN-BEG}ExYear(?P<_sep>[-/.])Month(?P=_sep)Day(?:T|  ?)24hour:Minute:Second(?:[.,]Microseconds)?(?:\s*Zone offset)?
`-

Lines: 8 lines, 3 ignored, 3 matched, 2 missed
[processed in 0.00 sec]

|- Ignored line(s):
|  2026-10-25T18:30:03.223456+00:00 web1 sshd-session[2101]: Connection closed by invalid user admin 203.0.113.42 port 51234 [preauth]
|  2026-10-25T18:30:09.523456+00:00 web1 sshd-session[2109]: Accepted publickey for deploy from 192.168.1.20 port 50100 ssh2: ED25519 SHA256:abc
|  2026-10-25T18:30:15.823456+00:00 web1 sshd-session[2111]: Disconnected from authenticating user root 192.0.2.15 port 33012 [preauth]
`-
|- Missed line(s):
|  2026-10-25T18:30:11.623456+00:00 web1 sshd-session[2109]: pam_unix(sshd:session): session opened for user deploy(uid=1000) by deploy(uid=0)
|  2026-10-25T18:30:13.723456+00:00 web1 CRON[2200]: pam_unix(cron:session): session opened for user root(uid=0) by root(uid=0)
`-
```

## How to read the fail2ban-regex output

**Running tests** shows what was loaded: the jail or filter, `maxlines`, the date pattern and the log source. Check this first — a wrong file or an unexpected `datepattern` explains many "0 matched" results.

**Prefregex** (only for filters that define one) is applied to every line first. `8 total` means all eight lines passed the common prefix; the failregexes then run on the `<F-CONTENT>` part.

**Failregex: N total** is the number of matches, followed by one row per regex that hit something: `4) [2]` means the filter's 4th failregex matched 2 lines. Add `-v` to list every regex, including those with no hits, with the address and time of each match under it.

**Ignoreregex** shows how many lines an `ignoreregex` excluded.

**Date template hits** shows which timestamp format was found and how often. If this section is empty or the count is lower than the line count, fail2ban can't read the time on those lines. fail2ban-regex may still report them as matched, but the running server skips such matches and logs `Found a match but no valid date/time found`. Fix it with `datepattern` in the filter or jail (`-d` here).

**Lines: 8 lines, 3 ignored, 3 matched, 2 missed** is the summary:

| Count | Meaning |
|---|---|
| matched | Lines counted as failures — these lead to bans |
| ignored | Lines matched by `ignoreregex`, or by a failregex marked `<F-NOFAIL>` (helper lines such as "Connection closed" or "Accepted publickey" that track a session but aren't failures) |
| missed | Lines no failregex matched |

In the example, the three real failures (two `Failed password`, one `Invalid user`) matched, two session/cron lines were correctly missed, and three helper lines were "ignored". A high missed count is normal for a shared log like auth.log; what matters is that every attack line you care about is in **matched**.

## Print matched and missed lines

By default fail2ban-regex prints only a handful of missed and ignored lines. To see everything:

```bash
sudo fail2ban-regex --print-all-missed  /var/log/auth.log sshd
sudo fail2ban-regex --print-no-missed   /var/log/auth.log sshd   # quieter
```

```text
$ sudo fail2ban-regex -o ip /var/log/auth.log sshd
203.0.113.42
198.51.100.7
198.51.100.7
```

## Test with the systemd journal

When a service logs only to the journal (Debian 12 sshd, many containers), use `systemd-journal` as the log source:

```bash
```

```bash
  systemd-journal 'Failed password for .* from <HOST>'
```

## Test an inline regex or a single line

An inline regex is the fastest way to develop a new failregex:

```text
$ fail2ban-regex '2026-10-25T18:30:05 web1 sshd[2105]: Failed password for root from 198.51.100.7 port 40022 ssh2' 'Failed password for \S+ from <HOST>'

Running tests
=============

Use   failregex line : Failed password for \S+ from <HOST>
Use      single line : 2026-10-25T18:30:05 web1 sshd[2105]: Failed passwo...


Results
=======

Failregex: 1 total
|-  #) [# of hits] regular expression
|   1) [1] Failed password for \S+ from <HOST>
`-

Ignoreregex: 0 total

Date template hits:
|- [# of hits] date format
|  [1] {^LN-BEG}ExYear(?P<_sep>[-/.])Month(?P=_sep)Day(?:T|  ?)24hour:Minute:Second(?:[.,]Microseconds)?(?:\s*Zone offset)?
`-

Lines: 1 lines, 0 ignored, 1 matched, 0 missed
[processed in 0.00 sec]
```

```text
```

## Test filter modes and options

Many stock filters have modes (`normal`, `ddos`, `extra`, `aggressive`) and other parameters. Pass them in brackets, quoted so the shell leaves them alone:

```bash
sudo fail2ban-regex /var/log/mail.log 'postfix[mode=auth]'
```

## fail2ban-regex options

From `man fail2ban-regex` (1.1.1):

| Option | Purpose |
|---|---|
| `-c, --config DIR` | Configuration directory (default `/etc/fail2ban`) |
| `-d, --datepattern PATTERN` | Custom date pattern |
| `--timezone, --TZ TZ` | Time zone for timestamps without one |
| `-e, --encoding ENC` | Log file encoding |
| `-r, --raw` | Don't resolve DNS hostnames |
| `--usedns yes|no` | How `<HOST>` handles hostnames |
| `-L, --maxlines N` | Lines buffered for multi-line regexes |
| `-m, --journalmatch MATCH` | Journal match (with `systemd-journal`) |
| `-l, --log-level LEVEL` | Log level of fail2ban's own logger |
| `-v, --verbose` / `--verbosity N` | More detail, including regexes with no hits |
| `--VD, --verbose-date` | Show date pattern details |
| `-D, --debuggex` | Print debuggex.com URLs for each regex |
| `--no-check-all` | Don't check every regex for every line |
| `-o, --out TOKEN` | Print only a field: `ip`, `id`, `msg`, `row`, `matches`… |
| `-i, --invert` | With `-o`, output the non-matching lines |
| `--print-all-matched` / `--print-all-missed` / `--print-all-ignored` | Print every line of that kind |
| `--print-no-missed` / `--print-no-ignored` | Don't print those lines |
| `-t, --log-traceback`, `--full-traceback` | Tracebacks on errors |
| `-V`, `--version` | Version |

## When fail2ban-regex matches but fail2ban doesn't ban

fail2ban-regex only tests the filter. If it reports matches but the jail never bans, check the rest of the chain:

- the jail reads the same file or journal (`fail2ban-client get <jail> logpath` or `journalmatch`);
- the log lines are recent — `findtime` ignores old timestamps, and a time zone mismatch can make new lines look old;
- the IP isn't covered by `ignoreip`;
- enough matches arrive within `findtime` to reach `maxretry`.

The full checklist is on [fail2ban not banning](/troubleshooting/not-banning/).

## FAQ

### How do I test a fail2ban filter?

Run `sudo fail2ban-regex /var/log/auth.log sshd`, replacing the log path and jail or filter name with yours. The output shows how many lines each failregex matched and how many were missed. Add `--print-all-matched` or `--print-all-missed` to list the lines, and use `systemd-journal` instead of a path for journal-based services.

### How do I use fail2ban-regex with the systemd journal?

Use `systemd-journal` as the log argument: `sudo fail2ban-regex systemd-journal sshd`. With a jail name it applies that jail's `journalmatch`; with a filter file or inline regex, add `-m '_SYSTEMD_UNIT=ssh.service'`. The python3-systemd module must be installed. Add `[rotated=on]` to include rotated journal files.

### What do missed lines mean in fail2ban-regex?

Missed lines are log lines that no failregex matched. In a shared log like auth.log most lines are unrelated and should be missed. Look at the missed list with `--print-all-missed`: if real failed logins appear there, your filter needs another failregex or the right `mode`.

### Can fail2ban-client test a filter?

No. `fail2ban-client -t` checks that the configuration files parse and that filters, actions and logs exist, but it doesn't run regexes against logs. Use `fail2ban-regex` to test what a filter matches, then `fail2ban-client -t` and `reload` to deploy it.
