Fail2ban Setup: Protect SSH on Linux from Brute-Force Attacks Step by Step
Fail2ban watches your login logs and blocks IP addresses that fail too many times. Install it (sudo apt install fail2ban on Ubuntu, from EPEL or the distro repo on Amazon Linux/RHEL), create /etc/fail2ban/jail.local with an enabled [sshd] jail (for example 5 failures in 10 minutes = 1-hour ban), add your own IP to ignoreip, and start it. Check bans with sudo fail2ban-client status sshd. It is an extra layer: SSH keys and a restricted port 22 come first.
Quick answer: /etc/fail2ban/jail.local
[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 203.0.113.10
bantime = 1h
findtime = 10m
maxretry = 5
[sshd]
enabled = true
backend = systemd ; Amazon Linux 2023 / journal-only systems
sudo systemctl enable --now fail2ban
sudo fail2ban-client status sshd
A live example first
We opened an Ubuntu server's SSH port to the internet for a training lab. Within an hour the log was full:
sudo grep "Failed password" /var/log/auth.log | tail -n 3
Oct 16 21:02:11 lab sshd[2211]: Failed password for root from 198.51.100.23 port 51422 ssh2
Oct 16 21:02:14 lab sshd[2213]: Failed password for invalid user admin from 198.51.100.23 port 51988 ssh2
Oct 16 21:02:17 lab sshd[2215]: Failed password for invalid user oracle from 198.51.100.23 port 52101 ssh2
- Install and configure:
sudo apt update && sudo apt install -y fail2ban
sudo nano /etc/fail2ban/jail.local # paste the quick-answer config (no backend line needed on Ubuntu)
sudo systemctl enable --now fail2ban
- Ten minutes later:
sudo fail2ban-client status sshd
Status for the jail: sshd
|- Filter
| |- Currently failed: 3
| |- Total failed: 41
`- Actions
|- Currently banned: 4
|- Total banned: 4
`- Banned IP list: 198.51.100.23 192.0.2.77 203.0.113.91 198.51.100.140
- Then we did the real fix: switched to key-only login and limited port 22 in the cloud firewall to our office IP. Friends, fail2ban cut the noise, but the keys closed the door.
How does fail2ban work?
fail2ban counts failed logins per IP and bans repeat guessers in the firewall for a set time.
Each jail joins a filter (a pattern that finds failed logins in a log) with an action (usually a firewall ban through nftables, iptables or firewalld). When one IP matches maxretry times within findtime, it is banned for bantime, then automatically unbanned.
How do I set it up step by step?
- Install fail2ban for your distribution (table below).
- Create
/etc/fail2ban/jail.local. Do not editjail.conf. - Put your own fixed IPs in
ignoreip. - Enable the
[sshd]jail; setbackend = systemdif there is no auth log file. sudo systemctl enable --now fail2ban.- Check
sudo fail2ban-client status sshd. - Test from another IP with wrong passwords (in a lab), and confirm it gets banned.
- Unban with
sudo fail2ban-client set sshd unbanip IP.
Install by distribution
| Distribution | Install | Log source |
|---|---|---|
| Ubuntu / Debian | sudo apt install fail2ban |
/var/log/auth.log (or journal on newer Debian, set backend = systemd) |
| Amazon Linux 2023 | sudo dnf install fail2ban if available in your repo, else from source/pip per docs |
systemd journal (backend = systemd) |
| Amazon Linux 2 | sudo amazon-linux-extras install epel then sudo yum install fail2ban |
/var/log/secure |
| RHEL / Rocky / Alma | sudo dnf install epel-release && sudo dnf install fail2ban |
/var/log/secure or journal |
On RHEL-family systems the ban action usually uses firewalld (banaction = firewallcmd-rich-rules).
Protect more than SSH
| Service | Jail | Catches |
|---|---|---|
| nginx basic auth | [nginx-http-auth] |
Failed password prompts |
| nginx bad bots | [nginx-botsearch] |
Scans for /wp-login.php, /phpmyadmin on non-WordPress sites |
| WordPress | plugin log + custom filter | Login guessing |
| Postfix / Dovecot | [postfix], [dovecot] |
Mail login guessing |
| Repeat offenders | [recidive] |
IPs banned many times; long ban |
[nginx-http-auth]
enabled = true
port = http,https
logpath = /var/log/nginx/error.log
The right order of SSH defences
| Defence | Effect |
|---|---|
1. Key-only login (PasswordAuthentication no) |
Password guessing becomes useless |
| 2. Firewall / security group: port 22 from your IP only | Attackers cannot even connect |
| 3. AWS Systems Manager Session Manager | No open port 22 at all |
4. PermitRootLogin no |
Removes the most guessed username |
| 5. fail2ban | Bans what still gets through, cuts log noise |
Real example
In 2016, the Mirai botnet infected hundreds of thousands of cameras and routers simply by trying a short list of default usernames and passwords over Telnet, then used them for record-breaking DDoS attacks, including the October 2016 attack on Dyn that slowed major websites. Automated password guessing against any open login port is constant, which is why you see attempts within minutes of opening SSH.
Useful fail2ban-client commands
| Command | What it does |
|---|---|
sudo fail2ban-client status |
List all active jails |
sudo fail2ban-client status sshd |
Show failures and banned IPs for SSH |
sudo fail2ban-client set sshd banip 198.51.100.23 |
Ban an IP by hand |
sudo fail2ban-client set sshd unbanip 198.51.100.23 |
Remove a ban |
sudo fail2ban-client reload |
Load changes to jail.local |
sudo fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf |
Test whether the filter matches your log |
Troubleshooting
- Jail shows 0 failures while attacks happen: the log path or backend is wrong. On journal-only systems set
backend = systemd. - fail2ban will not start: run
sudo fail2ban-client -dto print the config and find the error line. - Bans do not block traffic: check that the ban action matches your firewall (nftables, iptables or firewalld) with
sudo nft list rulesetorsudo firewall-cmd --list-all. - Behind a load balancer: SSH is not usually behind one, but for nginx jails the log shows the load balancer IP, so log the real client IP first.
Mistakes to avoid
- Banning yourself. Always set
ignoreipbefore testing. - Editing jail.conf. Updates overwrite it.
- Relying only on fail2ban with password login still on. Use keys.
Summary table
| Task | Command / setting |
|---|---|
| Install | apt install fail2ban / dnf install fail2ban |
| Config file | /etc/fail2ban/jail.local |
| SSH jail | [sshd] enabled = true |
| Journal systems | backend = systemd |
| Status | fail2ban-client status sshd |
| Unban | fail2ban-client set sshd unbanip IP |
Quick quiz: check what you learned
What does fail2ban read to find attackers?
- Log files of failed logins
- The bash history of users
- Your nginx config file
- The list of installed apps
Show answer
A. It watches logs like auth.log or the journal.
Which file should hold your fail2ban settings?
- jail.local
- jail.conf
- sshd_config
- fstab file
Show answer
A. jail.local survives package updates.
What is the best first defence for SSH?
- SSH keys, no passwords
- A longer welcome banner
- Changing the hostname
- Installing a GUI desktop
Show answer
A. Key-only login stops password guessing.
How do you list IPs banned by the sshd jail?
- fail2ban-client status sshd
- systemctl list-units all
- ss -tulpn on the host
- journalctl --disk-usage
Show answer
A. status shows counts and banned IPs.
Learn it properly
These pages go deeper, with labs:
Frequently asked questions
What does fail2ban do?
It reads log files, finds IP addresses with too many failed logins, and adds firewall rules to block them for a set time.
Is fail2ban enough to secure SSH?
No. Use SSH keys, disable password login, and limit port 22 in the firewall or security group. Fail2ban is an extra layer.
Why should I not edit jail.conf?
Package updates can overwrite it. Put your settings in jail.local or files in jail.d/.
How do I see banned IPs?
Run sudo fail2ban-client status sshd.
How do I unban an IP?
Run sudo fail2ban-client set sshd unbanip 203.0.113.5.
Does it work on Amazon Linux 2023?
Yes, but logs go to the systemd journal, so set backend = systemd in the jail.