Configure Fail2ban to Protect SSH on Linux

Deepak Prasad
Tested on RHEL 10.2 (Coughlan) — vm1.lab.example (192.168.56.116) as SSH server; vm2.lab.example (192.168.56.220, source 192.168.56.156) for failed-login tests
Debian 12 (Bookworm) — podman container (public.ecr.aws/docker/library/debian:bookworm-slim) for apt install; jail used /var/log/auth.log after rsyslog in that container (package defaults vary)
Package fail2ban-server 1.1.0-6.el10_0 and fail2ban-firewalld 1.1.0-6.el10_0 on RHEL
fail2ban 1.0.2-2, openssh-server 1:9.2p1-2+deb12u10, and rsyslog 8.2302.0-1+deb12u1 in the Debian container
Applies to RHEL, Rocky Linux, AlmaLinux, Fedora, Ubuntu, Debian, and other Linux systems with OpenSSH
Privilege sudo or root on the SSH server for package install, jail files, and firewall rules
Scope Install Fail2ban, enable the sshd jail, tune bantime / findtime / maxretry / ignoreip, verify bans after failed SSH, and unban addresses. Does not replace full SSH hardening or every method in prevent brute force SSH attacks.
Related guides ssh command
OpenSSH authentication methods
Prevent brute force SSH attacks
firewalld cheat sheet
Test SSH connection

Fail2ban watches authentication failures and adds a temporary firewall block when one IP crosses your threshold. For SSH, enable the sshd jail when you want repeated failed login attempts to trigger temporary source-IP blocks — after basic OpenSSH hardening.

The walkthrough uses short lab values (bantime = 2m, findtime = 1m, maxretry = 3) so the ban is easy to verify. Choose production values according to your login pattern and lockout policy.


Quick answer

Step RHEL family Debian / Ubuntu
Install sudo dnf install epel-release fail2ban sudo apt install fail2ban
Enable sshd jail Create /etc/fail2ban/jail.d/00-sshd.local with enabled = true under [sshd] Same file path and syntax
Start service sudo systemctl enable --now fail2ban Same on systemd hosts
Check jail sudo fail2ban-client status sshd Same command
Unban one IP sudo fail2ban-client set sshd unbanip 203.0.113.10 Same command

After install on any distro, run sudo fail2ban-client status sshd and note whether the jail reports Journal matches or a File list — that tells you which backend is active on your host.


How Fail2ban protects SSH

Fail2ban runs fail2ban-server as a daemon. It applies filter rules to log lines (or journal entries) that match failed SSH authentication, counts failures per source IP inside findtime, and calls a ban action when maxretry is reached.

On a typical setup:

  • The filter reads sshd failure messages (Invalid user, Failed password, and similar).
  • The action inserts a firewall rule that drops or rejects traffic from the offender for bantime.
  • When bantime expires, Fail2ban removes the rule automatically.

Fail2ban complements — it does not replace — disabling password auth, using keys, and limiting PermitRootLogin.


Install Fail2ban

RHEL, Rocky Linux, AlmaLinux, and Fedora

Fail2ban ships in EPEL on RHEL-family systems. Install the repository, then the package:

bash
sudo dnf install epel-release fail2ban -y

On RHEL 10 the search shows the expected package name:

output
======================== Name Exactly Matched: fail2ban ========================
fail2ban.noarch : Daemon to ban hosts that cause multiple authentication errors

The fail2ban-firewalld subpackage is pulled in on hosts that use firewalld. It sets firewallcmd-rich-rules as the default ban action.

Confirm the version you installed:

bash
rpm -q fail2ban-server

Sample output:

output
fail2ban-server-1.1.0-6.el10_0.noarch

Debian and Ubuntu

On Debian-family systems Fail2ban is in the main archive:

bash
sudo apt update
sudo apt install fail2ban -y

In a Debian 12 container used for this refresh, the installed versions were:

output
fail2ban 1.0.2-2
openssh-server 1:9.2p1-2+deb12u10

Start Fail2ban and run sudo fail2ban-client status sshd. If the jail reports a File list pointing at /var/log/auth.log but that file does not exist, install and start rsyslog so authentication events are written there:

bash
sudo apt install rsyslog -y
sudo systemctl enable --now rsyslog

If the status output shows Journal matches instead, the jail reads the systemd journal and you do not need auth.log for SSH monitoring on that host.


Enable the sshd jail

Shipped defaults keep jails disabled until you opt in. Add a drop-in under jail.d instead of editing jail.conf (package updates can overwrite the main file).

Create /etc/fail2ban/jail.d/00-sshd.local with short lab values you can replace for production:

