Security

Fail2ban Custom Jails: Creating Filters for Specific Application Log Errors

Write regex filters for your own application logs, test them with fail2ban-regex, and deploy custom jails that ban repeat offenders without touching your SSH rules.

AdvancedLinux server7 min read

Fail2ban custom jails let you ban an IP based on lines in your own application log, not just SSH or Nginx. You write a filter containing a regex that captures the attacker's address as <HOST>, test it with fail2ban-regex, then enable a jail in jail.local that watches that file. This guide builds one complete example end to end.

The examples work on Ubuntu 22.04/24.04, Debian 12, AlmaLinux 8/9 and Rocky 8/9. Adjust paths and service names for your stack.

Before you start

You need:

  • Root or sudo access to the server.
  • Fail2ban already installed and running. If not, follow protect SSH from brute-force attacks with Fail2ban first.
  • One application log file that records the client IP on every failure. A line must contain the IP in plain text; if your app logs the reverse proxy address instead, fix that first (see the troubleshooting section).
  • Basic comfort with regular expressions and the fail2ban-regex tool.

Warning: Editing Fail2ban configuration can stop the service from starting and leave you with no brute-force protection at all. Always validate with fail2ban-client -t before reloading, and keep an existing SSH session open while you work.

The example application writes to /var/log/myapp/auth.log with lines like this:

2026-01-01 10:12:33 WARN  login failed for user deploy from 203.0.113.10 (bad password)

Understand how a custom jail reads a log

Two files are involved and they are not the same thing:

  • A filter in /etc/fail2ban/filter.d/ holds the [Definition] block with failregex. It describes what a bad line looks like. No paths, no ban times.
  • A jail in /etc/fail2ban/jail.local references that filter by name and adds the log path, retry limits, ban duration and action.

Never edit jail.conf or the shipped filters in filter.d/; package updates overwrite them. Use jail.local and filter.d/<name>.conf (or a .local override) instead.

Fail2ban reads new lines appended to the log. At startup it seeks to the end of the file, so only lines written after the jail is active are counted.

Find the exact log line to match

Look at real failures before writing any regex:

Bash
tail -n 50 /var/log/myapp/auth.log
grep -c 'login failed' /var/log/myapp/auth.log

Copy one line verbatim. Note the timestamp format, the field separator and whether the IP is always in the same position. A regex that matches only one variant of the message will miss everything else, so check a few different failure types with grep -E.

Write the filter regex

Create /etc/fail2ban/filter.d/myapp-auth.conf:

INI
[Definition]

# 2026-01-01 10:12:33 WARN  login failed for user deploy from 203.0.113.10 (bad password)
failregex = ^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} WARN\s+login failed for user \S+ from <HOST>

ignoreregex =

datepattern = ^%%Y-%%m-%%d %%H:%%M:%%S

Key points:

  • <HOST> is the placeholder Fail2ban replaces with an IP (or hostname) pattern. Without it, the filter matches but nothing can be banned.
  • \s+ handles variable whitespace between columns; a literal space often fails.
  • %% in datepattern is an escaped percent sign. Fail2ban expands it to % before handing the format to Python's strptime.
  • Set datepattern explicitly whenever your log does not use the default syslog timestamp. A wrong date pattern is the most common reason a filter reports zero matches.

To ban only the username pattern and log it, add a named group:

INI
failregex = ^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} WARN\s+login failed for user (?P<user>\S+) from <HOST>

Test the filter with fail2ban-regex

Never enable an untested filter. Run it against the live log or a copy:

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

You want output similar to:

Lines: 1840 lines, 0 ignored, 37 matched, 1803 missed
[1] ^\d{4}-\d{2}-\d{2} ... from <HOST>
    37/37 hits

Check the Date template hits section too. If it shows 0 hits, your datepattern does not match the log and the jail will never ban anyone. To see what failed, run:

