How Servers Get Hammered Without You Knowing
Check your SSH auth log right now:
grep 'Failed password' /var/log/auth.log | wc -l
On a fresh VPS exposed to the public internet, that number hits the thousands within days — sometimes within hours. Botnets continuously sweep the entire IPv4 space, probing for weak passwords and default credentials. On my last new server setup, I had over 3,000 failed SSH attempts before I’d even finished configuring the firewall.
I’ve managed production Linux servers for years. Protecting SSH from brute force is one of the first things I configure on any new machine — not the last. There are several approaches, and picking the wrong one wastes time or creates a false sense of security.
Your Options for Brute Force Protection
Manual iptables / ufw Rules
You can manually block IPs using iptables or ufw. Fast and lightweight, but entirely static. You need to know which IPs to block before the attack happens — which defeats the purpose.
Port Knocking
Hide SSH behind a secret port sequence. Clever in theory, painful in practice. Forget the knock sequence while traveling and you’re locked out of your own server. It’s also awkward in team environments where multiple people need access.
Change the Default SSH Port
Moving SSH from port 22 to something like 2222 or 8022 cuts out a huge chunk of automated scans. It’s a solid supplementary measure — but not a solution. Determined scanners do full port sweeps. Treat it as a complement to real defenses, not a replacement.
Fail2Ban — Dynamic, Automatic Banning
Fail2Ban watches log files in real time. When it spots too many failed login attempts from the same IP, it adds a firewall rule to block that address for a configurable period. No manual intervention. No staying up to babysit logs. This is the approach that actually scales.
Fail2Ban: What It Does Well and Where It Falls Short
Strengths
- Automatic response — blocks attacks without you being awake or even aware.
- Flexible configuration — protect SSH, Nginx, Apache, Postfix, and dozens of other services using “jails”.
- Log-based detection — works with whatever logging your service already produces. No extra agents needed.
- Easy whitelisting — add your own IPs to an ignore list so you never lock yourself out.
- Integrates with iptables, nftables, and ufw — slots into your existing firewall without replacing it.
Real Limitations
- Not a substitute for strong authentication — use SSH keys, not passwords. Fail2Ban buys time; it doesn’t make weak credentials safe.
- Distributed attacks slip through — a botnet using 10,000 different IPs, each making one or two attempts, won’t trigger a ban. For that threat, look at CrowdSec or blocklist-based tools.
- Log rotation edge cases — if files rotate mid-scan, Fail2Ban can occasionally miss entries. Rarely an issue in practice, but worth knowing.
- IPv6 needs separate configuration — many default setups only cover IPv4. Check your config explicitly.
Recommended Setup
Here’s the configuration I use on a standard Linux server covering SSH and a web stack:
- Block after 5 failed attempts within 10 minutes.
- Ban for 1 hour on first offense, escalating with recidive jail for repeat offenders (24-hour ban).
- Whitelist your own static IP before doing anything else.
- Enable jails for SSH, Nginx bad requests, and WordPress login if applicable.
- Send email alerts for new bans — optional, but useful when reviewing logs later.
Implementation Guide
Step 1: Install Fail2Ban
On Debian/Ubuntu:
sudo apt update
sudo apt install fail2ban -y
On RHEL/CentOS/Rocky Linux:
sudo dnf install epel-release -y
sudo dnf install fail2ban -y
Step 2: Create a Local Config File
Never edit /etc/fail2ban/jail.conf directly — updates overwrite it. Create a local override instead:
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
Or start with a clean custom file:
sudo nano /etc/fail2ban/jail.d/custom.conf
Step 3: Configure the SSH Jail
Add this to your custom.conf:
[DEFAULT]
# Whitelist your own IP — replace with your actual IP
ignoreip = 127.0.0.1/8 ::1 YOUR.STATIC.IP.HERE
# Ban window: how far back to look for failed attempts
findtime = 600
# Number of failures before banning
maxretry = 5
# How long to ban (in seconds): 3600 = 1 hour
bantime = 3600
[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s
backend = %(sshd_backend)s
Step 4: Add the Recidive Jail (Repeat Offenders)
This jail is non-negotiable for me. It catches IPs that come back after the first ban expires — and hands them a 24-hour block instead of another one-hour slap on the wrist:
[recidive]
enabled = true
logpath = /var/log/fail2ban.log
banaction = iptables-allports
bantime = 86400 ; 24 hours
findtime = 86400
maxretry = 3
Step 5: Enable and Start the Service
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
Step 6: Verify It’s Working
Check the status of your jails:
sudo fail2ban-client status
sudo fail2ban-client status sshd
Sample output:
Status for the jail: sshd
|- Filter
| |- Currently failed: 3
| |- Total failed: 47
| `- File list: /var/log/auth.log
`- Actions
|- Currently banned: 2
|- Total banned: 11
`- Banned IP list: 103.45.67.89 185.220.101.12
Step 7: Manually Unban an IP (If Needed)
Locked out a legitimate user — or yourself?
sudo fail2ban-client set sshd unbanip 103.45.67.89
Optional: Protect Nginx from Bad Requests
Running a web server? Add these jails:
[nginx-http-auth]
enabled = true
port = http,https
logpath = /var/log/nginx/error.log
[nginx-botsearch]
enabled = true
port = http,https
logpath = /var/log/nginx/access.log
maxretry = 2
Hard-Learned Tips
These aren’t theoretical warnings. Most came from actual incidents or close calls on production machines.
- Whitelist your IP first — before testing or tightening config. I’ve locked myself out of remote servers more than once. It’s not a fun experience at 2am with a client waiting.
- Combine with SSH key authentication. Disable password login in
/etc/ssh/sshd_configwithPasswordAuthentication no. At that point, Fail2Ban becomes a safety net, not the primary defense. - Watch the log in real time to see exactly what’s hitting your server:
sudo tail -f /var/log/fail2ban.log - Test your filter regex before deploying a custom jail:
sudo fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf - On high-traffic servers, bump
maxretryto 10 or reducebantime. Aggressive banning can catch legitimate users behind shared IPs — corporate NAT being the most common culprit.
Inspecting What Fail2Ban Is Actually Blocking
Want to see the exact firewall rules Fail2Ban inserted? Run:
sudo iptables -L f2b-sshd -n --line-numbers
On newer systems using nftables:
sudo nft list ruleset | grep -A5 fail2ban
Fail2Ban won’t stop every attack — nothing does. But it cuts the noise dramatically, protects password-authenticated services, and gives you a clear picture of who’s probing your server. Set it up, tune it to your environment, and it quietly does its job while you sleep.

