All tutorials
14 min read

How to Install and Configure Fail2ban on Ubuntu 24.04 and 26.04

Install Fail2ban on Ubuntu 24.04 or 26.04 and protect SSH in 7 steps: journal backend, UFW bans, incremental ban times, the recidive jail, and unban commands.

TL;DR:

  • Install the fail2ban package, write one /etc/fail2ban/jail.local file, and set banaction = ufw so each ban appears as a UFW rule.
  • Ubuntu 24.04 LTS ships Fail2ban 1.0.2 and Ubuntu 26.04 LTS ships 1.1.0. Both packages turn on the sshd jail and read SSH logins from the systemd journal.
  • With this configuration, 5 failed logins in 10 minutes ban an address for 1 hour, and each repeat ban doubles, up to 1 week.
  • The recidive jail bans an address that collects 5 bans in 1 day from every TCP port for 1 week.
  • ignoreip keeps your own address off the ban list. sudo fail2ban-client status sshd lists banned addresses, and sudo fail2ban-client set sshd unbanip followed by the address removes one.
  • UFW bans do not reach ports that Docker publishes. Keep containers on 127.0.0.1 behind a reverse proxy on the host.

Applies to: Ubuntu 24.04 LTS (Fail2ban 1.0.2) and 26.04 LTS (Fail2ban 1.1.0) · UFW 0.36.2 · checked October 2026

Fail2ban is an open-source intrusion prevention tool, written in Python and licensed under the GPL, that reads log files and the systemd journal and bans IP addresses with too many failed logins by adding firewall rules. This tutorial installs Fail2ban on Ubuntu 24.04 or 26.04, protects SSH with a short jail.local, and covers bans through the Uncomplicated Firewall (UFW), incremental ban times, the recidive jail, a test ban, unbanning, and optional Nginx jails. Fail2ban's README recommends public-key or two-factor authentication alongside it, so this setup assumes SSH keys are in place.

Prerequisites

  • A VPS running Ubuntu 24.04 LTS or 26.04 LTS with SSH access. Fail2ban's README requires Python 3.5 or later and sets no RAM or CPU minimum. Ubuntu's minimum for cloud images is 1 GB of RAM and 4 GB of storage. Arct Cloud Linux VPS images include Ubuntu 26.04 LTS and accept an SSH public key at deploy, and the Cost Optimized cvm.pico plan meets that minimum (1 vCPU, 1 GB RAM, 15 GB NVMe).
  • A non-root user with sudo privileges who logs in with an SSH key. How to Generate an SSH Key on Windows, macOS, and Linux creates the key and the sudo user.
  • The public IP address you connect from, for the ignore list in Step 4.

Commands run as the sudo user. Replace values written in capitals, such as YOUR_IP and YOUR_SERVER_IP, with your own. The examples use 198.51.100.20 as your own address and 192.0.2.50 as a test address.

First steps on a new server

On a new server, check these basics first:

  • Install updates with sudo apt update and sudo apt upgrade.
  • Log in as a sudo user with an SSH key. The SSH key tutorial covers the key, the user, and turning off password login.
  • Run sudo sshd -T | grep -E '^(passwordauthentication|permitrootlogin) '. A passwordauthentication yes line means SSH still accepts passwords; Step 7 of the SSH key tutorial turns that off.
  • Turn on UFW with SSH allowed (Step 1).
  • If you run Docker, publish containers on 127.0.0.1, as How to Install Docker and Docker Compose on Ubuntu 24.04 and 26.04 explains.

Step 1: Enable UFW with SSH allowed

Fail2ban's ufw action adds a reject rule to UFW for each banned address, and those rules filter traffic only while UFW is active. Allow SSH before you enable UFW, or it blocks new SSH logins. If SSH listens on a port other than 22, replace sudo ufw allow OpenSSH with that port, for example sudo ufw allow 2222/tcp. If the ufw command is missing, install it first with sudo apt install ufw:

sudo ufw allow OpenSSH
sudo ufw enable
sudo ufw status

Type y when UFW asks to confirm. The output looks similar to this:

Status: active

To                         Action      From
--                         ------      ----
OpenSSH                    ALLOW       Anywhere
OpenSSH (v6)               ALLOW       Anywhere (v6)

If your provider runs a network firewall in front of the server, open the same ports there.

Step 2: Install Fail2ban

Ubuntu's fail2ban package installs the Fail2ban server, the fail2ban-client command, and python3-systemd, the library that reads the journal. Refresh the package index first. The 1.0.2-3 package from the original Ubuntu 24.04 release fails to start under Python 3.12, and the 1.0.2-3ubuntu0.1 update from June 2024 fixes it.