Bash
fail2ban-regex -v --print-all-missed /var/log/myapp/auth.log /etc/fail2ban/filter.d/myapp-auth.conf | less

Tip: You can test an inline regex before creating a file: fail2ban-regex /var/log/myapp/auth.log '^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} WARN\s+login failed for user \S+ from <HOST>'.

Warning: A poorly written regex with nested quantifiers can take minutes per line on a large log. Test against a trimmed copy (head -n 20000 /var/log/myapp/auth.log > /tmp/test.log) so you do not stall the server.

Create the custom Fail2ban jail

Add the jail to /etc/fail2ban/jail.local. Create the file if it does not exist, and put the [DEFAULT] section at the top:

INI
[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 203.0.113.10

[myapp-auth]
enabled  = true
filter   = myapp-auth
logpath  = /var/log/myapp/auth.log
port     = http,https
backend  = auto
maxretry = 5
findtime = 10m
bantime  = 1h

Notes on each line:

  • filter must match the filename in filter.d/ without the .conf suffix.
  • port decides which ports the ban action blocks. If you omit it, the jail inherits the [DEFAULT] port, which is usually ssh and therefore useless for a web application.
  • ignoreip should always contain your own admin addresses, or you can lock yourself out. See configure jail.local settings for how bantime, findtime and maxretry interact.
  • Use a glob such as logpath = /var/log/myapp/*.log if the app rotates into dated files.

If the application logs only to the systemd journal, replace logpath with:

INI
backend = systemd
journalmatch = _SYSTEMD_UNIT=myapp.service

If the app sits behind Nginx or another proxy, it will log the proxy IP. Configure it to trust and record X-Forwarded-For first, otherwise you will ban your own load balancer.

Reload Fail2ban and confirm the jail is running

Validate the configuration, then reload:

Bash
fail2ban-client -t
systemctl reload fail2ban
systemctl status fail2ban --no-pager

Confirm the jail exists and is watching the file:

Bash
fail2ban-client status
fail2ban-client status myapp-auth

The second command prints the filter list, the log file path and the current banned IPs. The Total line count should be close to the number of lines in the file and should grow over time.

Check it works

Append synthetic failures and watch the ban appear. Run this five times, once per maxretry:

Bash
printf '%s WARN  login failed for user deploy from 203.0.113.10 (bad password)\n' \
  "$(date '+%Y-%m-%d %H:%M:%S')" >> /var/log/myapp/auth.log

Then check:

Bash
fail2ban-client status myapp-auth

You should see 203.0.113.10 in the Banned IP list. Confirm the firewall rule exists:

Bash
iptables -S | grep f2b
# or, on nftables systems:
nft list ruleset | grep -A5 f2b

Clean up the test ban and the fake lines:

Bash
fail2ban-client set myapp-auth unbanip 203.0.113.10

If you use a firewall front end such as firewalld, the ban is applied through a different action — see Fail2ban with firewalld or nftables.

Troubleshooting

  • Jail shows Total line count: 0 or "No file found" — the logpath is wrong, the file does not exist yet, or the glob matches nothing. Check with ls -l /var/log/myapp/ and correct the path. Fail2ban only discovers new files when the jail is reloaded.
  • fail2ban-regex matches lines but the jail never bans — the timestamps are not parsed. Look at the Date template hits block and set datepattern to match your log exactly.
  • Lines match, retries are reached, no ban — the IP is in ignoreip, or the app logs the reverse proxy address instead of the real client. Fix the application's trusted-proxy setting so it writes the original IP.
  • Ban appears but traffic still gets through — the jail's port does not include the service port, or the ban action targets a different firewall backend than the one in use. Check iptables -S versus nft list ruleset.
  • Fail2ban will not start after your edit — a regex or config syntax error. Run fail2ban-client -t, then read /var/log/fail2ban.log for the exact line number.

For persistent problems, see Fail2ban not banning? Common causes and fixes and check banned IPs and unban an address.

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.