I’ve recently had a flood of botnet-driven brute force attempts against my SMTP server. After struggling with some Fail2ban configuration issues, I’d gotten it working smoothly. However, this was a particularly persistent botnet, and as soon as the ban expired, I’d get more entry attempts. This is a problem; I want hackers to be thwarted, so I can’t set ban times too low; but I do not want to lock out my users if they legitimately forget a password. After putting up with my mailbox being flooded with Fail2ban reports, I decided to look into increasing times for subsequent bans. Fail2ban does not support this natively, so I had to look into other options.

The solution I came up with, after trying various complicated methods, is so simple that I feel stupid for not having done it in the first place. All I did was create a filter that parses Fail2ban’s own log, searching for bans. I then created a series of jails and set incrementing “maxretry” variables, with increased find and ban times.

Update July 25, 2020:

Some time ago, I was contacted by a reader who said he found a bug with this implementation. Since the script triggers on a ban, it will overlap with the shorter base ban. Therefore, when the base ban expires, it will take the loop ban down with it, leaving the system undefended. His solution was to, instead, trigger on the Unban event. After some testing and thinking, I think his is a good solution. In addition, when adding/editing jails, it is important to avoid maxretry values that will coincide with other jails. This guide has been edited to reflect the changes, as well as modern ways of implementing configurations.

 

The result of the following example is that, if a service on my server receives 5 failed password attempts in an hour, the IP is banned for ten minutes. If this happens twice in three hours, it is banned for an hour. Thrice in a day, banned for three hours, and so on. All of these values can be adjusted, and I’m still playing with them to find settings I like on my own server, but so far the system works brilliantly well.

 
First, create file: /etc/fail2ban/filter.d/f2b-loop.conf

# Fail2Ban configuration file for subsequent bans
#
[INCLUDES]
before = common.conf
[Definition]
failregex = \]\s+Unban\s+<HOST>
ignoreregex = \[f2b-loop.*\]\s+Unban\s+<HOST>
#
# Author: Walter Heitman Jr.  http://blog.shanock.com

 
Now create /etc/fail2ban/jail.d/f2b-loop.local:

[DEFAULT]
bantime  = 10m
findtime  = 1h
maxretry = 5

[f2b-loop2]
enabled = true
filter = f2b-loop
bantime = 1h
findtime = 3h
logpath = /var/log/fail2ban.log
maxretry = 2

[f2b-loop3]
enabled = true
filter = f2b-loop
bantime = 3h
findtime = 1d
logpath = /var/log/fail2ban.log
maxretry = 3

[f2b-loop4]
enabled = true
filter = f2b-loop
bantime = 1d
findtime = 1w
logpath = /var/log/fail2ban.log
maxretry = 7

[f2b-loop5]
enabled = true
filter = f2b-loop
bantime = 1w
findtime = 1mo
logpath = /var/log/fail2ban.log
maxretry = 11

 

You may add or remove jails, but the naming scheme must be maintained. The reason is that the filter ignores entries from [f2b-loop*] in order to avoid unintended interactions with itself. This solution was last tested under Debian 10 Linux, running Fail2ban version 0.10.2, and may require modification for other distros and versions. You should also consider reviewing dbpurgeage functionality and setting it according to your needs.