sudo apt update
sudo apt install fail2ban

Start Fail2ban now and at every boot, then check the version:

sudo systemctl enable --now fail2ban
fail2ban-client --version

The output looks similar to this:

Fail2Ban v1.0.2

On Ubuntu 26.04 the version is v1.1.0. The Fail2ban project released 1.1.1 in August 2026; the Ubuntu archive carried 1.0.2 and 1.1.0, with distribution patches, in October 2026.

Step 3: Check the Ubuntu defaults

Ubuntu's package adds /etc/fail2ban/jail.d/defaults-debian.conf, which turns on the sshd jail. Read it before you add your own settings:

cat /etc/fail2ban/jail.d/defaults-debian.conf

On Ubuntu 24.04 the file contains:

[DEFAULT]
banaction = nftables
banaction_allports = nftables[type=allports]
backend = systemd

[sshd]
enabled = true

On Ubuntu 26.04 the journal backend applies to the sshd jail only:

[DEFAULT]
banaction = nftables
banaction_allports = nftables[type=allports]

[sshd]
backend = systemd
journalmatch = _SYSTEMD_UNIT=ssh.service + _COMM=sshd
enabled = true
  • backend = systemd makes a jail read the systemd journal, where Ubuntu's SSH service logs its logins, and ignore log files.
  • On Ubuntu 24.04 that backend applies to every jail, but the recidive and Nginx jails read log files, so jail.local sets the file backend back for them.
  • banaction = nftables writes bans as nftables rules, which sudo ufw status does not list. Step 4 switches to UFW.

