Fail2ban ships filters for SSH, Nginx, Postfix and many other services, but not for your own app, an admin panel or a game server. If that software writes a log line for each failed login, you can write a filter for it. This guide builds one from a sample log line, tests it with fail2ban-regex until it matches exactly what it should, and wires it into a jail.
Before you start
- A Linux VPS with Fail2ban 0.10 or newer. Check with
fail2ban-client version. See Install Fail2ban if needed. - Your own IP in
ignoreipin/etc/fail2ban/jail.local, so testing never bans you. - An application that logs failed logins with the client's IP address on the line. If the log has no IP, Fail2ban cannot ban anyone.
- Basic regular expression knowledge helps:
\S+(non-space characters),\d+(digits),.*(anything).
Warning: A filter that matches too much bans real users, and a wide ban action can also block SSH. If you lock yourself out, use the browser console in your VPS provider's control panel to log in and run fail2ban-client unban YOUR_IP.
Look at the log first
Collect a few real failure lines and a few success lines. This guide uses an imaginary app that logs to /var/log/myapp/auth.log:
2026-11-09 10:15:02 WARN login failed user=bob ip=203.0.113.10 reason=bad_password
2026-11-09 10:15:09 WARN login failed user=admin ip=198.51.100.20 reason=no_such_user
2026-11-09 10:16:44 INFO login ok user=alice ip=203.0.113.55Note three things:
- The timestamp format. Here it is
YYYY-MM-DD HH:MM:SSat the start of the line. - A fixed phrase that only appears on failures:
login failed. - Where the IP sits: after
ip=.
How a filter is built
A filter is a small .conf file in /etc/fail2ban/filter.d/. The important keys:
- failregex: one or more regular expressions. A line that matches counts as one failure. Each needs a tag that captures the address.
<HOST>: the tag that captures an IP address or hostname. Fail2ban 0.10+ also has<ADDR>, which matches only IP addresses and is slightly safer. Either works.- ignoreregex: lines that should never count, even if
failregexmatches. - datepattern: tells Fail2ban how to read the timestamp. Fail2ban detects many formats on its own, but setting it is faster and avoids surprises.
Fail2ban finds and removes the timestamp from a line before it applies failregex, so your regex describes the rest of the line.
Write the filter
Create the file:
sudo nano /etc/fail2ban/filter.d/myapp-auth.conf[Definition]
failregex = WARN login failed user=\S* ip=<HOST>(?: |$)
ignoreregex =
datepattern = ^%%Y-%%m-%%d %%H:%%M:%%SPoints that trip people up:
- In Fail2ban config files, a literal
%must be written%%. That is why the date pattern uses%%Y. - Make the regex specific.
ip=<HOST>on its own would also match a log line where an attacker putsip=1.2.3.4inside a username field. Anchoring onWARN login failed user=\S*keeps the match tight, because\S*cannot contain spaces. (?: |$)makes sure the address ends where it should, followed by a space or the end of the line.
Test with fail2ban-regex
fail2ban-regex runs a filter against a file and reports matches. It never bans anything, so you can run it freely:
sudo fail2ban-regex /var/log/myapp/auth.log /etc/fail2ban/filter.d/myapp-auth.confRead the summary at the bottom:
Failregex: 2 total
|- #) [# of hits] regular expression
| 1) [2] WARN login failed user=\S* ip=<HOST>(?: |$)
Date template hits:
|- [# of hits] date format
| [3] ^Year(?P<_sep>[-/.])Month(?P=_sep)Day 24hour:Minute:Second
Lines: 3 lines, 0 ignored, 2 matched, 1 missedThat is the result you want: both failures matched, the success line missed, and the date template hit every line.
Useful options while you iterate:
--print-all-matchedshows each matched line.--print-all-missedshows lines that did not match, to spot failures you forgot.- You can test a single line by passing it as a string instead of a file:
fail2ban-regex '2026-11-09 10:15:02 WARN login failed user=bob ip=203.0.113.10 reason=x' \
'WARN login failed user=\S* ip=<HOST>(?: |$)'If Date template hits is zero, Fail2ban could not read the timestamps. Fix datepattern first, because lines without a valid time are ignored even when the regex matches.
Tip: Craft a hostile test line, such as a username containing ip=8.8.8.8, and confirm your filter does not extract that address. This catches log injection before an attacker uses it to get innocent addresses banned.
Attach it to a jail
Add a jail to /etc/fail2ban/jail.local:
[myapp-auth]
enabled = true
filter = myapp-auth
logpath = /var/log/myapp/auth.log
port = http,https
maxretry = 5
findtime = 10m
bantime = 1hfilteris the file name without.conf.portshould list the ports your app uses, so the ban blocks the right traffic.- If the app logs to the systemd journal instead of a file, use
backend = systemdand addjournalmatch = _SYSTEMD_UNIT=myapp.serviceto the filter's[Definition]instead oflogpath.
Test the full configuration, then reload:
sudo fail2ban-client -t && sudo fail2ban-client reloadCheck it works
sudo fail2ban-client status myapp-authThe jail should list the log file it watches. Trigger a few failed logins from a second network, such as a phone on mobile data, and watch:
sudo tail -f /var/log/fail2ban.log | grep myapp-authYou should see Found lines for each failure and a Ban line once maxretry is reached.
Troubleshooting
fail2ban-regexmatches, but the jail never bans: the jail reads a different file, or the log timestamps are in a different time zone and look too old. Checklogpath, and confirm the date template hits every line.- "No failure-id group" or "No 'host' group" error: the regex has no
<HOST>or<ADDR>tag. Add one. - Jail fails to start after adding the filter: a
%was not doubled, or the file has a syntax error. Runsudo fail2ban-client -tto see the exact problem. - Successful logins are counted as failures: your regex is too broad. Use
--print-all-matched, then tighten it or add anignoreregex. - Nothing matches at all: check for extra spaces, tabs or a different log level word in the real lines. Copy a line straight from the file into a single-line test.
Next steps
- Get told when your new jail fires: Get email or Telegram alerts when Fail2ban bans an IP.
- Debug a jail that stays silent: Fail2ban not banning? Common causes and fixes.
- Tune timings across all jails: Configure Fail2ban jails.
