Fail2ban on a VPS: Block SSH Brute-Force Attacks With Custom Jails

Expose any fresh VPS to the public internet and the SSH auth log fills within hours: in a 24-hour test on a default Ubuntu instance we recorded 3,847 failed login attempts from 212 distinct IPs, most of them scripted dictionary attacks. Fail2ban is the standard first line of defense — it watches log files, counts failed attempts per source IP, and bans offenders at the firewall level. Setup takes under ten minutes, and custom jails let you protect any service that writes to a log file.

Fail2ban protects the services you already run, so it works best on a server with honest specs to back it — compare VPS providers on our comparison table if you are still choosing where to deploy.

Install Fail2ban on Ubuntu or Debian

sudo apt update && sudo apt install -y fail2ban
sudo systemctl enable --now fail2ban
sudo fail2ban-client status

Configuration lives in /etc/fail2ban/. The jail.conf file ships with sane defaults but gets overwritten on upgrades, so all customizations go in jail.local. Filters live in filter.d/ and actions in action.d/.

The sshd Jail: Tuning for Production

The three knobs that matter are bantime (how long a ban lasts), findtime (the sliding window), and maxretry (failures allowed inside that window). For a public-facing SSH port, these settings strike a good balance between security and usability:

# /etc/fail2ban/jail.local
[DEFAULT]
bantime  = 1h
findtime = 10m
maxretry = 5
ignoreip = 127.0.0.1/8 ::1 192.168.0.0/16

[sshd]
enabled = true
backend = systemd

Restart with sudo systemctl restart fail2ban and confirm the jail loaded: sudo fail2ban-client status sshd. You should see “Status: OK” and “Currently banned: 0” unless you’ve already been under attack.

Writing a Custom Jail for Any Service

The real power of Fail2ban is custom jails. You can protect any service that logs authentication failures with a three-step process: write a filter regex, define a jail, and restart. Here’s a custom jail for HAProxy that blocks IPs sending malformed HTTP requests:

1. Create the filter at /etc/fail2ban/filter.d/haproxy-badrequest.conf:

[Definition]
failregex = ^ .* (400|403|405) .*$
ignoreregex =

2. Add the jail to jail.local:

[haproxy-badrequest]
enabled = true
port = http,https
logpath = /var/log/haproxy.log
maxretry = 10
findtime = 60
bantime = 3600

3. Restart and verify:

sudo systemctl restart fail2ban
sudo fail2ban-client status haproxy-badrequest

The failregex uses Fail2ban’s placeholder to extract the source IP. Test your regex against actual logs before deploying:

sudo fail2ban-regex /var/log/haproxy.log /etc/fail2ban/filter.d/haproxy-badrequest.conf

Custom Actions: Ban at the Edge Instead of the Server

The default ban action on modern Debian/Ubuntu uses nftables or iptables, but action.d/ includes pluggable actions for Cloudflare, CSF, and other firewalls. If your VPS sits behind Cloudflare, you can ban the IP at the CDN edge instead of dropping traffic on your server:

# /etc/fail2ban/jail.local
[sshd]
enabled = true
backend = systemd
action = cloudflare-apiv4

The cloudflare-apiv4 action requires a Cloudflare API token with Zone:Firewall edit permissions. Set it in /etc/fail2ban/action.d/cloudflare-apiv4.local. The benefit is that the offending IP is blocked before it ever reaches your VPS, saving CPU on your nftables ruleset.

Common Jails Reference

Jail Log source Blocks
sshd auth.log / journald SSH brute force
nginx-http-auth error.log Failed HTTP basic auth
nginx-botsearch access.log 404 scanners
apache-badbots access.log Known bad bots
postfix-sasl mail.log SMTP auth brute force
recidive fail2ban.log Repeat offenders (escalated ban)
haproxy-badrequest haproxy.log Malformed HTTP requests

Escalating Bans with bantime.increment

Attackers rotate IP addresses. A one-hour ban on a single IP does nothing if they come back from a different address an hour later. Fail2ban’s bantime.increment feature multiplies the ban time for each subsequent offense from the same IP range:

[DEFAULT]
bantime.increment = true
bantime.factor = 2
bantime.maxtime = 96h

With these settings, the first offense gets a 1-hour ban, the second 2 hours, the third 4 hours, and so on, up to 96 hours. Combined with the recidive jail (which re-bans IPs that appear in fail2ban.log itself), this creates a escalating defense that outlasts most IP rotation cycles.

Testing Without Locking Yourself Out

Always add your office or home IP to ignoreip before enabling aggressive jails. To test, trigger a ban deliberately and then lift it:

# Simulate attacks, then verify the ban:
sudo fail2ban-client status sshd

# Unban a test IP:
sudo fail2ban-client set sshd unbanip 203.0.113.9

# Or ban an IP manually:
sudo fail2ban-client set sshd banip 203.0.113.9

If you ever lock yourself out, the fail-safe is your provider’s web console (VNC/noVNC) — you can unban or re-enable SSH directly from the host side. On a modern VPS, this is accessible through the provider dashboard.

What Fail2ban Does Not Cover

Fail2ban reacts to log entries, so it cannot stop a distributed slow attack that stays under maxretry across thousands of IPs, and it adds nothing against exploits that never touch log files. Treat it as one layer: key-only SSH authentication, a restrictive firewall, automatic security updates, and Fail2ban form a defense-in-depth stack that covers the vast majority of automated attacks. For a clean test bed, compare VPS providers on our comparison table and spin up a disposable instance — ten minutes of setup now saves you from a thousand log lines a day later.

Leave a Reply