Fail2ban reads jail.conf, then jail.d/*.conf, then jail.local, then jail.d/*.local, and a later file wins for the same setting. Leave jail.conf unchanged: its header says a distribution update may overwrite it. The 26.04 package is also patched for sshd-session, the process name OpenSSH 9.8 and later use in login messages.

Step 4: Write jail.local for SSH

jail.local holds every setting you change. Create it:

sudo nano /etc/fail2ban/jail.local

Paste these lines, then save with Ctrl+O and exit with Ctrl+X:

[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 YOUR_IP
bantime = 1h
findtime = 10m
maxretry = 5
bantime.increment = true
bantime.maxtime = 1w
banaction = ufw
backend = auto

[sshd]
enabled = true
backend = systemd

Replace YOUR_IP with the address you connect from. To print it, run this in your SSH session, without sudo:

echo "${SSH_CONNECTION%% *}"

SSH_CONNECTION holds four values, and the first is your client address, such as 198.51.100.20. If your server has a public IPv6 address and you connect over IPv6, add that address or your IPv6 prefix as well. Separate entries with spaces.

SettingValueWhat it does
ignoreiploopback and your addressNever banned. ignoreself, on by default, also skips the server's own addresses.
bantime1hLength of a first ban (default 10 minutes).
findtime, maxretry10m, 55 failures from one address within 10 minutes trigger a ban (the defaults).
bantime.incrementtrueEach repeat ban doubles: 1 hour, then 2, 4 and 8 hours. The history lives in /var/lib/fail2ban/fail2ban.sqlite3.
bantime.maxtime1wStops the doubling at 1 week.
banactionufwBans become UFW reject rules for every port.
backendauto, systemd for sshdJails read log files; the SSH jail reads the journal.

The ufw action ignores the jail's port setting and rejects all traffic from the banned address, so an SSH port other than 22 needs no change here.

Step 5: Turn on the recidive jail

The recidive jail reads Fail2ban's own log, /var/log/fail2ban.log. With the defaults from jail.conf and maxretry = 5 from [DEFAULT], it bans an address that collects 5 bans in 1 day for 1 week. Open jail.local again and add this section at the end:

[recidive]
enabled = true
banaction = iptables-allports

This jail uses Fail2ban's iptables-allports action instead of UFW. UFW keeps one rule per address, so when two jails ban the same address through UFW, the first unban removes the rule for both, and a 1-hour SSH ban would end a 1-week recidive ban early. iptables-allports keeps recidive bans in a separate f2b-recidive chain for every TCP port. UFW depends on iptables, so the command is already installed.

jail.conf also advises raising the ban database purge age (1 day by default) for recidive. Create /etc/fail2ban/fail2ban.local with its suggested 648000 seconds, 7.5 days:

sudo tee /etc/fail2ban/fail2ban.local > /dev/null <<'EOF'
[Definition]
dbpurgeage = 648000
EOF

Step 6: Test the configuration and restart Fail2ban

Check the configuration before you restart, so a typo does not stop the service:

sudo fail2ban-client -t
OK: configuration test is successful

Restart Fail2ban and list the running jails:

sudo systemctl restart fail2ban
sudo fail2ban-client status

The output looks similar to this:

Status
|- Number of jail:	2
`- Jail list:	recidive, sshd

If the test prints an error instead, find it in the troubleshooting table below.

Step 7: Verify Fail2ban with a test ban

Check the SSH jail:

sudo fail2ban-client status sshd

The output looks similar to this:

Status for the jail: sshd
|- Filter
|  |- Currently failed:	0
|  |- Total failed:	0
|  `- Journal matches:	_SYSTEMD_UNIT=sshd.service + _COMM=sshd
`- Actions
   |- Currently banned:	0
   |- Total banned:	0
   `- Banned IP list:

A Journal matches line means the jail reads the systemd journal; a File list line means it reads a log file. On Ubuntu 26.04 the match names ssh.service. Total failed and Total banned count from the last start of the jail, so on a public server they grow as login attempts arrive.

Ban 192.0.2.50, an address reserved for documentation, and look at UFW:

sudo fail2ban-client set sshd banip 192.0.2.50
sudo ufw status

The ban command prints 1, the number of addresses banned. UFW now lists a reject rule at the top:

Status: active

To                         Action      From
--                         ------      ----
Anywhere                   REJECT      192.0.2.50                 # by Fail2Ban after 0 attempts against sshd
OpenSSH                    ALLOW       Anywhere
OpenSSH (v6)               ALLOW       Anywhere (v6)

A manual ban records 0 attempts, and /var/log/fail2ban.log logs every ban as a [sshd] Ban line. Remove the test ban:

sudo fail2ban-client set sshd unbanip 192.0.2.50

The command prints 1 and deletes the UFW rule. For a real ban, try ssh nouser@YOUR_SERVER_IP 5 times from a network outside ignoreip, such as a phone hotspot. Each try logs at least one Invalid user line, so the address is banned by the fifth try.

sudo fail2ban-client status recidive shows File list: with /var/log/fail2ban.log, which confirms the file backend. Later, sudo grep "Increase Ban" /var/log/fail2ban.log shows incremental bans.

Add Nginx jails for login-page bots (optional)

Fail2ban includes Nginx filters that read the log files in /var/log/nginx/. They apply to Nginx installed on the host, such as the setup in the Nginx, PostgreSQL, and Redis guide, and not to Nginx inside a container. Add two jails to the end of jail.local:

[nginx-http-auth]
enabled = true
banaction = iptables-multiport

[nginx-botsearch]
enabled = true
banaction = iptables-multiport

Like recidive, these jails use iptables, so their bans stay in their own f2b- chains, apply to ports 80 and 443, and do not remove the SSH jail's UFW rules.

  • nginx-http-auth bans addresses that fail HTTP basic authentication on a password-protected page, from the password mismatch and was not found in lines in the error log.
  • nginx-botsearch bans addresses that request missing WordPress login, phpMyAdmin, webmail, or cgi-bin paths. It matches the error-log lines Nginx writes for missing files, so it bans only when your setup logs those lines.
  • The package also ships nginx-limit-req, nginx-bad-request and, on Ubuntu 26.04, nginx-forbidden.

Test a filter against your own log first. The Lines: summary reports how many lines matched:

sudo fail2ban-regex /var/log/nginx/error.log nginx-botsearch

Then test the configuration and restart:

sudo fail2ban-client -t
sudo systemctl restart fail2ban

sudo fail2ban-client status now lists 4 jails.

Fail2ban and Docker-published ports

Docker's documentation states that traffic to published container ports is diverted in the nat table before it reaches the INPUT chain UFW uses, so a banned address can still reach a port that Docker publishes. Bans do apply to a reverse proxy running on the host, because its ports pass through UFW. Publish containers on 127.0.0.1 behind a host reverse proxy, as the Docker tutorial sets up. For a container port that must stay public, Docker's iptables documentation names the DOCKER-USER chain as the place for your own rules on container traffic.

Unban an IP address and recover from a lockout

Unban one address in one jail, or in every jail and the ban database:

sudo fail2ban-client set sshd unbanip 198.51.100.20
sudo fail2ban-client unban 198.51.100.20

To list banned addresses per jail, run sudo fail2ban-client banned. To clear every ban, run sudo fail2ban-client unban --all.

If your own address gets banned:

  • Keep a second SSH session open while you change Fail2ban or SSH settings. UFW accepts packets for open connections before it checks its own rules, so that session survives an sshd ban. A recidive ban sits in front of UFW and cuts open sessions too.
  • Connect from another network, such as a phone hotspot, and run the unban command.
  • Wait: a first SSH ban lasts 1 hour here, repeats last longer, and a recidive ban lasts 1 week.
  • Use a provider web console, if your provider offers one.

Then add the address to ignoreip and restart Fail2ban.

Update Fail2ban

Ubuntu updates Fail2ban through apt with the rest of the system:

sudo apt update
sudo apt upgrade

The package does not ship jail.local or fail2ban.local, so updates leave your settings in place. A release upgrade from Ubuntu 24.04 to 26.04 moves Fail2ban from 1.0.2 to 1.1.0. After you upgrade Ubuntu 24.04 to 26.04, run sudo fail2ban-client -t and sudo fail2ban-client status sshd again.

Troubleshoot common Fail2ban errors

Fail2ban's service log is in sudo journalctl -u fail2ban, and its ban log is /var/log/fail2ban.log.

SymptomCauseFix
No module named 'asynchat' in journalctl -u fail2banThe 1.0.2-3 package from the original Ubuntu 24.04 release, or Fail2ban 1.0.2 or older built from source, on Python 3.12Run sudo apt update and sudo apt install fail2ban to get 1.0.2-3ubuntu0.1 or newer
Failed during configuration: Have not found any log file for sshd jailThe SSH jail uses a file backend and /var/log/auth.log does not existSet backend = systemd in the [sshd] section
Have not found any log file for nginx-botsearch jailAn Nginx jail is on but /var/log/nginx/ has no matching logInstall Nginx on the host, or set enabled = false for that jail
Total failed stays at 0 while logins failThe failing address is in ignoreip, or the jail reads the wrong sourceTest from an address outside ignoreip, and run sudo fail2ban-regex systemd-journal sshd
fail2ban-client status sshd shows a ban but ufw status does notbanaction is still nftables from defaults-debian.conf, or UFW is inactiveSet banaction = ufw in jail.local, run sudo ufw enable, and restart Fail2ban
recidive never bans on Ubuntu 24.04The jail inherited backend = systemd, and Fail2ban writes its log to a fileKeep backend = auto in the [DEFAULT] section of jail.local

FAQ

Is fail2ban still used?

Yes. Ubuntu 26.04 LTS packages Fail2ban 1.1.0, and the project released 1.1.1 in August 2026. Its README says it cuts the rate of failed logins but cannot fix weak authentication, so run it next to SSH key login.

What triggers fail2ban?

A log line that matches the jail's filter, such as Failed password or Invalid user for SSH. When one address produces maxretry matches within findtime, 5 in 10 minutes in this tutorial, Fail2ban bans it. Failed public-key attempts by valid users do not count by default.

How to know if fail2ban is working?

Run sudo fail2ban-client status sshd. Total failed and Total banned grow as login attempts arrive, and /var/log/fail2ban.log records each ban. A test ban with sudo fail2ban-client set sshd banip 192.0.2.50 also appears in sudo ufw status.

How do I unban an IP in fail2ban?

Run sudo fail2ban-client set sshd unbanip followed by the address to unban it in one jail, or sudo fail2ban-client unban followed by the address for every jail and the ban database. Fail2ban removes the firewall rule as well.

How do I whitelist an IP in fail2ban?

Add the address to ignoreip in the [DEFAULT] section of /etc/fail2ban/jail.local, separated by spaces, then restart Fail2ban. ignoreip accepts single addresses and CIDR ranges. The server's own addresses are already skipped, because ignoreself is on by default.

Does fail2ban work with Docker?

Fail2ban runs next to Docker, but UFW bans do not reach ports that Docker publishes, because Docker routes that traffic before UFW's rules. Publish containers on 127.0.0.1 behind a reverse proxy on the host, where the bans apply.

Where are the fail2ban logs?

Fail2ban writes its log to /var/log/fail2ban.log, which Ubuntu rotates weekly and keeps 4 old copies of. The SSH login failures it reads are in the systemd journal: run sudo journalctl -u ssh to see them.

Next steps

Arct Cloud Linux images deploy in under 30 seconds with root SSH on every plan. Compare plans.

This work is licensed under CC BY-NC-SA 4.0.