Skip to content

Fail2ban

Initial configuration

Next, we’ll install and configure fail2ban. This service basically scans log files and looks for potentially malicious attempts at accessing the system. If it finds such an attempt, it will reconfigure the firewall to block the offending IP address for a specified amount of time. It’s a useful defence against brute force password attempts and basic DOS attacks:

sudo apt-get install fail2ban
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo nano /etc/fail2ban/jail.local

Explanation: The fail2ban configuration is stored at /etc/fail2ban/jail.conf, however it could potentially be overwritten if the software is updated. For this reason, we use the cp command to copy the file to a new file called jail.local. The rules in this file will not be overwritten and take precedence over those defined in jail.conf.

Find the following lines and change as shown. These will be the defaults for fail2ban. We shall assume that Ronald has an email address of ronald@example.com. The following default rules are saying that if a person attempts and fails to access the system 6 times within a 3600 second (1 hour) window, then ban the IP address for 3600 seconds. The action = $(action_mwl)s statement is saying that each time the rule is triggered, ban the IP address, and send an email alert including the relevant lines from the log file. You can also whitelist addresses in the ignoreip section.

ignoreip = 127.0.0.1 ::1
bantime = 3600
findtime = 3600
maxretry = 6
destemail = admin@example.com
sendername = Fail2Ban
mta = sendmail
#action = $(action_mwl)s if you want an email for every ban
action = $(action_)s #ban only

The time can also be inputted using different units other than the default of seconds:

Unit Abbreviation
days days?, da, dd?
weeks weeks?, wee?, ww?
months months?, mon?
years years?, yea?, yy?
seconds seconds?, sec?, ss?
minutes minutes?, min?, mm?
hours hours?, hou?, hh?

A '?' means the char is optional, so 'days?' means that it will accept 'day' as well as 'days'.

Next, we’ll enable the jail for ssh. This is also found within the same config file. We can make this one a little stricter and limit it to just 3 incorrect attempts over a period of 1 hour. Find the following section and change as shown:

sudo nano /etc/fail2ban/jail.local
[sshd]
enabled = true
port = 371
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 3hh
backend = %(sshd_backend)s
action = iptables-allports

Note that the default config has the line port = ssh. If this isn't changed then it will be set up to deal with the default ssh port 22. The default action blocks only relevant ports, but I sometimes like to override this and completely block the offending IP from all ports. This can be done with the action = iptables-allports line.

