Security

Write a custom Fail2ban filter and test it with fail2ban-regex

Build your own Fail2ban filter for an app that has no built-in one. Write a failregex with the HOST tag, set a datepattern, test it with fail2ban-regex and attach it to a new jail.

AdvancedLinux server6 min read

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 ignoreip in /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.55

Note three things:

  1. The timestamp format. Here it is YYYY-MM-DD HH:MM:SS at the start of the line.
  2. A fixed phrase that only appears on failures: login failed.
  3. 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 failregex matches.
  • 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:

Bash
sudo nano /etc/fail2ban/filter.d/myapp-auth.conf
INI
[Definition]
failregex = WARN login failed user=\S* ip=<HOST>(?: |$)
ignoreregex =
datepattern = ^%%Y-%%m-%%d %%H:%%M:%%S

Points 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 puts ip=1.2.3.4 inside a username field. Anchoring on WARN 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:

Bash
sudo fail2ban-regex /var/log/myapp/auth.log /etc/fail2ban/filter.d/myapp-auth.conf

Read 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 missed

That 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-matched shows each matched line.
  • --print-all-missed shows 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:
Bash
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:

INI
[myapp-auth]
enabled  = true
filter   = myapp-auth
logpath  = /var/log/myapp/auth.log
port     = http,https
maxretry = 5
findtime = 10m
bantime  = 1h
  • filter is the file name without .conf.
  • port should 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 = systemd and add journalmatch = _SYSTEMD_UNIT=myapp.service to the filter's [Definition] instead of logpath.

Test the full configuration, then reload:

Bash
sudo fail2ban-client -t && sudo fail2ban-client reload

Check it works

Bash
sudo fail2ban-client status myapp-auth

The 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:

Bash
sudo tail -f /var/log/fail2ban.log | grep myapp-auth

You should see Found lines for each failure and a Ban line once maxretry is reached.

Troubleshooting

  • fail2ban-regex matches, 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. Check logpath, 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. Run sudo fail2ban-client -t to see the exact problem.
  • Successful logins are counted as failures: your regex is too broad. Use --print-all-matched, then tighten it or add an ignoreregex.
  • 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

Was this guide helpful?

Still stuck?

Ask MdmHost AI for a quick answer, or open a ticket and our team will take a look. We are here 24/7.