Commands
fail2ban-regex: test a filter before you trust it
fail2ban-regex runs a filter against real log lines and tells you exactly what matched, what was ignored and what slipped through. Use it every time you write or change a filter.
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.
# jail or filter name
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 sshdfail2ban-regex syntax#
fail2ban-regex [OPTIONS] <LOG> <REGEX> [IGNOREREGEX]<LOG> can be:
- a file path such as
/var/log/auth.log; - a single log line in quotes — handy for a quick check;
systemd-journalto read the journal (needs the python3-systemd module), optionally with backend options likesystemd-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#
$ 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)
`-This run used an eight-line test log with OpenSSH 9.8+ sshd-session lines.
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:
sudo fail2ban-regex --print-all-matched /var/log/auth.log sshd
sudo fail2ban-regex --print-all-missed /var/log/auth.log sshd
sudo fail2ban-regex --print-no-missed /var/log/auth.log sshd # quieterTo print only what fail2ban would extract, use -o / --out with a token such as ip, id, msg, row, host or matches:
$ sudo fail2ban-regex -o ip /var/log/auth.log sshd
203.0.113.42
198.51.100.7
198.51.100.7That's a quick way to see which addresses a filter would count. Add -i / --invert (1.1.1+) to print the lines that did not match instead.
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:
sudo fail2ban-regex systemd-journal sshdWith a jail name, fail2ban-regex uses that jail's journalmatch. With a filter file or inline regex, set the match yourself with -m / --journalmatch:
sudo fail2ban-regex -m '_SYSTEMD_UNIT=ssh.service + _COMM=sshd' \
systemd-journal 'Failed password for .* from <HOST>'By default only current journal files are read. Add rotated=on to search the whole journal: systemd-journal[rotated=on]. Use journalctl -u ssh -o short-iso to look at the raw lines you're matching. More on journal jails: backends and the systemd journal.
Test an inline regex or a single line#
An inline regex is the fastest way to develop a new failregex:
$ 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]Every failregex needs a tag that captures the address — <HOST>, <ADDR>, <IP4>, <IP6> or <F-ID>. Without one you get:
ERROR: No failure-id group in 'Failed password for .*'The date is stripped before the failregex runs, so don't include the timestamp in your regex. Prefer the online regex tester for quick iterations, then confirm with fail2ban-regex on the server. Writing a complete filter file is covered in write a custom filter.
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:
sudo fail2ban-regex /var/log/auth.log 'sshd[mode=aggressive]'
sudo fail2ban-regex /var/log/mail.log 'postfix[mode=auth]'Compare matched counts between modes before changing mode in your jail. The modes each filter offers are listed in its reference entry, for example the sshd filter.
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> logpathorjournalmatch); - the log lines are recent —
findtimeignores old timestamps, and a time zone mismatch can make new lines look old; - the IP isn't covered by
ignoreip; - enough matches arrive within
findtimeto reachmaxretry.
The full checklist is on fail2ban 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.
Spotted something wrong or out of date? Tell us — every page is checked against the current fail2ban release.