Protecting SSH with Fail2ban
SSH allows you to log in to a remote computer or server and control it through a command-line interface. However, because SSH is exposed to the internet; attackers can try to log in by trying various username and password combinations.
A good way to protect SSH would be to ban an IP address from logging in if there are too many failed login attempts. Fail2ban does this right out of the box. In addition, you can even configure Fail2ban to protect other applications, like web servers.
We’ll cover how to protect SSH with Fail2ban in this post.
On Debian and Ubuntu, you can install Fail2ban with:
sudo apt update sudo apt install fail2ban
On CentOS, you should first enable the EPEL repository and install Fail2ban.
sudo yum -y install epel-release sudo yum -y install fail2ban
Having enabled it, you should enable and start the service.
sudo systemctl enable fail2ban sudo systemctl start fail2ban
Fail2ban should now protect SSH out of the box. If Fail2ban notices six failed login attempts in the last ten minutes, then it blocks that IP for ten minutes. However, you can change this policy if you wish.
The basics of Fail2ban
So how does that work? Put simply, Fail2ban is a daemon that monitors logs and takes actions based on their contents. It is driven by three types of configuration:
- Filters specify certain patterns of text that Fail2ban should recognize in log files.
- Actions are things Fail2ban can do.
- Jails tell Fail2ban to match a filter on some logs. When the number of matches goes beyond a certain limit specified in the jail, Fail2ban takes an action specified in the jail.
Fail2ban comes with a jail instructing it to look at system logs and take actions against attacks on SSH. The default action (which is discussed later in detail) adds iptables rules to block out attackers.
When you make changes to the Fail2ban configuration, you need to restart it for changes to come into effect:
sudo systemctl restart fail2ban
On older, sysvinit based systems, the command is a bit different:
sudo service fail2ban restart
Configuring the jail
While the defaults will suffice for most, sometimes you may need to change things so that it suits your needs better.
In order to do this, you should determine the jail that protects SSH. Run this on the remote system:
sudo iptables -L -n | sed -nr 's/Chain (fail2ban|f2b)-([^ ]+).*/\2/p'
You will see something like
sshd as an output. This is the jail we were looking for. For the purposes of this tutorial, we’ll assume the jail is named
/etc/fail2ban/jail.local with a text editor such as nano:
sudo nano /etc/fail2ban/jail.local
Type the jail name you just found out inside square brackets, like so:
This begins the configuration of a jail. The rules of the jail will go below it.
Say, you want to block IP addresses for a day, if they have tried to make 10 login attempts within 12 hours. To do this, type in the following text below
maxretry = 10 findtime = 43200 bantime = 86400
The file would end up looking like this:
[sshd] maxretry = 10 findtime = 43200 bantime = 86400
maxretrycontrols the maximum number of allowed retries.
findtimespecifies the time window (in seconds) which should be considered for banning an IP.
bantimespecifies the time window (in seconds) for which the IP address will be banned.
If you have moved SSH to a different port, you can also specify the port number with
port = <port_number> in this file.
Restart Fail2ban for these changes to come into effect.
Configuring the action
By default, Fail2ban uses the iptables-multiport action, which rejects packets with a “port unreachable” message. If you want to drop packets instead of rejecting them, create
/etc/fail2ban/action.d/iptables-common.local and type in the following:
[Init] blocktype = DROP
On some older distributions, you may have to save the file in
If you want to take an entirely custom action, you have to first define an action file in
/etc/fail2ban/action.d/. A typical action file looks like this:
[Definition] actionstart = ... actionstop = ... actioncheck = ... actionban = ... actionunban = ...
As their names suggest, the
actionstop lets you specify initialization commands that will be run when Fail2ban starts up and shuts down.
actionunban specify the commands which will be run when Fail2ban wants to block an IP. In addition, Fail2ban may also need to verify whether an IP is already blocked, and the command specified in
actioncheck helps it to do so.
When specifying these commands, you can use the
<ip> placeholder to get the IP address of the attacker.
After you’ve written your action file, in the SSH jail defined in
/etc/fail2ban/jail.local, you can set the action by setting the following:
banaction = my-action
Blocking repeat offenders with multiple jails
As a server administrator, you may have noticed that there are some IPs that keep brute forcing over and over, despite the bans. You could work around this by setting a low value of
findtime and a high value of
bantime, but this risks locking out legitimate users. Fortunately, you can block these attacks by setting up multiple jails.
For our example, we’re going to define a jail named
sshlongterm. It will block an IP for a week if there are 35 failed attempts login attempts over 3 days.
However, before we get started, we need the configuration of the default jail. If you open
/etc/fail2ban/jail.conf, you will find a default configuration of the SSH jail. On Ubuntu 16.04, the default jail is named
sshd and the configuration looks like this:
[sshd] port = ssh logpath = %(sshd_log)s
Copy these rules over to
/etc/fail2ban/jail.local, under the definition of the
sshlongterm jail. The file should end up looking like this:
[sshd] ; rules for the default SSH jail if you've customized it. [sshlongterm] port = ssh logpath = %(sshd_log)s
If you don’t have a
filter = <value> rule in the default configuration (like our case), you need to define a filter explicitly for this jail:
filter = sshd
Now, you need to set up the primary rules for the jail. Add the following lines:
maxretry = 35 findtime = 259200 bantime = 608400 enabled = true
These settings allow up to 35 failed logins over 3 days before blocking the IP for a week.
The complete jail would end up looking like this:
[sshlongterm] port = ssh logpath = %(sshd_log)s filter = sshd maxretry = 35 findtime = 259200 bantime = 608400 enabled = true
Restart fail2ban for these changes to take effect.
The default jail as well as the
sshlongterm jail should now work in conjunction. Short term attacks will be handled by the default jail, and the long term attacks handled by our own jail.
As we’ve seen, Fail2ban protects SSH right away. With a little bit of configuration, it can make massive brute force attacks a trivial problem.