$linuxjunkies
>

Protect SSH against Brute-force with fail2ban

Install and configure fail2ban to automatically block SSH brute-force attacks using jails, custom ban times, allowlists, and the recidive jail.

BeginnerUbuntuDebianFedoraArch7 min readUpdated June 7, 2026

Before you start

  • Root or sudo access on the target server
  • OpenSSH server installed and running
  • A firewall active (nftables, firewalld, or iptables)
  • Your own public IP address known so you can add it to the allowlist

Brute-force attacks against SSH are relentless. A freshly exposed server can absorb thousands of login attempts per hour from automated scanners. fail2ban watches authentication logs and temporarily bans IPs that trip a threshold of failures. It is not a replacement for key-based auth or disabling password login, but it is a practical second line of defence that stops credential stuffing cold.

How fail2ban Works

fail2ban runs as a daemon that tails log files, matches lines against regular-expression filters, and executes actions (usually a firewall rule) when a source IP crosses a failure threshold within a time window. The unit of configuration that ties a filter to an action is called a jail. The built-in sshd jail covers the most common OpenSSH log patterns.

Modern fail2ban can use nftables, iptables, or firewalld as its backend. On current distros nftables is preferred; we will confirm the backend after installation.

Install fail2ban

Debian / Ubuntu

sudo apt update && sudo apt install fail2ban -y

Fedora / RHEL 9 / Rocky Linux

sudo dnf install epel-release -y   # RHEL and Rocky only
sudo dnf install fail2ban -y

Arch Linux

sudo pacman -S fail2ban

Enable and start the service:

sudo systemctl enable --now fail2ban

Create a Local Configuration File

Never edit /etc/fail2ban/jail.conf directly. It is overwritten on package upgrades. Instead, create a local override file:

sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

Or, more surgically, create a drop-in directory file (preferred on all distros):

sudo nano /etc/fail2ban/jail.d/sshd-local.conf

Configure the SSH Jail

Paste the following into sshd-local.conf. Adjust values to suit your environment.

[DEFAULT]
# Ban duration in seconds. 1h = 3600, 24h = 86400
bantime  = 3600
# Window in which maxretry failures trigger a ban
findtime = 600
# Number of failures before a ban
maxretry = 5
# Your trusted IPs / subnets — never ban these
ignoreip = 127.0.0.1/8 ::1 192.168.1.0/24

[sshd]
enabled  = true
port     = ssh
# On systemd-journal distros use backend = systemd
backend  = systemd
maxretry = 4
bantime  = 7200

Key options explained:

  • bantime: How long a ban lasts. Negative values mean permanent.
  • findtime: The sliding window. fail2ban counts failures inside this window.
  • maxretry: Failures inside findtime that trigger a ban. 4–6 is reasonable for SSH.
  • ignoreip: Space-separated allowlist. Always include your own IP or management subnet to avoid locking yourself out.
  • backend = systemd: Reads from the journal directly instead of /var/log/auth.log. Works on Fedora/RHEL where auth.log may not exist.

If your SSH daemon listens on a non-standard port, update the port line:

port = 2222

Choose a Firewall Backend

fail2ban auto-detects the available firewall. You can pin it explicitly in [DEFAULT]:

# nftables (preferred on modern systems)
banaction = nftables-multiport
banaction_allports = nftables-allports

# firewalld (Fedora / RHEL)
# banaction = firewallcmd-rich-rules

# Legacy fallback
# banaction = iptables-multiport

To verify which backend is actually active after reload:

sudo fail2ban-client get sshd actions

Enable the Recidive Jail

The recidive jail watches fail2ban's own log. An IP that gets banned repeatedly earns a much longer ban. Add this block to your local config:

[recidive]
enabled  = true
logpath  = /var/log/fail2ban.log
banaction = %(banaction_allports)s
bantime  = 604800   ; 1 week
findtime = 86400    ; within 24 hours
maxretry = 3        ; 3 bans in 24 h → 1-week ban

On systems where fail2ban logs to the journal rather than a file, enable file logging first:

sudo nano /etc/fail2ban/fail2ban.d/logging.conf
[Definition]
logtarget = /var/log/fail2ban.log

Reload and Verify

sudo systemctl restart fail2ban
sudo fail2ban-client status

You should see output listing active jails, including sshd and recidive. Then check the SSH jail specifically:

sudo fail2ban-client status sshd

Realistic output (yours will differ):

Status for the jail: sshd
|- Filter
|  |- Currently failed: 2
|  |- Total failed:     47
|  `- Journal matches:  _SYSTEMD_UNIT=sshd.service
`- Actions
   |- Currently banned: 1
   |- Total banned:     8
   `- Banned IP list:   203.0.113.42

Reading the Logs

fail2ban logs bans, unbans, and errors to its own log file (if configured above) and to the journal:

sudo journalctl -u fail2ban -n 50 --no-pager
sudo tail -f /var/log/fail2ban.log

Grep for ban events against a specific IP:

sudo grep '203.0.113.42' /var/log/fail2ban.log

Check raw SSH authentication failures that fail2ban is reading:

sudo journalctl -u sshd --since "1 hour ago" | grep -i 'failed\|invalid'

Managing Bans Manually

Unban an IP you accidentally blocked (swap in the real address):

sudo fail2ban-client set sshd unbanip 203.0.113.42

Manually ban an IP immediately:

sudo fail2ban-client set sshd banip 198.51.100.7

List all currently banned IPs across all jails:

sudo fail2ban-client banned

Troubleshooting

  • Jail shows 0 failures even though attacks are happening: The backend may be wrong. If you are on a system that uses the journal (Fedora, RHEL, Arch, modern Ubuntu), set backend = systemd in the [sshd] block. If you rely on /var/log/auth.log, set backend = auto or polling and confirm the file exists and is being written.
  • fail2ban-client status returns "Unable to contact server": The daemon crashed, often due to a config syntax error. Run sudo fail2ban-client -t to test configuration before restarting.
  • You banned your own IP: Access the server via an out-of-band console (cloud provider dashboard, physical access) and run the unban command above. Then add your IP to ignoreip.
  • Bans are not sticking across reboots (nftables): fail2ban re-applies bans on startup from its database (/var/lib/fail2ban/fail2ban.sqlite3). If bans vanish, check that dbpurgeage in fail2ban.conf is not set too low, and confirm the nftables service does not flush all rules before fail2ban starts. Adjust After= in the fail2ban unit file if needed.
  • Recidive jail log not found: Confirm /var/log/fail2ban.log exists and fail2ban is writing to it. The logging drop-in above fixes this on most systemd-journal-only distros.
tested on:Ubuntu 24.04Debian 12Fedora 40Rocky 9

Frequently asked questions

Will fail2ban stop all SSH brute-force attacks?
No. It significantly raises the cost of attacks but is not a complete solution. Disable password authentication and use SSH keys as the primary control; fail2ban is a complementary layer.
What is the difference between bantime, findtime, and maxretry?
findtime is the sliding window in seconds. maxretry is how many failures within that window trigger a ban. bantime is how long the resulting ban lasts.
How do I make a ban permanent?
Set bantime to a negative value, for example bantime = -1. fail2ban will insert a permanent firewall rule and never automatically remove it.
Does fail2ban work if I changed my SSH port?
Yes. Set port = 2222 (or whatever port you use) in the [sshd] jail block. fail2ban applies the ban rule to that specific port rather than the default 22.
Can fail2ban protect services other than SSH?
Yes. fail2ban ships with filters for Nginx, Apache, Postfix, and dozens of other services. Enable additional jails in your local config following the same pattern as the sshd jail.

Related guides