VPS Security · Linux
Fail2ban Guide 2026: Protect Your VPS from SSH Brute Force Attacks
Table of Contents
Every VPS on the internet faces constant SSH brute-force attacks — bots scanning port 22 around the clock, trying thousands of username/password combinations. Fail2ban is the industry-standard tool that detects and automatically blocks these attacks by banning offending IP addresses at the firewall level.
What is Fail2ban?
Fail2ban is an open-source, Python-based daemon that monitors log files for suspicious patterns — such as repeated failed login attempts — and responds by temporarily blocking the offending IP address using the system firewall (iptables, nftables, ufw, or firewalld).
The current stable release is v1.1.x, requiring Python ≥ 3.5. It ships with ready-made filters for dozens of services including SSH, Nginx, Apache, Postfix, and more. Fail2ban is licensed under GPLv2 and is completely free.
- 100% free and open-source (GPLv2)
- IPv6 support since v0.10
- Works with iptables, nftables, ufw, pf (BSD), and firewalld
- Pre-built filters for SSH, web servers, mail servers, FTP, and more
- Lightweight — typically uses only 10-20 MB RAM
- Highly configurable via plain-text .conf files
Verified Facts (GitHub fail2ban/fail2ban, October 2026)
Fail2ban v1.1.x · Python ≥ 3.5 required · GPLv2 license · IPv6 support since v0.10 · Compatible with iptables / nftables / ufw / firewalld
How Fail2ban Works
Fail2ban operates in three stages:
- Log monitoring — Continuously reads log files such as
/var/log/auth.logor/var/log/nginx/access.log - Pattern matching — Uses regular expressions (filters) to detect suspicious lines, e.g., "Failed password" or "Invalid user"
- Action execution — When an IP exceeds
maxretryfailures withinfindtimeseconds, Fail2ban bans it forbantime
Fail2ban creates dedicated firewall chains (e.g., f2b-sshd) that don't interfere with your existing firewall rules.
# Example: SSH brute force detected in /var/log/auth.log
Oct 30 01:23:45 server sshd[1234]: Failed password for root from 45.142.212.10 port 54321 ssh2
Oct 30 01:23:47 server sshd[1234]: Failed password for root from 45.142.212.10 port 54321 ssh2
Oct 30 01:23:49 server sshd[1234]: Failed password for root from 45.142.212.10 port 54321 ssh2
# → maxretry=3 reached within findtime → IP is banned
Installing Fail2ban on Ubuntu/Debian
# Update package list and install
sudo apt update
sudo apt install fail2ban -y
# Verify the service is running
sudo systemctl status fail2ban
# Enable auto-start on reboot
sudo systemctl enable fail2ban
Fail2ban starts immediately after installation with a default configuration protecting SSH. However, you should customize it further for your environment.
For CentOS/AlmaLinux/Rocky Linux
sudo dnf install epel-release -y
sudo dnf install fail2ban -y
sudo systemctl enable --now fail2ban
Configuring jail.local
Important: Never modify /etc/fail2ban/jail.conf directly — it gets overwritten during updates. Instead, create jail.local which overrides jail.conf:
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo nano /etc/fail2ban/jail.local
Essential [DEFAULT] settings
[DEFAULT]
# Ban duration — 1h = ban for 1 hour
bantime = 1h
# Detection window — find maxretry failures within this time
findtime = 10m
# Number of failures before ban
maxretry = 5
# IPs that are never banned — ALWAYS add your own IP!
ignoreip = 127.0.0.1/8 ::1 YOUR_HOME_IP/32
# Log backend (auto-detects the best option)
backend = auto
Need a VPS to Run Fail2ban On?
We recommend VPS providers with full root access, Ubuntu/Debian support, and data centers in Southeast Asia.
DigitalOcean Droplets Ruk-Com Cloud VPSProtecting SSH — The Most Critical Jail
Add the following to your jail.local:
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 2h
findtime = 5m
If you've changed your SSH port (e.g., to 2222), set port = 2222. After editing, reload:
sudo systemctl reload fail2ban
Protecting Nginx, Apache, and WordPress
Nginx
[nginx-http-auth]
enabled = true
port = http,https
filter = nginx-http-auth
logpath = /var/log/nginx/error.log
maxretry = 3
[nginx-botsearch]
enabled = true
port = http,https
filter = nginx-botsearch
logpath = /var/log/nginx/access.log
maxretry = 2
Apache
[apache-auth]
enabled = true
port = http,https
filter = apache-auth
logpath = /var/log/apache2/error.log
maxretry = 5
[apache-badbots]
enabled = true
port = http,https
filter = apache-badbots
logpath = /var/log/apache2/access.log
maxretry = 2
WordPress Login Protection
Create a custom filter file:
sudo nano /etc/fail2ban/filter.d/wordpress.conf
[Definition]
failregex = ^<HOST>.*POST.*(wp-login\.php|xmlrpc\.php).* 200
ignoreregex =
Add to jail.local:
[wordpress]
enabled = true
port = http,https
filter = wordpress
logpath = /var/log/nginx/access.log
maxretry = 5
findtime = 5m
bantime = 24h
Essential Fail2ban Commands
| Command | Description |
|---|---|
sudo fail2ban-client status | List all active jails |
sudo fail2ban-client status sshd | Show sshd jail status + currently banned IPs |
sudo fail2ban-client set sshd unbanip 1.2.3.4 | Unban a specific IP from sshd jail |
sudo fail2ban-client set sshd banip 1.2.3.4 | Manually ban an IP |
sudo fail2ban-client reload | Reload config without restart |
sudo tail -f /var/log/fail2ban.log | Watch Fail2ban log in real time |
sudo fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf | Test a filter against a real log file |
Advanced: Recidive Jail for Repeat Offenders
The recidive jail adds an extra layer of protection: if an IP gets banned multiple times across different jails, recidive applies a much longer ban (1 week or permanent). This stops attackers who simply wait out the regular bantime and try again.
[recidive]
enabled = true
filter = recidive
logpath = /var/log/fail2ban.log
action = iptables-allports[name=recidive]
bantime = 1w
findtime = 1d
maxretry = 5
Whitelisting Important IPs
# In [DEFAULT] section of jail.local
ignoreip = 127.0.0.1/8 ::1 203.0.113.10/32 10.0.0.0/8
Always whitelist your own IP address to prevent accidentally locking yourself out.
Fail2ban vs CrowdSec: Which Should You Use?
| Feature | Fail2ban | CrowdSec |
|---|---|---|
| Cost | Free (GPLv2) | Free (MIT) |
| Threat intelligence | Local log-based only | Crowdsourced + shared blocklists |
| Setup complexity | Simple, quick | More complex (API, bouncers) |
| False positives | Occasional | Fewer (community-verified) |
| RAM usage | ~10-20 MB | Slightly more |
| Documentation | Extensive (10+ years) | Growing (2020+) |
| Best for | Single VPS, simplicity | Multi-server, enterprise |
Recommendation: For a single VPS or small business server, start with Fail2ban. It's simpler, better documented, and widely understood by sysadmins worldwide. Consider CrowdSec if you manage multiple servers and want to share threat intelligence between them.
Further Reading
Frequently Asked Questions
Q: Does Fail2ban work with UFW?
A: Yes. Set banaction = ufw in jail.local, or use action = %(action_)s which auto-detects the firewall backend. Fail2ban supports iptables, nftables, and ufw.
Q: How much RAM does Fail2ban use?
A: Typically 10-20 MB RAM, depending on the number of active jails. It works comfortably on VPS instances with as little as 512 MB RAM.
Q: Does Fail2ban persist bans after a restart?
A: By default in recent versions, Fail2ban uses a SQLite database (/var/lib/fail2ban/fail2ban.sqlite3) to persist bans across restarts. Older versions may lose the ban list on restart.
Q: What should I set maxretry to for SSH?
A: We recommend maxretry = 3 for SSH. A legitimate user rarely mistype their password more than 3 times. Always whitelist your own IP to avoid accidental lockouts.
Q: I accidentally banned my own IP — how do I fix it?
A: Use the VPS provider's emergency console (KVM/IPMI access) to log in, then run: sudo fail2ban-client set sshd unbanip YOUR_IP
Q: Does Fail2ban support IPv6?
A: Yes, since version 0.10, Fail2ban has full IPv6 support. Ensure your firewall backend (iptables with ip6tables or nftables) also supports IPv6.