Block Brute-Force Attacks and Bad IPs with fail2ban
Watch your logs, catch failed logins, and automatically ban attacking IPs to add a self-defending layer to your server.
Any server exposed to the public internet gets hammered daily by thousands of automated brute-force attempts against its SSH port. Strong passwords or keys alone don't reduce that noise. fail2ban does: it automatically locks out attackers who keep guessing.
How fail2ban works
fail2ban is a background daemon with a refreshingly simple loop:
- Read the logs. It continuously tails log files (such as SSH's /var/log/auth.log) or the systemd journal.
- Match failures. Regex rules (called filters) recognize events like "authentication failure."
- Count and ban. When one IP hits maxretry failures within the findtime window, fail2ban tells the firewall (iptables/nftables/firewalld) to block it for bantime.
- Auto-unban. Once bantime elapses, the IP is released automatically. No manual cleanup.
One bundle of "watch + rules + ban action" is called a jail. The most common one is sshd.
Installation
Ubuntu / Debian:
sudo apt update
sudo apt install fail2ban
sudo systemctl enable --now fail2ban
CentOS / Rocky / AlmaLinux (fail2ban lives in the EPEL repo):
sudo dnf install epel-release
sudo dnf install fail2ban fail2ban-firewalld
sudo systemctl enable --now fail2ban
Configuring the sshd jail
Don't edit /etc/fail2ban/jail.conf directly — it gets overwritten on upgrade. Instead, create /etc/fail2ban/jail.local and override only what you need:
[DEFAULT]
# Whitelist: localhost and your fixed office IP, never banned
ignoreip = 127.0.0.1/8 ::1 203.0.113.10
bantime = 1h
findtime = 10m
maxretry = 5
[sshd]
enabled = true
port = ssh
maxretry = 3
bantime = 1h
The three knobs that matter:
- maxretry — failures allowed within the window before a ban kicks in.
- findtime — the sliding window over which failures are counted (e.g. 10m).
- bantime — how long the ban lasts. Accepts 1h, 1d; set -1 for a permanent ban.
> Note: on newer systems SSH logs only to the systemd journal, not to /var/log/auth.log. If bans never trigger, add backend = systemd to the [sshd] block. CentOS/Rocky usually needs it too.
Apply your changes with a restart:
sudo systemctl restart fail2ban
Checking status and unbanning
Inspect the overall daemon and a specific jail:
sudo fail2ban-client status
sudo fail2ban-client status sshd
The output shows the running failure count and the Banned IP list.
Locked yourself or a colleague out? Unban manually:
sudo fail2ban-client set sshd unbanip 203.0.113.55
You can also proactively ban a known-bad IP:
sudo fail2ban-client set sshd banip 198.51.100.7
Extending to nginx
The same idea protects web services. fail2ban ships with nginx filters — just enable the matching jail in jail.local and point it at your logs:
[nginx-http-auth]
enabled = true
logpath = /var/log/nginx/error.log
[nginx-botsearch]
enabled = true
logpath = /var/log/nginx/access.log
maxretry = 2
nginx-http-auth stops brute force against HTTP Basic Auth, while nginx-botsearch bans bots probing for sensitive paths. You can also write custom filters for WordPress logins, SMTP, recursive DNS, and more.
Summary
fail2ban is a "install it and benefit" security tool: it reads logs, spots failures, bans automatically, and releases automatically. There are really only three steps — install it with apt/dnf, tune maxretry/findtime/bantime for sshd in jail.local, and watch it work with fail2ban-client status sshd. Remember to add your own fixed IP to ignoreip so you never lock yourself out, then extend the same pattern to nginx and beyond. It's no substitute for key-based logins, disabling root SSH, or a firewall, but it slashes brute-force noise dramatically — the first automated line of defense worth a few minutes on any VPS.