Fail2Ban Setup Guide: How I Blocked 8,000+ Brute Force Attempts on My Home Server



Your home server is under attack right now. You just don't know it yet.

I didn't either — until I ran a security audit and found 8,390 failed login attempts in a single week. Two IPs were permanently banned. Over 1,000 lifetime bans. All automated bots hammering SSH around the clock.

Here's exactly how I set up fail2ban to stop them — and how you can do the same in under 30 minutes.

What You'll Need



- A Linux server (Ubuntu/Debian) with SSH access
- Root or sudo privileges
- 30 minutes

Step 1: Install Fail2Ban



sudo apt update
sudo apt install fail2ban -y


Fail2ban is now running with default settings. But defaults are too lenient for real-world traffic. Let's fix that.

Step 2: Create a Local Configuration



Never edit jail.conf directly — it gets overwritten on updates. Create a local override:

sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo nano /etc/fail2ban/jail.local


Alternatively, create jail.local from scratch with only your overrides — leaner, but either approach works on Ubuntu/Debian.

Everything you change goes in jail.local. The original config stays untouched.

Step 3: Configure the SSH Jail



Find the [sshd] section and set these values:

[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
findtime = 600
bantime = 86400


What these do:
- maxretry = 3 — Ban after 3 failed attempts (default is 5 — too generous)
- findtime = 600 — Count attempts within a 10-minute window
- bantime = 86400 — Ban for 24 hours. Long enough to deter, short enough to unblock your own typos

Step 4: Add Recidive (The Repeat Offender Jail)



This is the jail most tutorials skip. Recidive bans IPs that get banned multiple times — escalating the penalty for bots that just keep trying.

[recidive]
enabled = true
logpath = /var/log/fail2ban.log
bantime = 604800     # 7 days
findtime = 86400     # Look back 24 hours
maxretry = 3         # 3 bans in 24h = 7-day ban


The two IPs from my audit? They cycled through multiple bans before recidive kicked in. Now they're on a lifetime ban list. They'll never connect again.

Step 5: Start and Verify



sudo systemctl restart fail2ban
sudo systemctl enable fail2ban


sudo fail2ban-client status sshd


You should see output like:

Status for the jail: sshd
|- Filter
|  |- Currently failed: 2
|  |- Total failed:     147
|  `- File list:        /var/log/auth.log
`- Actions
   |- Currently banned: 1
   |- Total banned:     23
   `- Banned IP list:   185.x.x.x


If you see banned IPs, it's working. If total failed is climbing, the bots are already at your door.

Step 6: Lock Down the Firewall (UFW)



Fail2ban blocks repeat offenders after they try. UFW prevents them from reaching most services in the first place.

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow ssh
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verbose


The principle is simple: every port should be DENY unless you have a specific reason to ALLOW it. Most of my rules are DENY. Bots hitting random ports never get through because those ports never open.

Step 7: Fix File Permissions



This trips people up. A config file with 644 permissions is readable by anyone on the system. A private key with 644 is an open door.

# Find overly permissive config files
sudo find /etc -type f -perm /o=r -name "*.conf" | head -20

# Fix the critical ones
sudo chmod 600 /etc/ssh/sshd_config
sudo chmod 600 ~/.ssh/authorized_keys
sudo chmod 700 ~/.ssh


Rule: config files are 600. Keys are 600. SSH directories are 700. No exceptions.

Step 8: Automate Security Audits



Fail2ban only works if you check it. I run a weekly cron job that catches what I'd otherwise miss:

# Add to crontab
0 3 * * 0 /usr/local/bin/security-audit.sh >> /var/log/security-audit.log 2>&1


Here's the audit script:

#!/bin/bash
echo "=== Security Audit: $(date) ==="
echo ""
echo "--- Fail2Ban Status ---"
sudo fail2ban-client status sshd
sudo fail2ban-client status recidive
echo ""
echo "--- UFW Status ---"
sudo ufw status verbose
echo ""
echo "--- Recent Auth Failures ---"
sudo grep "Failed password" /var/log/auth.log | tail -20
echo ""
echo "--- Banned IPs (recent) ---"
sudo zgrep "Ban" /var/log/fail2ban.log* | tail -20


This is how I caught those 8,390 attempts — not because I was watching, but because the system was watching for me.

The Numbers After Setup



After running this configuration for a month:

- 8,390 failed attempts blocked in a single audit period
- 2 lifetime bans for the most persistent attackers
- 1,019 total lifetime bans
- Zero successful breaches

The bots haven't stopped. They never will. But they're not getting in.

What I'd Do Differently



Three things I learned the hard way:

1. Start with longer ban times. I began with 10-minute bans. Attackers just waited them out. Go straight to 24 hours, with recidive escalating to a week or more.

2. Monitor from day one. I didn't set up automated audits early enough. By the time I checked, thousands of attempts had already happened. Start monitoring before you think you need to.

3. Default deny, then selectively allow. I started with most ports open and closed them one by one. Should have been the other way around. Close everything, then open only what you need.

Quick Reference



- maxretry: 3 — fail fast, don't give attackers five tries
- findtime: 600 (10 min) — catch burst attacks
- bantime: 86400 (24h) — long enough to deter, short enough to unblock mistakes
- Recidive bantime: 604800 (7d) — repeat offenders get a week off
- UFW default: DENY incoming — nothing open unless you say so

---

Defense in depth isn't about perfect security. It's about building a system that catches breaches early and keeps attackers out permanently. Set up fail2ban, lock your firewall, automate your audits, and read the logs.

The question isn't will I be attacked? It's will I know about it when it happens?

---

If you're running multi-agent infrastructure, the same defense-in-depth thinking applies to how your agents communicate and authenticate. I wrote about that in [When AI Agents Swarm: Why One Agent Is a Tool, But Many Is a System](https://blog.holm.io/post/when-ai-agents-swarm-why-one-agent-is-a-tool-but-many-is-a-system-1774690338807).