bash
sudo tee /etc/fail2ban/jail.d/00-sshd.local <<'EOF'
[DEFAULT]
ignoreip = 127.0.0.1/8 ::1
bantime  = 2m
findtime = 1m
maxretry = 3

[sshd]
enabled = true
EOF

The settings mean:

  • ignoreip — never ban these addresses (add your admin VPN or office subnet in production).
  • findtime — window in which failures are counted together.
  • maxretry — failures inside findtime that trigger a ban.
  • bantime — how long the firewall block lasts.

Add trusted admin networks to ignoreip before you test with deliberate failed logins. Locking yourself out of SSH is a common first-time mistake.


Start Fail2ban and verify the jail

Enable and start the service:

bash
sudo systemctl enable --now fail2ban

Check that the unit is running:

bash
systemctl is-active fail2ban

Sample output:

output
active

List active jails — you should see sshd:

bash
sudo fail2ban-client status

Sample output from the RHEL lab:

output
Status
|- Number of jail:	1
`- Jail list:	sshd

Inspect the jail backend and ban counters:

bash
sudo fail2ban-client status sshd

On RHEL 10 the jail reads the journal and uses the firewalld action:

output
Status for the jail: sshd
|- Filter
|  |- Currently failed:	0
|  |- Total failed:	0
|  `- Journal matches:	_SYSTEMD_UNIT=sshd.service + _COMM=sshd + _COMM=sshd-session
`- Actions
   |- Currently banned:	0
   |- Total banned:	0
   `- Banned IP list:	
The jail sshd has the following actions:
firewallcmd-rich-rules

In the Debian 12 container lab — after rsyslog was added — the same command showed File list: /var/log/auth.log and iptables-multiport as the ban action. On your host, trust the status sshd output rather than assuming either backend or action.


Test the ban with failed SSH logins

Run failed attempts from a second host (not from the server console logged in as root on the same IP Fail2ban sees as ignoreself). From vm2.lab.example three invalid-user tries against vm1.lab.example produced:

bash
for i in 1 2 3; do
  ssh -o BatchMode=yes -o StrictHostKeyChecking=no bogus@192.168.56.116 exit
  sleep 1
done

Each attempt returned Permission denied until the fourth try hit the ban:

output
bogus@192.168.56.116: Permission denied (publickey,password).
bogus@192.168.56.116: Permission denied (publickey,password).
bogus@192.168.56.116: Permission denied (publickey,password).
ssh: connect to host 192.168.56.116 port 22: Connection refused

After the threshold, the jail reported the client source address (192.168.56.156 from vm2):

