MHOSTMHOST

Fail2ban: protection against brute-force

SSH on any public VPS gets scanned by bots almost immediately — the authentication log quickly fills up with failed login attempts from strangers guessing passwords. Fail2ban watches these logs itself and temporarily bans an IP address in the firewall after a few failed attempts in a row. In this article you'll install fail2ban, configure the sshd jail in jail.local (bantime, findtime, maxretry), check its status with fail2ban-client status sshd, and learn how to manually unban an IP address.

What you'll need

  • An mHost VPS on Ubuntu 22.04/24.04, Debian 11/12, or AlmaLinux/Rocky Linux 8/9.
  • SSH access as root or a user with sudo rights.
Tip: the commands below are given with sudo. If you're already connected as root and sudo isn't installed, just run the same commands without the sudo prefix.

Step 1. Install fail2ban

Ubuntu / Debian

bash
sudo apt update
sudo apt install -y fail2ban

The fail2ban package is in the standard repository — no third-party source needed.

AlmaLinux / Rocky Linux

Fail2ban isn't in AlmaLinux/Rocky's standard repositories — enable EPEL first:

bash
sudo dnf install -y epel-release
sudo dnf install -y fail2ban fail2ban-systemd fail2ban-firewalld
Tip: fail2ban-systemd and fail2ban-firewalld aren't optional extras. On AlmaLinux/Rocky the sshd jail reads from the systemd journal by default instead of a log file — that needs the Python bindings from fail2ban-systemd. And fail2ban needs to ban IPs through firewalld — the default firewall on these distros — otherwise it inserts iptables rules that bypass firewalld, and a ban will show up in the status output without actually stopping any traffic. The fail2ban-firewalld package wires this up automatically.

Enable and check the service

bash
sudo systemctl enable --now fail2ban
sudo systemctl status fail2ban

The status should read active (running). While you're at it, check the client version:

bash
fail2ban-client version
Tip: on Debian/Ubuntu, the fail2ban package already enables the sshd jail (via /etc/fail2ban/jail.d/defaults-debian.conf) — SSH is already protected with the default settings. But below we'll still define the jail explicitly in jail.local, so the configuration is clear and identical across distributions.

Step 2. Configure the sshd jail in jail.local

Important: don't edit /etc/fail2ban/jail.conf directly — it's a package file and will be overwritten on the next fail2ban update. Put all your own settings in /etc/fail2ban/jail.local — that's what jail.conf's own header recommends, and settings from jail.local take precedence.

Create the file:

bash
sudo nano /etc/fail2ban/jail.local

And fill it in like this:

ini
[DEFAULT]
ignoreip = 127.0.0.1/8 ::1

[sshd]
enabled  = true
bantime  = 1h
findtime = 10m
maxretry = 5

What these parameters mean:

  • maxretry — how many failed login attempts a single IP is allowed;
  • findtime — the time window they're counted over (if more than findtime passes between attempts, that IP's counter starts over);
  • bantime — how long an IP stays banned once it exceeds maxretry attempts within findtime. Time can be written as a number of seconds or with a suffix: m for minutes, h for hours, d for days, w for weeks;
  • ignoreip — a list of addresses fail2ban will never ban; in jail.conf this option is commented out by default, so without it even localhost isn't exempt;
  • enabled — the sshd jail is disabled by default in jail.conf; enabled = true turns it on here, in jail.local.
Important: ignoreip = 127.0.0.1/8 ::1 only protects localhost from being banned — it won't save your own working IP address. If you have a permanent home/office IP, add it separated by a space: ignoreip = 127.0.0.1/8 ::1 203.0.113.5 — otherwise a few password typos in a row could ban you yourself.
Tip: if SSH on your server listens on a non-default port (not 22), add a port = <your port> line to the same [sshd] section. Fail2ban passes this port to the ban action — if you leave port = ssh (i.e. 22) while SSH actually runs on, say, 2222, the ban will show up in the status output but block the wrong port, and the attack will keep going.

Apply the changes:

bash
sudo systemctl restart fail2ban
Tip: after enabling a jail for the first time (when enabled changes), a full systemctl restart fail2ban as above is the more reliable option. For small tweaks to an already-running jail (say, you only changed maxretry), sudo fail2ban-client reload is usually enough — it doesn't interrupt current monitoring.

Step 3. Check the sshd jail status

List of active jails:

bash
sudo fail2ban-client status
text
Status
|- Number of jail:      1
`- Jail list:   sshd

Details for a specific jail:

bash
sudo fail2ban-client status sshd
text
Status for the jail: sshd
|- Filter
|  |- Currently failed: 2
|  |- Total failed:     13
|  `- File list:        /var/log/auth.log
`- Actions
   |- Currently banned: 1
   |- Total banned:     2
   `- Banned IP list:   203.0.113.10
  • Currently failed / Total failed — how many failed login attempts the filter sees right now (within the current findtime) and in total since the jail started;
  • Currently banned / Total banned — how many IPs are banned right now and how many have been banned in total;
  • Banned IP list — the specific banned addresses.
Tip: on AlmaLinux/Rocky, instead of a File list: /var/log/auth.log line you'll see Journal matches: _SYSTEMD_UNIT=sshd.service... — that's expected, since logs are read from the systemd journal there instead of a file (see Step 1).

Step 4. Unban an IP address

If the wrong address ends up banned (say, your own, after a series of typos), remove the ban manually:

bash
sudo fail2ban-client set sshd unbanip 203.0.113.10

Check that the address is gone from the list:

bash
sudo fail2ban-client status sshd

Banned IP list should no longer contain that IP.

Tip: if you don't remember which jail banned the address, use sudo fail2ban-client unban 203.0.113.10 — this form removes the ban from every jail and from fail2ban's database at once.
Done: the sshd jail is enabled, banning works according to your bantime/findtime/maxretry, and you can unban any IP at any time with a single command.

What's next

Fail2ban isn't limited to SSH — /etc/fail2ban/filter.d/ already ships ready-made filters for Nginx, Apache, Postfix, MySQL, and dozens of other services; to turn any of them on, add your own [jail-name] section to jail.local with enabled = true, the same way as [sshd] above. For IPs that keep coming back after being unbanned, fail2ban includes a separate recidive jail with a longer ban time — that's a topic of its own. Fail2ban bans IPs directly through the server's firewall (iptables/nftables/firewalld), so it works fine alongside ufw — if you haven't set up a firewall on the server yet, see "Setting up the UFW firewall".