Your Server Is Under Attack Right Now
If your server has SSH exposed to the internet — and most do — someone is trying to break into it at this very moment. Automated bots scan the entire IPv4 address space, probing port 22, trying common usernames and passwords. On a typical VPS connected to the public internet, you’ll see hundreds of failed login attempts per day. On a bad day, thousands.
We see this every day across the servers we manage. Brute-force attacks are so common that many server owners stop noticing them. But these aren’t harmless background noise: a successful brute-force login gives an attacker shell access to your server. From there, they can install malware, steal data, or use your server to attack others.
This guide walks through installing and configuring Fail2Ban — the single most effective tool for stopping brute-force attacks on a Linux server. It’s lightweight, easy to set up, and works with SSH, web servers, mail servers, and dozens of other services out of the box.
What You’ll Need
Before you start, make sure you have:
- A Linux VPS running Ubuntu 22.04+ or Debian 12+ (these instructions also work on CentOS/RHEL with minor package-manager changes)
- Root or sudo access to the server
- SSH access to the server (connected via key-based authentication — if you haven’t set this up yet, follow our SSH Hardening Guide first)
- UFW or iptables installed and active (see our UFW Firewall Guide if you’re starting from scratch)
- At least 512 MB RAM — Fail2Ban is extremely lightweight (uses ~30-50 MB RAM)
If you haven’t hardened your SSH yet, do that before configuring Fail2Ban. Fail2Ban is a defence layer, not a replacement for key-based authentication and disabling root password login. The two tools complement each other: SSH hardening stops the easy attacks, and Fail2Ban stops the persistent ones.
What Is Fail2Ban?
Fail2Ban is a log-parsing daemon that watches service logs for repeated authentication failures. When it detects too many failures from the same IP address within a time window, it creates a temporary firewall rule to block that IP. After the ban expires, the rule is automatically removed — unless the IP keeps coming back (Fail2Ban tracks recidivism too).
Think of it as a bouncer for your server. The bouncer doesn’t stop everyone from entering, but they keep a close eye on anyone who tries the wrong password more than a few times, and they’re not afraid to throw people out.
Key design features:
- No external dependencies: Everything runs locally on your server. No API calls, no third-party databases, no internet connection needed. Your log data never leaves the machine.
- 100+ pre-configured jails: Ready-to-use configurations for SSH, Apache, Nginx, Postfix, Dovecot, ProFTPD, vsftpd, and many more.
- Flexible backend support: Works with iptables, nftables, UFW, CSF, and even cloud-firewall APIs (Cloudflare, etc.).
- Minimal resource usage: ~30-50 MB RAM, negligible CPU. Runs comfortably on the smallest VPS.
Step 1: Install Fail2Ban
Installation is a single command on most Linux distributions.
Ubuntu/Debian
sudo apt update
sudo apt install fail2ban -y
CentOS/RHEL/AlmaLinux 9+
sudo dnf install epel-release -y
sudo dnf install fail2ban -y
Once installed, enable and start the service:
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
Check that it’s running:
sudo systemctl status fail2ban
Expected output should show active (running) with no errors.
Step 2: Configure Fail2Ban — the Right Way
Fail2Ban ships with a comprehensive default configuration in /etc/fail2ban/jail.conf. Never edit this file directly. Package updates overwrite it. Instead, create a local override file:
sudo mkdir -p /etc/fail2ban/jail.dnsudo nano /etc/fail2ban/jail.d/sshd.local
Or, for a minimal setup, create /etc/fail2ban/jail.local with only the settings you want to override. Fail2Ban merges the two files — any setting in jail.local takes precedence over jail.conf.
Essential Base Configuration
Open /etc/fail2ban/jail.local and add:
[sshd]nenabled = truenmaxretry = 3nbantime = 1hnfindtime = 10mn# Add your trusted office or VPN IPs here, separated by spaces.nignoreip = 127.0.0.1/8 ::1
Let’s break down the most important settings:
bantime = 1h: How long an IP stays banned. Start with 1 hour. If attacks are frequent, increase to 24h. Withbantime.incrementenabled (see below), repeat offenders automatically get longer bans.findtime = 10m: The sliding window. If 5 failures happen within 10 minutes, the ban triggers.maxretry = 5: Number of failures before a ban. For SSH, 3-5 is reasonable. For web services with login forms, 5-10 might be better to avoid blocking legitimate users.ignoreip: Your office IP, team VPN ranges, monitoring services. Always set this — you don’t want to ban yourself.bantime.increment = true: Progressive banning. First offense: 1 hour. Second: 2 hours. Third: 4 hours. Each attempt doubles untilbantime.maxtime(4 weeks). This is the single most effective setting for stopping persistent attackers.
Enable the SSH Jail
Below the DEFAULT section, add:
[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s
backend = %(sshd_backend)s
maxretry = 3
This configures Fail2Ban to watch SSH authentication logs (/var/log/auth.log on Debian/Ubuntu, /var/log/secure on RHEL/CentOS). After 3 failed SSH login attempts within 10 minutes, the offending IP gets a 1-hour ban (incrementing on repeat).
Enable the Nginx/Apache Jails (Web Servers)
If you’re running a web server, add these jails:
[nginx-http-auth]
enabled = true
logpath = /var/log/nginx/error.log
[nginx-botsearch]
enabled = true
logpath = /var/log/nginx/access.log
maxretry = 10
findtime = 60
bantime = 24h
The nginx-http-auth jail catches repeated HTTP authentication failures. The nginx-botsearch jail catches bots probing for vulnerable paths (wp-admin, phpMyAdmin, .env, etc.) — a common pre-attack reconnaissance technique.
For Apache:
[apache-auth]
enabled = true
logpath = /var/log/apache2/error.log
[apache-badbots]
enabled = true
logpath = /var/log/apache2/access.log
maxretry = 5
bantime = 48h
After making changes, reload Fail2Ban:
sudo fail2ban-client reload
Ops Note: Do Not Ban Your Own Rescue Path
Fail2Ban is useful, but a careless rule can lock out the person trying to fix the server. Before enabling aggressive bans, add trusted office or VPN IPs to ignoreip, keep console access available, and test status with fail2ban-client. Treat it as one layer beside SSH keys, updates, and a firewall, not a replacement for them.
Step 3: Verify It’s Working
Check the status of your SSH jail:
sudo fail2ban-client status sshd
Expected output:
Status for the jail: sshd
?? Filter
? ?? Currently failed: 2
? ?? Total failed: 47
? ?? File list: /var/log/auth.log
?? Actions
?? Currently banned: 3
?? Total banned: 12
?? Banned IP list: 203.0.113.5 198.51.100.22 192.0.2.88
The numbers show how many IPs are currently banned and how many have been banned in total since the jail started. If you see “Total banned: 0” after a few minutes, your server likely isn’t under active attack — or the log path is misconfigured.
Check the Fail2Ban log for more detail:
sudo tail -f /var/log/fail2ban.log
You’ll see entries like:
2026-05-20 14:23:11,789 fail2ban.actions [1234]: NOTICE [sshd] Ban 203.0.113.5
2026-05-20 15:23:12,012 fail2ban.actions [1234]: NOTICE [sshd] Unban 203.0.113.5
Testing Your Configuration Safely
Before relying on Fail2Ban in production, test that your ignoreip includes your own IP. Then trigger a test ban from a different machine (or your own, temporarily removed from ignoreip):
# From another machine, try SSH with wrong passwords:
ssh nonexistent@your-server-ip
# Enter wrong passwords 3+ times in quick succession
# On your server, verify the ban:
sudo fail2ban-client status sshd
You should see the test IP appear in the banned list. To unban it manually:
sudo fail2ban-client set sshd unbanip TEST_IP_ADDRESS
Production Hardening
1. Pair with UFW or CSF
Fail2Ban writes iptables rules by default, but it works straightforwardly with UFW. Configure Fail2Ban to use UFW as the ban action:
[DEFAULT]
banaction = ufw
This is cleaner than raw iptables rules, especially if you already manage firewall rules through UFW.
2. Enable Email Alerts
The action_mwl setting in our base configuration sends email alerts that include whois data on the attacker and the relevant log lines. Configure your destination email:
[DEFAULT]
destemail = admin@yourdomain.com
sender = fail2ban@yourdomain.com
[sshd]
enabled = true
action = %(action_mwl)s
This requires a working MTA (Postfix, Sendmail, or msmtp) on your server. Without email alerts, you won’t know attacks are happening unless you actively check the logs. For a lightweight alternative, many server owners pipe Fail2Ban logs into their monitoring stack.
3. Set Up Persistent Bans
By default, bans are in-memory — a Fail2Ban restart clears the ban list. Enable persistent bans so known attackers stay banned across restarts:
[DEFAULT]
persistent = bantime
dbpurgeage = 1d
This stores ban information in the SQLite database (/var/lib/fail2ban/fail2ban.sqlite3). The dbpurgeage setting controls how long old entries are kept before being purged.
4. Recidivism Tracking
We already enabled bantime.increment in the base config. This is the single most impactful production setting. Without it, a persistent attacker can get banned, wait out the bantime, and try again immediately. With increment enabled, each subsequent offense earns a longer ban — up to 4 weeks.
5. Monitor with fail2ban-client
Add a daily cron check:
# /etc/cron.daily/fail2ban-status
#!/bin/bash
echo "=== SSH Jail Status ==="
fail2ban-client status sshd 2>/dev/null || echo "SSH jail not enabled"
echo "=== Web Auth Jail Status ==="
fail2ban-client status nginx-http-auth 2>/dev/null || echo "nginx-http-auth not enabled"
Make it executable: sudo chmod +x /etc/cron.daily/fail2ban-status
Troubleshooting Common Issues
Jail shows “Currently banned: 0” despite known attacks
Likely cause: Wrong log path. Different Linux distributions store authentication logs in different locations.
Ubuntu/Debian uses /var/log/auth.log. RHEL/CentOS/AlmaLinux uses /var/log/secure. If you’re on a systemd-based distro using journald exclusively, set:
[sshd]
backend = systemd
logpath = %(sshd_log)s
To test your log path:
sudo fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf
This runs the regex against your log file and reports how many matches were found. If it finds 0 matches, either the path is wrong or no SSH failures were logged.
Fail2Ban keeps banning my own IP
Likely cause: ignoreip doesn’t include your network. Double-check your global ignoreip setting in jail.local. Include your office static IP, team VPN ranges, and any monitoring service IPs (like Uptime Kuma or Netdata).
To unban yourself:
sudo fail2ban-client set sshd unbanip YOUR_IP_ADDRESS
Then update ignoreip and reload:
sudo fail2ban-client reload
Docker containers are still accessible from banned IPs
Likely cause: Docker containers with -p 8080:80 go through Docker’s bridge network, which doesn’t pass through the host’s iptables INPUT chain. Fail2Ban’s default iptables-allports action blocks traffic at the INPUT chain, but Docker traffic takes a different path through the FORWARD chain.
Fix: Use the iptables-allports action with the FORWARD chain, or switch to host networking for exposed ports:
[DEFAULT]
# Use the Docker-aware ban action
banaction = iptables-allports
chain = FORWARD
Alternatively, if you’re running Docker, use UFW with Docker-aware rules (the ufw-docker project handles this cleanly) and configure Fail2Ban to ban via UFW instead.
When to Escalate
Fail2Ban handles automated brute-force attacks well, but it has limits:
- Distributed attacks from thousands of unique IPs (DDoS-style): Fail2Ban’s per-IP approach won’t help. Consider a Web Application Firewall or CDN-level DDoS protection.
- Application-layer attacks that don’t trigger auth failures: Fail2Ban only sees log entries. If an attacker finds a valid vulnerability (SQL injection, file upload bypass), Fail2Ban can’t help. Keep your applications patched.
- Compromised credentials: If someone has a valid username and password, Fail2Ban won’t stop them (they’re not triggering auth failures). Use strong passwords, key-based SSH, and consider managed security services for monitoring.
If you’re seeing sophisticated attacks that bypass Fail2Ban, CWH’s Managed Support team can review your security posture and deploy additional layers of defence.
What’s Next?
Fail2Ban is one piece of a complete server security strategy. After you have it running, here’s what to add next:
- Already read this far? Check out our VPS Essential Security Tools roundup for the full list of tools we recommend for every server.
- SSH hardening: If you haven’t already, follow our SSH hardening guide — disable password login entirely, use key-based auth, and change the default SSH port.
- Want a modern alternative? Read our Fail2Ban vs CrowdSec comparison to see if a collaborative approach suits your needs better.
- Firewall basics: Our UFW guide covers setting up a baseline firewall that Fail2Ban works alongside.
Need help setting up Fail2Ban or hardening your server? CWH Cloud VPS plans come with full root access and optional Managed Support — our team handles security patches, monitoring, and incident response so you can focus on your applications.
Sources and Command Notes
This Fail2Ban guide was refreshed in June 2026. The /etc/fail2ban/jail.d/sshd.local pattern was smoke-tested in Ubuntu 24.04 with fail2ban-client -d. Avoid copying the whole upstream jail.conf; small drop-in files are easier to review and less likely to drift during package upgrades.
Be First to Comment