Protecting My Server with Fail2ban
Table of Contents
How I set up Fail2ban on my server to stop brute force attacks, SSH hammering and Nginx abuse with the actual config I use. I was going through my Microbin logs the other day and found this: Dozens of requests, all within seconds, all from the same IP. Classic scanner behavior just probing for anything left exposed. My server had been up for maybe 20 minutes before this started. That's when I decided to set up Fail2ban properly instead of leaving it to Nginx rate limiting alone. It's simpler than it sounds. Fail2ban watches your log files and when it sees an IP doing something suspicious too many failed SSH logins, too many 429s from Nginx, too many bad auth attempts it adds an iptables rule to drop all traffic from that IP. No dashboard. No agent. No background service eating 200MB of RAM. Just a Python daemon reading log files and writing firewall rules. On my Lightsail instance it sits at around 13MB. It works through "jails" each jail watches a specific service with its own threshold and ban duration. SSH gets tighter rules than everything else because it's the most attacked. That's it for installation. The default config protects SSH out of the box but I wanted more. Create A few things worth explaining here. The This filter doesn't ship with Fail2ban by default, you have to create it: Then restart: Should show something like: Check a specific jail: This shows currently banned IPs and total failures. On a fresh server you'll see bans within hours there are bots constantly scanning the entire IPv4 space. To manually unban an IP (if you accidentally lock yourself out): Fail2ban won't save you from a real distributed DDoS if 10,000 different IPs each send one request, there's no pattern to detect. For that you need something upstream like Cloudflare sitting in front of your server. What it does well: single-IP brute force, credential stuffing from one source, aggressive scanners like the one I found in my logs. For a personal homelab server that's the realistic threat model most of the time. That Small thing. But multiply that across SSH, Nginx auth, and everything else running on the server and it adds up. The config above is what I'm running now on my Lightsail instance alongside Nginx rate limiting. Both together handle the realistic day-to-day noise without touching the server's RAM budget meaningfully. +++185.177.72.50 "GET /wp-config.php.bak HTTP/1.0" 200
185.177.72.50 "GET /env HTTP/1.0" 200
185.177.72.50 "GET /debug.log HTTP/1.0" 200
185.177.72.50 "GET /admin HTTP/1.0" 302 What Fail2ban actually does
Installation
sudo apt install fail2ban -y
sudo systemctl enable --now fail2ban My config
/etc/fail2ban/jail.local never edit jail.conf directly, package updates will overwrite it:[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
ignoreip = 127.0.0.1/8 YOUR_HOME_IP YOUR_SERVER_IP
bantime.increment = true
bantime.multiplier = 2
bantime.maxtime = 1w
[sshd]
enabled = true
maxretry = 3
bantime = 24h
[nginx-http-auth]
enabled = true
logpath = /var/log/nginx/error.log
maxretry = 3
[nginx-req-limit]
enabled = true
filter = nginx-req-limit
logpath = /var/log/nginx/error.log
maxretry = 5bantime.increment = true means repeat offenders get progressively longer bans. First ban is 1 hour, second is 2 hours, third is 4 hours, up to a week maximum. Someone who keeps coming back gets treated accordingly.ignoreip is important. Add your home IP and your server's own IP. Without this you can accidentally lock yourself out which is not a fun situation when you're 100km from the datacenter.[nginx-req-limit] jail works together with whatever rate limiting you have in Nginx. When Nginx rejects a request with a 429, it logs it as an error. Fail2ban sees enough of those from one IP and bans them at the firewall level so they stop hitting Nginx entirely. The nginx-req-limit filter
sudo nano /etc/fail2ban/filter.d/nginx-req-limit.conf[Definition]
failregex = limiting requests, excess:.* by zone .*, client: <HOST>
ignoreregex =sudo systemctl restart fail2ban Checking it works
sudo fail2ban-client statusStatus
|- Number of jail: 3
`- Jail list: nginx-http-auth, nginx-req-limit, sshdsudo fail2ban-client status sshdsudo fail2ban-client set sshd unbanip YOUR_IP What it doesn't protect against
The scanner from my logs
185.177.72.50 that was scanning my server with Fail2ban running, it would have been banned after hitting the Nginx rate limiter 5 times. Instead of dozens of requests getting logged, it would have been blocked at the firewall after the first few.