bash
sudo fail2ban-client status sshd
output
Status for the jail: sshd
|- Filter
|  |- Currently failed:	0
|  |- Total failed:	3
|  `- Journal matches:	_SYSTEMD_UNIT=sshd.service + _COMM=sshd + _COMM=sshd-session
`- Actions
   |- Currently banned:	1
   |- Total banned:	1
   `- Banned IP list:	192.168.56.156

On RHEL with firewalld, list rich rules to see the reject rule Fail2ban added:

bash
sudo firewall-cmd --list-rich-rules

Sample output:

output
rule family="ipv4" source address="192.168.56.156" port port="ssh" protocol="tcp" reject type="icmp-port-unreachable"

The Debian 12 container test through published port 2222 banned the container gateway address 10.88.0.1 after the same three-failure pattern — confirming a file-based jail works outside the RHEL journal path used above.


Unban an address and tune ignoreip

When you lock out a lab client, remove the ban immediately:

bash
sudo fail2ban-client set sshd unbanip 192.168.56.156

Sample output:

output
1

Recheck the jail — the banned list should be empty:

bash
sudo fail2ban-client status sshd

The Banned IP list line should show no addresses. To prevent future bans for a subnet, add it to ignoreip in 00-sshd.local and reload:

bash
sudo fail2ban-client reload sshd

You can also add a single address at runtime (lost on service restart unless it is in the file):

bash
sudo fail2ban-client set sshd addignoreip 192.168.56.0/24

Where the sshd jail reads failures

Distro family Common backend
RHEL, Rocky, AlmaLinux, Fedora systemd journal
Debian, Ubuntu systemd journal or /var/log/auth.log, depending on Fail2ban and package configuration

Do not assume one path from the distro name alone. After you start Fail2ban, run:

bash
sudo fail2ban-client status sshd

If the filter section shows Journal matches, the jail reads sshd events from the journal (typical on RHEL 10 in this lab). If it shows File list: /var/log/auth.log, the jail tails that file — which is what the Debian 12 container used once rsyslog was running.

If Total failed never increments, check the backend your status output reports first, then rule out testing from an address in ignoreip or matched by ignoreself, or too few failures inside findtime.


Useful fail2ban-client commands

Task Command
Ban an IP manually sudo fail2ban-client set sshd banip 203.0.113.55
Unban an IP sudo fail2ban-client set sshd unbanip 203.0.113.55
Reload jail after editing jail.d sudo fail2ban-client reload sshd
Stop monitoring SSH temporarily sudo fail2ban-client stop sshd
Start the sshd jail again sudo fail2ban-client start sshd

Runtime banip / unbanip changes apply immediately; persistent settings belong in /etc/fail2ban/jail.d/*.local.


Troubleshooting

Symptom Likely cause Fix
Have not found any log file for sshd jail Jail expects a file backend but the log path is missing Check fail2ban-client status sshd for File list; create the log (often via rsyslog and auth.log) or switch the jail to backend = systemd if you intend to use the journal
Total failed stuck at zero on Debian or Ubuntu Wrong backend for your logging setup Compare status sshd (Journal matches vs File list) with where sshd actually logs (journalctl -u ssh vs /var/log/auth.log)
Failures logged but no ban Testing from the server own IP Use a remote client or adjust ignoreself / ignoreip for lab work
You banned yourself ignoreip missing your admin subnet Console in through out-of-band access; fail2ban-client set sshd unbanip …; add subnet to ignoreip
Jail empty after install [sshd] enabled = true missing Add 00-sshd.local as shown above; systemctl restart fail2ban
SSH shows connection refused right after failures Ban action active Expected during bantime; wait or unban; on RHEL check firewall-cmd --list-rich-rules
Total failed stuck at zero on RHEL Journal not matching filter Confirm sshd logs failures: journalctl -u sshd --since today; ensure fail2ban service is running

References


Summary

Fail2ban gives you an automatic SSH safety net: enable the sshd jail in /etc/fail2ban/jail.d/00-sshd.local, set ignoreip for networks you never want blocked, and tune bantime, findtime, and maxretry to match your lockout policy.

On RHEL-family servers the jail commonly reads sshd events from the systemd journal and, with fail2ban-firewalld installed, adds firewalld rich rules. On Debian and Ubuntu the backend may be the journal or /var/log/auth.log depending on package defaults — use fail2ban-client status sshd to see Journal matches or File list on your system. In the tested Debian 12 package the ban action was iptables-multiport; other releases or local configs may differ. Test bans from a remote client so ignoreself does not hide your failures, and keep fail2ban-client set sshd unbanip handy during the first rollout.

After a successful lab test, adjust thresholds for production and add your admin sources to ignoreip before you rely on the jail on production SSH. Pair Fail2ban with key-based login and the broader checklist in prevent brute force SSH attacks rather than treating it as the only control.


Frequently Asked Questions

1. Does Fail2ban replace hardening sshd_config?

No. Fail2ban reacts after failed login attempts appear in logs or the journal. You still want key-based auth, sensible PermitRootLogin, and rate limits in sshd_config. Fail2ban adds automatic temporary firewall blocks for repeat offenders.

2. Why is my own IP banned during testing?

Fail2ban ignores localhost by default but not your public or LAN address unless you list it in ignoreip. On the server itself, Fail2ban also skips the host primary address through ignoreself, so test from a second machine or add your admin subnet to ignoreip before deliberate failed logins.

3. Where does the sshd jail read failed logins on RHEL versus Debian?

On RHEL-family systems the sshd jail commonly uses the systemd journal backend. On Debian and Ubuntu the package may use the journal or tail /var/log/auth.log depending on Fail2ban and local configuration. Run fail2ban-client status sshd and check Journal matches or File list on your host.

4. Which firewall backend does Fail2ban use for SSH bans?

On RHEL with firewalld active, the fail2ban-firewalld package sets firewallcmd-rich-rules as the default ban action. Other distros use whatever banaction the package or jail.local defines; check fail2ban-client status sshd for the action name on your system.

5. How do I unban an IP immediately?

Run fail2ban-client set sshd unbanip followed by the address. The ban also expires automatically after bantime seconds or minutes elapse.
Omer Cakmak

Linux Administrator

Highly skilled at managing Debian, Ubuntu, CentOS, Oracle Linux, and Red Hat servers. Proficient in bash scripting, Ansible, and AWX central server management, he handles server operations on OpenStack, KVM, Proxmox, and VMware.

  • Debian
  • Ubuntu
  • Linux
  • Red Hat Enterprise Linux
  • Shell Script
  • System Administration