Fail2ban cuts brute-force and credential-stuffing noise on a production server by banning IP addresses that repeatedly fail SSH logins. It is one layer in a defence-in-depth stack, not a replacement for key-only authentication or a firewall.
Fail2ban reads log files (or the systemd journal), matches failure patterns, and calls a firewall to block the source address for a set time.
/etc/fail2ban/filter.d/) extracts the offending IP from each failure.in /etc/fail2ban/action.d/) adds a firewall rule through nftables, iptables or firewalld.| Threat | Does Fail2ban help? | Primary control | ——– | ——————- | —————-| Password guessing from one IP | Yes, bans after a few failures | Disable password logins | Slow, low-rate attacks | Partly, with a long findtime and the recidive jail | Key-only authentication | Distributed botnets using many IPs | Limited, each IP stays under the threshold | Key-only authentication, network allow-listing | Stolen or leaked SSH private key | No | MFA, key passphrases, short-lived certificates | Unpatched OpenSSH or kernel vulnerability | No | Patching, minimal attack surface | Log flooding and CPU load from scanners | Yes | Fail2ban plus a firewall rate limit |
This guide targets Linux servers running OpenSSH, covering Debian/Ubuntu and RHEL-family distributions (RHEL, Rocky, AlmaLinux, Fedora). It installs Fail2ban, tunes the SSH jail, hardens sshd itself, and lists wider baseline controls for a production host.
Commands assume root or sudo access.
A mistake in SSH or firewall configuration can lock you out of a remote server, so set up a recovery path before you change anything.
[ ] A non-root user with sudo rights and a working SSH public key.
[ ] A second, already-open SSH session kept alive while you test changes.
[ ] Out-of-band access: cloud provider console, Intelligent Platform Management Interface (IPMI), or a rescue mode.
[ ] Your own admin IP addresses or VPN range noted for the ignoreip allow-list.
[ ] A recent backup or snapshot of the server.
[ ] Accurate time via chrony or systemd-timesyncd, since ban windows depend on correct timestamps.
[ ] Firewall in place (nftables, ufw or firewalld) with the SSH port allowed.
cat /etc/os-release
ssh -V
ls -l /var/log/auth.log /var/log/secure 2>/dev/null
sudo journalctl -u ssh -u sshd --since "10 min ago" -n 10 --no-pager
sudo nft list ruleset | head
sudo iptables -V
Important: Recent Debian releases (12 and later) no longer ship rsyslog by default, so /var/log/auth.log may not exist. Use the systemd backend in that case (see the SSH jail section).
Install Fail2ban from your distribution’s repositories, enable the service, and confirm the default sshd jail works before customising it.
sudo apt update
sudo apt install -y fail2ban python3-systemd
sudo dnf install -y epel-release
sudo dnf install -y fail2ban fail2ban-firewalld
sudo dnf install -y fail2ban
sudo systemctl enable --now fail2ban
sudo systemctl status fail2ban --no-pager
| Path | Purpose | Edit it? | | —- | ——- | ——– | | /etc/fail2ban/jail.conf | Packaged defaults for all jails | No, upgrades overwrite it | | /etc/fail2ban/jail.local | Your overrides | Yes | | /etc/fail2ban/jail.d/*.local | Per-jail drop-in overrides, loaded in alphabetical order | Yes | |/etc/fail2ban/filter.d/|Failure-matching regular expressions|Only to add custom filters| |/etc/fail2ban/action.d/|Ban and unban actions (nftables, firewalld, mail)| Rarely | |/var/log/fail2ban.log | Fail2ban’s own log | No |
Note: Never edit jail.conf directly. Put changes in
jail.localorjail.d/so package updates do not overwrite them.
On Debian and Ubuntu the sshd jail is enabled out of the box. On RHEL-family systems it is not, so enable it in the next section. Verify the state:
sudo fail2ban-client status
sudo fail2ban-client status sshd
A working jail reports Currently failed, Total failed, Currently banned and Banned IP list.
Create /etc/fail2ban/jail.local with conservative defaults, an allow-list for your admin IPs, and escalating bans for repeat offenders.
| Parameter | Meaning | Recommended value | | ——— | ——- | —————– | | maxretry | Failures allowed within findtime before a ban | 3 to 5 | | findtime | Window in which failures are counted | 10m | | bantime | Duration of a ban | 1h for the base ban | | bantime.increment | Lengthen the ban for repeat offenders | true | | bantime.factor | Multiplier applied on each repeat ban | 2 | | bantime.maxtime | Ceiling for escalated bans | 4w | | ignoreip | Addresses never banned | Admin IPs, VPN range, 127.0.0.1/8, ::1 | | backend | How logs are read | systemd (journal) or auto (log files) | | banaction | Firewall tool used to block an IP | nftables, iptables-multiport or firewallcmd-rich-rules | | mode (sshd filter) | Strictness of failure matching | aggressive |
Example /etc/fail2ban/jail.local.
[DEFAULT]
# Never ban these (space or comma separated).
ignoreip = 127.0.0.1/8 ::1
bantime = 1h
findtime = 10m
maxretry = 3
# Escalate bans for repeat offenders
bantime.increment = true
bantime.factor = 2
bantime.maxtime = 4w
# Firewall action that matches your host
banaction = nftables
banaction_allports = nftables[type=allports]
# Email alerts: requires a working MTA
# destemail = security@example.com
# sender = fail2ban@example.com
# action = %(action_mwl)s
[sshd]
enabled = true
port = ssh
mode = aggressive
maxretry = 3
# Debian 12+/Ubuntu with journald only:
backend = systemd
# Servers that still write /var/log/auth.log (or /var/log/secure on RHEL):
# backend = auto
# logpath = %(sshd_log)s
If you changed the SSH port, set port = 2222 (or your port number) so the ban rule covers the correct port.
Block repeat offenders: the recidive jail.
The recidive jail watches Fail2ban’s own log and bans IPs that keep getting banned, for a long time across all ports.
[recidive]
enabled = true
bantime = 1w
findtime = 1d
maxretry = 3
banaction = nftables[type=allports]
| Host uses | Set banaction to |
| ——— | —————— |
| nftables directly (Debian 11+, Ubuntu 22.04+, RHEL 9) | nftables and nftables[type=allports] |
| iptables (legacy) | iptables-multiport and iptables-allports |
| firewalld (RHEL family default) | firewallcmd-rich-rules and firewallcmd-allports |
| ufw | ufw |
Use only one firewall manager. Running ufw or firewalld alongside hand-written nftables rules can overwrite Fail2ban’s chains when the firewall reloads.
Note: add allowipv6 = auto to Fail2ban’s
/etc/fail2ban/fail2ban.confconfiguration file. Find the[Definition]section
Validate configuration syntax first.
sudo fail2ban-client -t
Apply
sudo systemctl restart fail2ban
Confirm the jails are loaded and read the effective settings.
sudo fail2ban-client status
sudo fail2ban-client status sshd
sudo fail2ban-client get sshd maxretry
sudo fail2ban-client get sshd bantime
Caution: Add your own IP to
ignoreipbefore testing. A mistyped password during testing can ban you from your own server.
Note: bantime.increment, bantime.factor and bantime.maxtime need Fail2ban 0.11 or later. Check with
fail2ban-client --version.