All Articles

Fail2ban in 20 Minutes: An Automated Bouncer for Any VPS

Every public server on the internet is being scanned right now — hundreds of probes a day, looking for a door you left open. Here's the 20-minute setup that makes the door fight back.

Point any server at the public internet and wait an hour. The logs will already show what security people call background radiation: automated scanners probing /admin, /.env, /wp-login.php, trying passwords against your SSH port, and generally checking whether anyone's home. On one small business site we manage, that noise ran to 2,200+ failed requests per day — most of it from a handful of cheap cloud instances scanning thousands of sites in rotation.

None of this means you're being targeted. It means you're visible. And the standard answer — the one that's worked for fifteen years — is fail2ban: a daemon that reads your logs, counts failures per source IP, and tells the firewall to drop the offenders. Not an antivirus, not a magic box — just a bouncer with a clipboard, standing at the door, remembering faces.

What fail2ban actually does

The model is simple: a filter defines what "misbehavior" looks like in a log (a failed SSH login, a 404 flood, a bad password against your web form). A jail says how much misbehavior in how much time earns a ban, and how long the ban lasts. The action is what gets done — usually a firewall rule that makes the offender's packets stop arriving at all.

That last part matters more than it sounds. A scanner that's banned isn't slowed down — it's invisible. No more log spam, no more noise in your traffic reports, no more CPU spent answering probes that were never going to be anything else.

The 20-minute setup

This is the whole arc on a typical Ubuntu/Debian VPS running nginx. (SSH protection is enabled by default on most distros the moment fail2ban installs — the real work is the web tier.)

1. Install it (2 minutes)

sudo apt install fail2ban
sudo systemctl enable --now fail2ban

2. Check what's already watching (1 minute)

sudo fail2ban-client status

Most distros ship an sshd jail out of the box. That's your SSH brute-force protection — already working. The gap is usually your web server.

3. Write a filter (5 minutes)

A filter is a regex that matches the bad lines in your log. For an nginx 404-flood jail, the core of it is one line:

[Definition]
failregex = ^<HOST> .*"(GET|POST|HEAD|OPTIONS).*" 404
ignoreregex = ^203.0.113.7 

<HOST> is the placeholder fail2ban swaps the IP into. The ignoreregex is where you exempt yourself — your office or home IP — so you can never lock yourself out by aggressively testing your own site.

4. Write a jail (5 minutes)

[web-404]
enabled  = true
port     = http,https
filter   = web-404
logpath  = /var/log/nginx/access.log
maxretry = 40
findtime = 600
bantime  = 3600
banaction = nftables

Translation: 40 forty-somethings within 10 minutes earns a 1-hour ban, enforced by nftables. Those numbers are a starting point — you want them strict enough to catch scanners, loose enough to never catch a real customer who fat-fingers a URL a few times.

5. Reload and verify (3 minutes)

sudo fail2ban-client reload
sudo fail2ban-client status web-404

Then test the filter against your real logs before you trust it:

sudo fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/web-404.conf

That last command is the one most tutorials skip. It replays your actual log through the filter and tells you exactly how many lines matched, how many were ignored, and — critically — how many missed. A filter that matches everything is useless; a filter that misses everything is worse, because it looks like it's working.

The trap we find most often

Here's the part that earns consultants their fee: plenty of servers have fail2ban already installed, already running, and catching nothing.

The default configuration on some distros reads logs from the systemd journal instead of log files. That's the right design — except when your nginx logs to /var/log/nginx/access.log and the jail is pointed at a journal that doesn't carry it. The jail shows "active," the status command reports zero failures, and everyone walks away warm and fuzzy while the scanner writes two thousand lines a day into a log nobody's reading. We've seen exactly this on a production box: three jails configured, months of uptime, zero bans ever.

The fix is checking one field: does the jail's File list (or journal match) actually contain your log? Run fail2ban-client status <jail> and look. If it's watching the wrong source, it's a night watchman asleep at the wrong door.

Tuning without shooting your customers

The part nobody says out loud

Fail2ban doesn't make you secure. It makes you quiet. The scanners that hit a banned IP move on to the next server in their list — your logs get clean, your threat surface shrinks, and the signal-to-noise ratio in your monitoring improves enough that a real problem actually gets noticed. That's the win: not a wall, but a filter.

"A firewall decides who gets in. A bouncer decides who stops asking."

Want This Running — and Watched — on Your Servers?

GRAYTECH.ONE hardens small-business infrastructure: log-aware fail2ban jails tuned to your actual traffic, verified with real log replay, wired into alerting so a ban or a burst is something you hear about, not something you discover in a post-mortem.

Schedule a Consultation