Background: Bots Attack the Moment Your Server Goes Online
Just spun up a fresh VPS with a public IP? Within 10 to 15 minutes, port-scanning bots from all over the internet will start knocking on your door. Through numerous real-world infrastructure audits, I’ve found that the most common vulnerability is leaving SSH and web server ports wide open without any active defense mechanism in place.
Take a look at your SSH log file (/var/log/auth.log on Ubuntu or /var/log/secure on RHEL/CentOS) or your Nginx access log (/var/log/nginx/access.log). You’ll typically see thousands of failed login attempts every single day. Attackers constantly brute-force common usernames like root, admin, and test, or probe for sensitive paths such as /.env, /wp-login.php, and /phpmyadmin.
Fail2ban was built to solve this exact problem. Its operating mechanism is straightforward:
- Log monitoring: Continuously parses system log files in real time.
- Pattern matching: Uses regex filters to catch failed authentication attempts or suspicious requests.
- IP banning: When an IP exceeds the failure threshold (
maxretry) within a specified window (findtime), Fail2ban dynamically updates firewall rules (iptables, nftables, or UFW) to block that IP for a defined duration (bantime).
Installing Fail2ban on Linux
Fail2ban is available in the official package repositories of almost all major Linux distributions. The installation takes less than a minute.
1. On Ubuntu / Debian
sudo apt update
sudo apt install fail2ban -y
2. On CentOS / RHEL / AlmaLinux / Rocky Linux
sudo dnf install epel-release -y
sudo dnf install fail2ban fail2ban-systemd -y
Once installed, enable and start the service so Fail2ban launches automatically on boot:
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
sudo systemctl status fail2ban
Configuring Fail2ban to Protect SSH and Nginx
Keep this golden rule in mind: never edit /etc/fail2ban/jail.conf directly. This file gets overwritten during package upgrades. Instead, copy its contents to jail.local to make safe, persistent customizations.
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
1. Configuring Global Defaults
Open /etc/fail2ban/jail.local in your preferred text editor:
sudo nano /etc/fail2ban/jail.local
Locate the [DEFAULT] section and configure these fundamental parameters:
[DEFAULT]
# Whitelist trusted IPs (office IP, home static IP, localhost)
ignoreip = 127.0.0.1/8 ::1 203.0.113.50
# Default ban duration (1h = 1 hour, 1d = 1 day)
bantime = 1h
# Time window for tracking cumulative failures (10 minutes)
findtime = 10m
# Maximum retry attempts before an IP gets banned
maxretry = 5
# Incremental ban time: automatically increase penalties for repeat offenders
bantime.increment = true
bantime.factor = 2
bantime.maxtime = 4w
The bantime.increment = true directive is invaluable in production. On a first offense, an IP is banned for 1 hour. If it resumes attacking right after the ban expires, the penalty doubles to 2 hours, then 4 hours, scaling up to a maximum of 4 weeks (4w). This effectively neutralizes persistent brute-force botnets.
2. Protecting SSH (sshd)
Scroll down to the [sshd] section or append the following block to your jail.local file:
[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s
backend = systemd
maxretry = 3
findtime = 15m
bantime = 24h
Note: If you run SSH on a custom port (such as 2222), update the directive to port = 2222 so Fail2ban applies firewall rules to the correct port.
3. Protecting Nginx
On Nginx web servers, three attack vectors are most prevalent: HTTP Basic Auth brute force, sensitive source file enumeration, and aggressive request flooding.
Add these specialized jails to jail.local:
# 1. Block HTTP Basic Auth brute force (password-protected admin/staging areas)
[nginx-http-auth]
enabled = true
port = http,https
logpath = /var/log/nginx/error.log
maxretry = 3
bantime = 12h
# 2. Block bots scanning for leaked files (.env, .git, wp-admin, phpmyadmin)
[nginx-botsearch]
enabled = true
port = http,https
logpath = /var/log/nginx/access.log
maxretry = 2
findtime = 10m
bantime = 48h
# 3. Block clients that exceed rate limits configured in Nginx
[nginx-limit-req]
enabled = true
port = http,https
logpath = /var/log/nginx/error.log
findtime = 5m
maxretry = 5
bantime = 1h
4. Creating a Custom Filter to Block 404 Scanning Bots
Automated vulnerability scanners often send hundreds of requests per minute hunting for backup files (like backup.zip or db.sql). We can create a custom filter to ban IPs generating an excessive volume of 404 Not Found errors.
Create the filter configuration file at /etc/fail2ban/filter.d/nginx-404-scan.conf:
[Definition]
failregex = ^<HOST> - .* "(GET|POST|HEAD).*".* 404 .*
ignoreregex =
Declare the new jail at the end of /etc/fail2ban/jail.local:
[nginx-404-scan]
enabled = true
port = http,https
filter = nginx-404-scan
logpath = /var/log/nginx/access.log
findtime = 1m
maxretry = 20
bantime = 2h
This rule is simple: if an IP triggers 20 404 errors within 1 minute, Fail2ban immediately blocks it for 2 hours.
Restart the service to load all new configurations:
sudo systemctl restart fail2ban
Monitoring and Daily Operations
The fail2ban-client CLI is your primary utility for monitoring and managing active jails.
1. Checking Overall Jail Status
sudo fail2ban-client status
Sample output when jails are running normally:
Status
|- Number of jail: 4
`- Jail list: nginx-404-scan, nginx-botsearch, nginx-http-auth, sshd
2. Viewing Banned IPs for a Specific Jail
To inspect how many bots the SSH jail has caught:
sudo fail2ban-client status sshd
Status for the jail: sshd
|- Filter
| |- Currently failed: 2
| |- Total failed: 45
| `- File list: /var/log/auth.log
`- Actions
|- Currently banned: 3
|- Total banned: 12
`- Banned IP list: 198.51.100.23 203.0.113.11 192.0.2.88
3. Troubleshooting: Unbanning Your Own IP
Entering the wrong SSH password multiple times can accidentally lock you out. If you connect via mobile data, a backup network, or your VPS provider’s web console, run the following commands to unban your IP:
# Unban a specific IP in the sshd jail
sudo fail2ban-client set sshd unbanip 203.0.113.50
# Or unban the IP across all active jails
sudo fail2ban-client unban 203.0.113.50
4. Monitoring Logs in Real Time
To watch Fail2ban detect and ban malicious IPs live:
sudo tail -f /var/log/fail2ban.log
Whenever an IP violates a rule, a clear notice is logged:
2026-10-07 10:15:22,418 fail2ban.actions [1452]: NOTICE [sshd] Ban 198.51.100.23
2026-10-07 10:20:05,112 fail2ban.actions [1452]: NOTICE [nginx-botsearch] Ban 192.0.2.14
With just 5 minutes of configuration, you’ve set up a solid automated perimeter defense. Your server can now focus CPU cycles on serving legitimate users instead of wasting resources on thousands of junk requests every day.

