Fail2ban is the highest-leverage security tool on a Linux VPS — 15 minutes to install and configure, and it silently blocks brute-force attacks around the clock without human intervention. On a fresh Commsult Indonesia DigitalOcean Droplet with fail2ban running, we tracked 145 failed SSH attempts and 45 auto-bans in the first week from bots scanning the internet. Without fail2ban, those 145 attempts would continue indefinitely. This guide walks through the full fail2ban setup including SSH, Nginx, and custom application jails.
Fail2ban is a daemon that monitors log files in real time using configurable regex filters. When an IP address triggers a failure threshold within a time window (e.g., 5 failed SSH logins in 10 minutes), fail2ban adds an iptables or nftables rule that drops all traffic from that IP for a configurable ban duration. When the ban expires, the rule is removed automatically. The system is stateless and survives reboots if configured correctly — banned IPs are written to a persistent database.
On Ubuntu/Debian: apt install fail2ban. Fail2ban ships with a default configuration at /etc/fail2ban/jail.conf — never edit this file directly, as package updates overwrite it. Instead, create /etc/fail2ban/jail.local with your overrides. The SSH jail is enabled by default on most systems. Key settings in jail.local: [DEFAULT] section sets global bantime (1h recommended), findtime (10m), maxretry (3), and backend (auto). Enable the sshd jail explicitly in the [sshd] section.
A jail is a combination of a log file to monitor, a filter (regex patterns to match failures), and an action (what to do when the threshold is hit). Fail2ban ships with pre-built jails for SSH, Nginx, Apache, Postfix, Dovecot, and dozens of other services. Each jail has a corresponding filter file in /etc/fail2ban/filter.d/ containing the regex patterns. You can test a filter against a log file with fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf to see how many matches it finds.
┌─────────────────────────────────────────────────────┐
│ HOW FAIL2BAN WORKS │
└─────────────────────────────────────────────────────┘
Bot attempts SSH login
│
▼
/var/log/auth.log ──► fail2ban monitors in real-time
│
▼
Filter regex matches ──► Failure count for IP
"Failed password for..."
│
maxretry (3) exceeded?
│
YES │ NO
▼
iptables DROP rule added for IP
│
▼
bantime expires (1h) ──► Rule auto-removed
│
▼
Persistent offenders get escalating bans:
1h → 2h → 4h → 1 day → 1 weekFrom my experience running fail2ban on Commsult Indonesia production servers in Jakarta, increase bantime progressively for repeat offenders using the bantime.increment and bantime.multiplier settings (available in fail2ban 0.11+). Set bantime.increment = true and bantime.maxtime = 1w. This means a first offense gets a 1-hour ban, second offense 2 hours, eventually escalating to a week-long ban for persistent attackers — without permanently blocking IPs that might be shared infrastructure.
Beyond SSH, protect your Nginx server with fail2ban jails for HTTP auth failures, WordPress brute-force, and request rate anomalies. The nginx-http-auth jail bans IPs that fail HTTP basic authentication. The nginx-badbots jail bans known malicious user agents. The nginx-req-limit jail (custom) bans IPs that trigger Nginx rate limiting too frequently. For each jail, enable it in jail.local with the correct logpath pointing to your Nginx error log (/var/log/nginx/error.log).
For NestJS APIs with custom authentication, create a custom fail2ban jail that monitors your application log for failed login events. Define a filter that matches your log format, set it to trigger after 5 failures in 5 minutes, and ban for 1 hour. This protects your API login endpoints from credential stuffing attacks that bypass Nginx-level rate limiting by spreading attempts over time.
# /etc/fail2ban/jail.local
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 3
ignoreip = 127.0.0.1/8 YOUR_HOME_IP YOUR_OFFICE_IP
# Progressive banning
bantime.increment = true
bantime.multiplier = 2
bantime.maxtime = 1w
[sshd]
enabled = true
port = 2222
logpath = /var/log/auth.log
maxretry = 3
[nginx-http-auth]
enabled = true
logpath = /var/log/nginx/error.log
[nginx-req-limit]
enabled = true
filter = nginx-req-limit
logpath = /var/log/nginx/error.log
maxretry = 10
# Check status and manage bans
fail2ban-client status sshd
fail2ban-client set sshd unbanip 1.2.3.4
fail2ban-client set sshd banip 5.6.7.8Check fail2ban status: fail2ban-client status (lists all active jails), fail2ban-client status sshd (shows current banned IPs and total bans for the SSH jail). To manually unban an IP: fail2ban-client set sshd unbanip 1.2.3.4. To manually ban an IP: fail2ban-client set sshd banip 1.2.3.4. Check the fail2ban log at /var/log/fail2ban.log for ban/unban events. Export ban statistics to Prometheus via fail2ban-exporter for Grafana dashboards showing ban trends over time.
I have banned my own home IP from a production server twice — once while testing a new jail with an aggressive maxretry setting, once while a script made repeated failed API calls. Fail2ban bans are applied immediately and silently. Always add your home IP, office IP, and any CI/CD server IPs to the ignoreip setting in jail.local (space-separated list) before enabling aggressive jails. On DigitalOcean, the Cloud Firewall whitelist is your safety net — even if fail2ban bans you, you can access the Droplet console.
If your site is behind Cloudflare, ban events at the fail2ban level are ineffective because all traffic comes from Cloudflare IPs. Instead, configure fail2ban to use the Cloudflare API action — when a ban triggers, fail2ban calls the Cloudflare API to block the real client IP at the edge, before it reaches your server. This requires a Cloudflare API token and a custom fail2ban action file. The result: bans are enforced globally at Cloudflare's edge, not just on your VPS.
Fail2ban is one layer in a defense-in-depth strategy, not a complete security solution. Attackers can rotate IPs across hundreds of cloud providers and residential proxies, defeating single-IP bans. Combine fail2ban with: SSH key authentication only (no passwords to brute-force), a cloud firewall restricting SSH to known IPs, regular security updates (unattended-upgrades), and Nginx rate limiting for API endpoints. Fail2ban catches what slips past other defenses and adds the automated response that manual monitoring cannot provide.