Also ensure that the lines that you are enabling are not commented out. If they are commented out then they will be prefixed with a hash sign (#). Make sure that these are removed for the above lines.

Restart the service so that the new rules are properly applied:

sudo service fail2ban restart

Check that the service is running with an SSH jail:

sudo fail2ban-client status

We can also create jails for other services that we’ll install later. The Ubuntu fail2ban documentation can be found at https://help.ubuntu.com/community/Fail2ban and the official fail2ban wiki can be found at https://www.fail2ban.org/wiki/index.php/Main_Page.

IPTables

I personally prefer the Fail2Ban IPTables rules to be inserted in their own custom chain. See the IPTables HowTo for more details.

The comments in the iptables-common.conf file suggest that you need to create a iptables-common.local file to override. Confusingly, the chain variable is also defined in jail.conf. To be on the safe side, I do both!

sudo nano /etc/fail2ban/action.d/iptables-common.local
[Init]

# Option:  chain
# Notes    specifies the iptables chain to which the Fail2Ban rules should be
#          added
# Values:  STRING  Default: INPUT
chain = FAIL2BAN
sudo nano /etc/fail2ban/jail.local
...
chain = FAIL2BAN
...

Jails

As examples, here are a few of the jails that I have configured on my server:

Dovecot

/etc/fail2ban/filter.d/dovecot-pop3imap.conf

[Definition]
failregex = (?: pop3-login|imap-login): .*(?:Authentication failure|Aborted login \(auth failed|Aborted login \(tried to use disabled|Disconnected \(auth failed).*rip=(<HOST>\S*?),.*

/etc/fail2ban/jail.d/dovecot-pop3imap.local

[dovecot-pop3imap]
enabled = true
filter = dovecot-pop3imap
action = iptables-multiport[name=dovecot-pop3imap, port="25,465,563,993", protocol=tcp]
logpath = /var/log/mail.log
maxretry = 20
findtime = 3600
bantime = 3600

Prosody

in order to use this one, the module mod_log_auth needs to be enabled within Prosody

/etc/fail2ban/filter.d/prosody-auth.conf

[Definition]
failregex = Failed authentication attempt \(not-authorized\) for user .* from IP: <HOST>
ignoreregex =

/etc/fail2ban/jail.d/dovecot-pop3imap.local

[prosody]
enabled = true
port    = 5222
filter  = prosody-auth
logpath = /var/log/prosody/prosody*.log
maxretry = 6
bantime = 36000
findtime = 36000
action = iptables-allports

Nextcloud

/etc/fail2ban/filter.d/nextcloud.conf

[Definition]
failregex=^{"reqId":".*","remoteAddr":".*","app":"core","message":"Login failed: '.*' \(Remote IP: '<HOST>'\)","level":2,"time":".*"}$
          ^{"reqId":".*","level":2,"time":".*","remoteAddr":".*","app":"core".*","message":"Login failed: '.*' \(Remote IP: '<HOST>'\)".*}$
          ^.*\"remoteAddr\":\"<HOST>\".*Trusted domain error.*$

/etc/fail2ban/jail.d/nextcloud.local

[nextcloud]
backend = auto
enabled = true
port = 80,443
protocol = tcp
filter = nextcloud
maxretry = 3
bantime = 36000
findtime = 36000
logpath = /var/nc_data/nextcloud.log
action = iptables-allports

Gitea

/etc/fail2ban/filter.d/gitea.conf

[Definition]
failregex =  .*Failed authentication attempt for .* from <HOST>
ignoreregex =

/etc/fail2ban/jail.d/gitea.local

[gitea]
enabled = true
port = http,https
filter = gitea
logpath = /var/lib/gitea/log/gitea.log
maxretry = 4
findtime = 36000
bantime = 36000
action = iptables-allports

Wordpress

This assumes that the 'WP fail2ban' plugin has been installed and configured (https://en-gb.wordpress.org/plugins/wp-fail2ban/).

/etc/fail2ban/filter.d/wordpress-hard.conf

[INCLUDES]

before = common.conf

[Definition]

_daemon = (?:wordpress|wp)

failregex = ^%(__prefix_line)sAuthentication attempt for unknown user .* from <HOST>$
            ^%(__prefix_line)sREST authentication attempt for unknown user .* from <HOST>$
            ^%(__prefix_line)sXML-RPC authentication attempt for unknown user .* from <HOST>$
            ^%(__prefix_line)sSpam comment \d+ from <HOST>$
            ^%(__prefix_line)sBlocked user enumeration attempt from <HOST>$
            ^%(__prefix_line)sBlocked authentication attempt for .* from <HOST>$
            ^%(__prefix_line)sXML-RPC multicall authentication failure from <HOST>$
            ^%(__prefix_line)sPingback error .* generated from <HOST>$

ignoreregex =

/etc/fail2ban/filter.d/wordpress-soft.conf

[INCLUDES]

before = common.conf

[Definition]

_daemon = (?:wordpress|wp)

failregex = ^%(__prefix_line)sAuthentication failure for .* from <HOST>$
            ^%(__prefix_line)sREST authentication failure for .* from <HOST>$
            ^%(__prefix_line)sXML-RPC authentication failure for .* from <HOST>$

ignoreregex =
/etc/fail2ban/jail.d/wordpress.local

[wordpress-hard]
enabled = true
filter = wordpress-hard
logpath = /var/log/auth.log
maxretry = 1
bantime = 36000
findtime = 36000
port = http,https

[wordpress-soft]
enabled = true
filter = wordpress-soft
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600
findtime = 3600
port = http,https

Show all jails script

Using the fail2ban client, you can only view the status of individual jails. This script will loop through all jails and display them all at once:

sudo nano /usr/local/sbin/fail2ban-allstatus.sh
#!/bin/bash
JAILS=`fail2ban-client status | grep "Jail list" | sed -E 's/^[^:]+:[ \t]+//' | sed 's/,//g'`
for JAIL in $JAILS
do
fail2ban-client status $JAIL
done

I usually always tighten the security of the scripts that I run as root:

sudo chmod 700 /usr/local/sbin/fail2ban-allstatus.sh