Any SSH server reachable from the internet collects failed login attempts within minutes of going online. Most of them come from automated scanners cycling through common usernames and passwords. fail2ban reads the authentication log, counts those failures per source address and drops the offender into a firewall rule before the attempts add up to anything.
This tutorial covers installing fail2ban, writing a jail.local for the sshd jail, enabling incremental ban times, and verifying that bans actually reach the firewall.
What is fail2ban?
fail2ban is a log-parsing daemon that matches failed authentication attempts against filter regexes and inserts a temporary firewall rule for every source IP address that exceeds a configured failure threshold within a time window. It ships with filters for OpenSSH, Postfix, Dovecot, nginx and around 100 other services.
fail2ban does not replace key-based authentication or a firewall. It reduces noise and stops password-guessing loops, and it is one layer in a set that also includes PasswordAuthentication no, a restricted AllowUsers list and a default-deny firewall policy.
Prerequisites
- A Linux server with root or
sudoaccess. The commands below are shown for Ubuntu 24.04 and Debian 12 (fail2ban 1.0.2) and for RHEL-compatible distributions such as AlmaLinux 9. A scalable Cloud VM from centron is a suitable test target. - A running OpenSSH server.
- A second SSH session that stays open for the whole procedure. A misconfigured jail can ban your own address, and an already-established session gives you a way back in.
- Knowledge of the public IP address or network you connect from.
Install fail2ban
On Debian and Ubuntu the package is in the main repository:
$ sudo apt update
$ sudo apt install fail2banOn AlmaLinux, Rocky Linux and RHEL 9, fail2ban comes from EPEL:
$ sudo dnf install epel-release
$ sudo dnf install fail2ban fail2ban-firewalldEnable and start the service:
$ sudo systemctl enable --now fail2ban
$ systemctl status fail2ban --no-pagerCheck the version before you copy configuration snippets from elsewhere. Options such as bantime.increment require fail2ban 0.11 or newer:
$ fail2ban-client version
1.0.2Matching infrastructure at centron
No hardware needed to follow along: ccloud³ VMs with full root access, billed by the hour and ready in seconds. Rent a cloud server →
How does fail2ban decide to ban an IP?
fail2ban bans an IP address when the number of log lines matching a jail's failregex reaches maxretry inside the findtime window, and it then keeps the firewall rule in place for bantime seconds. All three values are per jail, so the sshd jail can be stricter than a mail jail on the same host.
The path from a failed login to a firewall rule looks like this:
graph TD
A["Failed SSH login"] --> B["journald or /var/log/auth.log"]
B --> C["Filter /etc/fail2ban/filter.d/sshd.conf"]
C -->|"failregex matches"| D["Failure counter per IP"]
C -->|"no match"| E["Line ignored"]
D --> F{"maxretry reached within findtime?"}
F -->|"no"| G["Counter expires after findtime"]
F -->|"yes"| H["banaction inserts firewall rule"]
H --> I["Rule removed after bantime"]
Addresses listed in ignoreip never reach the counter stage, which is what keeps your own management network out of the jail.
Configure the sshd jail
Never edit /etc/fail2ban/jail.conf. Package upgrades overwrite it. Put your settings in /etc/fail2ban/jail.local, which is read last and wins:
$ sudo nano /etc/fail2ban/jail.localA working configuration for a public-facing SSH server:
[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 203.0.113.0/24
bantime = 1h
findtime = 10m
maxretry = 5
bantime.increment = true
bantime.factor = 2
bantime.maxtime = 1w
[sshd]
enabled = true
backend = systemd
port = ssh
maxretry = 4
findtime = 10m
bantime = 1hWhat the individual keys do:
ignoreip— space-separated list of addresses and CIDR ranges that are never banned. Replace203.0.113.0/24with your own office or VPN range.findtime— the sliding window in which failures are counted.10mmeans five failures spread over an hour do not trigger a ban.maxretry— failures required insidefindtimebefore the ban action runs.bantime— how long the firewall rule stays in place. A negative value (-1) bans permanently.backend— where the jail reads its log data from.systemdreads the journal directly and is the correct choice on hosts withoutrsyslog.port— the port the ban rule covers. If SSH listens on a non-standard port, write the number here, for exampleport = 2222.
Enable incremental ban times
bantime.increment = true multiplies the ban duration by bantime.factor each time the same address is banned again, up to bantime.maxtime. With the values above, a host is banned for 1 hour, then 2 hours, then 4 hours, capped at one week. This punishes persistent scanners without permanently blacklisting an address that a legitimate user may later be assigned.
Choose the right ban action
The default banaction is iptables-multiport. On Debian 12 and Ubuntu 24.04 the iptables command is a compatibility wrapper around nftables, so this works out of the box. If you manage rules with nftables natively, or with firewalld, set the matching action in the [DEFAULT] section:
# Native nftables
banaction = nftables-multiport
# firewalld (RHEL-compatible systems)
banaction = firewallcmd-rich-rulesReload fail2ban so it reads the new file:
$ sudo fail2ban-client reloadVerify the jail is active
Ask fail2ban which jails it loaded and what the sshd jail currently sees:
$ sudo fail2ban-client status
$ sudo fail2ban-client status sshdExpected output for an active jail:
Status for the jail: sshd
|- Filter
| |- Currently failed: 2
| |- Total failed: 47
| `- Journal matches: _SYSTEMD_UNIT=sshd.service + _COMM=sshd
`- Actions
|- Currently banned: 1
|- Total banned: 6
`- Banned IP list: 198.51.100.24If Total failed stays at 0 on a server that has been reachable for a while, the filter is not matching. Test the filter against the log source directly:
$ sudo fail2ban-regex systemd-journal /etc/fail2ban/filter.d/sshd.confThe summary at the end reports how many lines were matched. A Lines: ... 0 matched result means the backend or the filter is wrong, not that no attacks happened.
Confirm the ban reached the firewall. For the iptables action:
$ sudo iptables -S f2b-sshdFor the nftables action:
$ sudo nft list set inet f2b-table addr-set-sshdBans and unbans are also written to /var/log/fail2ban.log, which is useful when you want to correlate blocks with monitoring data or forward the events to a central log collector.
Unban an address
Remove a single address from a jail:
$ sudo fail2ban-client set sshd unbanip 198.51.100.24Clear every ban in a jail at once:
$ sudo fail2ban-client unban --allList the addresses a jail currently holds (fail2ban 0.10 and newer):
$ sudo fail2ban-client get sshd bannedAn unban only removes the firewall rule. If the source keeps trying, it will be banned again on the next threshold breach, and with bantime.increment enabled the next ban is longer.
Add a recidive jail for repeat offenders
The recidive jail watches /var/log/fail2ban.log itself and applies a long ban to hosts that have already been banned several times by other jails. Append it to /etc/fail2ban/jail.local:
[recidive]
enabled = true
logpath = /var/log/fail2ban.log
banaction = %(banaction_allports)s
findtime = 1d
bantime = 4w
maxretry = 5This blocks an address on all ports for four weeks once it has collected five bans within a day. The jail depends on fail2ban writing its own log file. On installations that log only to the journal, set logtarget = /var/log/fail2ban.log in /etc/fail2ban/fail2ban.local first.
$ sudo fail2ban-client reload
$ sudo fail2ban-client status recidiveTroubleshooting
The jail fails to start with Failed during configuration: Have not found any log file. Ubuntu 24.04 server images do not always install rsyslog, so /var/log/auth.log does not exist. Set backend = systemd in the [sshd] section, or install rsyslog if you prefer file-based logs.
Bans are counted but connections still get through. The banaction does not match the firewall in use. Check journalctl -u fail2ban -n 50 for iptables or nft errors, then set banaction to nftables-multiport or firewallcmd-rich-rules as described above.
You banned your own address. Use an existing session, the provider console or an out-of-band connection and run fail2ban-client set sshd unbanip <YOUR_IP>. Then add your network to ignoreip and reload. Existing connections are not terminated by a ban, which is why keeping a second session open matters.
Changes in jail.local appear to have no effect. Confirm which values fail2ban actually loaded:
$ sudo fail2ban-client get sshd maxretry
$ sudo fail2ban-client get sshd bantimeFiles in /etc/fail2ban/jail.d/ are read after jail.local. On Debian and Ubuntu, jail.d/defaults-debian.conf enables the sshd jail and can override settings you expected to win.
Wrap-up
The sshd jail now blocks any address that fails four logins within ten minutes, and repeat offenders accumulate longer bans automatically. Verify the setup with fail2ban-client status sshd after a day of uptime. The counters tell you whether the filter matches real traffic.
fail2ban handles the noise, not the root cause. Disable password authentication in /etc/ssh/sshd_config with PasswordAuthentication no, restrict logins to specific accounts with AllowUsers, and keep the ban thresholds strict enough that a scanner never gets more than a handful of attempts.
Testen Sie Ihr Setup auf ccloud³
Registrieren Sie sich in der ccloud³ und erhalten Sie 200 € Startguthaben für Ihr Projekt – z. B. für eine PostgreSQL-VM mit automatischen Backups.