Fail2ban Setup for Self-Hosting

What Is Fail2ban?

Fail2ban monitors log files for repeated failed authentication attempts and temporarily bans the offending IP addresses. Point a server at the internet and within hours you will see thousands of automated SSH login attempts from botnets. Fail2ban detects these patterns and adds firewall rules to block the attackers.

It works with SSH, web applications, mail servers, and any service that logs authentication failures. For self-hosting, fail2ban is essential on any server with ports exposed to the internet.

Prerequisites

Installing Fail2ban

sudo apt update && sudo apt install fail2ban -y

Fail2ban starts automatically after installation. Check the status:

sudo systemctl status fail2ban

Configuration

Fail2ban’s config files:

  • /etc/fail2ban/jail.conf — default configuration (never edit this — it gets overwritten on updates)
  • /etc/fail2ban/jail.local — your overrides (create this file)
  • /etc/fail2ban/jail.d/ — additional config files (alternative to jail.local)

Create your configuration:

sudo nano /etc/fail2ban/jail.local
[DEFAULT]
# Ban duration: 1 hour
bantime = 3600

# Detection window: 10 minutes
findtime = 600

# Max failures before ban
maxretry = 5

# Ban action — use UFW if you have it, iptables otherwise
banaction = ufw
# banaction = iptables-multiport  # Use this if not using UFW

# Email notifications (optional — requires mail setup)
# destemail = [email protected]
# sender = [email protected]
# action = %(action_mwl)s

# Ignore local network
ignoreip = 127.0.0.1/8 ::1 192.168.1.0/24

[sshd]
enabled = true
port = 22
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600

Restart fail2ban to apply:

sudo systemctl restart fail2ban

Key Settings Explained

SettingWhat It DoesRecommended Value
bantimeHow long an IP stays banned3600 (1 hour) for SSH, longer for repeated offenders
findtimeTime window for counting failures600 (10 minutes)
maxretryFailures allowed before ban3 for SSH, 5 for web apps
ignoreipIPs that are never bannedYour local subnet + localhost
banactionFirewall backend to useufw if using UFW, iptables-multiport otherwise

Protecting SSH

The SSH jail is the most important. Most bots target SSH on port 22 with dictionary attacks.

[sshd]
enabled = true
port = 22
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600
findtime = 600

Combined with SSH key authentication (which disables password login), this makes SSH extremely secure. The bots still get banned quickly, reducing log noise and server load.

Aggressive Mode for Repeat Offenders

Use recidive jail to escalate bans for IPs that get banned repeatedly:

[recidive]
enabled = true
bantime = 604800    # 1 week
findtime = 86400    # 1 day
maxretry = 3        # Banned 3 times in a day = week-long ban
logpath = /var/log/fail2ban.log

Protecting Web Applications

If you expose web applications directly (without Cloudflare proxy), add jails for common attacks:

Nginx (behind your reverse proxy)

[nginx-http-auth]
enabled = true
port = http,https
logpath = /var/log/nginx/error.log
maxretry = 5

[nginx-botsearch]
enabled = true
port = http,https
logpath = /var/log/nginx/access.log
maxretry = 2

Vaultwarden (Password Manager)

Vaultwarden logs failed login attempts. Create a custom filter:

sudo nano /etc/fail2ban/filter.d/vaultwarden.conf
[Definition]
failregex = ^.*Username or password is incorrect\. Try again\. IP: <HOST>\. Username:.*$
ignoreregex =

Add the jail:

[vaultwarden]
enabled = true
port = 80,443
filter = vaultwarden
logpath = /path/to/vaultwarden/vaultwarden.log
maxretry = 5
bantime = 3600

Note: Vaultwarden must be configured to log to a file (LOG_FILE environment variable) and to log failed attempts (LOG_LEVEL=warn).

Nextcloud

sudo nano /etc/fail2ban/filter.d/nextcloud.conf
[Definition]
failregex = ^.*Login failed: '.*' \(Remote IP: '<HOST>'\).*$
ignoreregex =
[nextcloud]
enabled = true
port = 80,443
filter = nextcloud
logpath = /path/to/nextcloud/data/nextcloud.log
maxretry = 5
bantime = 3600

Docker Considerations

Fail2ban reads log files, but Docker containers write logs to Docker’s logging driver, not to host files. You have two options:

Option 1: Map container logs to host files

services:
  myapp:
    image: myapp:1.0
    volumes:
      - ./logs:/var/log/myapp  # Map log directory to host
    restart: unless-stopped

Then point fail2ban’s logpath to the mapped host directory.

Option 2: Use Docker’s json-file logging driver with a known path

Docker stores container logs at /var/lib/docker/containers/<id>/<id>-json.log. This path is not human-friendly and changes with container recreation. Option 1 is more reliable.

Important: If your services are behind a reverse proxy, the IP addresses in application logs may all be the reverse proxy’s IP (usually 172.x.x.x). Configure your reverse proxy to pass the real client IP via X-Forwarded-For or X-Real-IP headers, and configure the application to log the real IP.

Monitoring Fail2ban

# Show status of all jails
sudo fail2ban-client status

# Show status of a specific jail (banned IPs, total bans)
sudo fail2ban-client status sshd

# Manually unban an IP
sudo fail2ban-client set sshd unbanip 203.0.113.50

# Manually ban an IP
sudo fail2ban-client set sshd banip 203.0.113.50

# Check fail2ban log
sudo tail -f /var/log/fail2ban.log

Example output of fail2ban-client status sshd:

Status for the jail: sshd
|- Filter
|  |- Currently failed: 2
|  |- Total failed:     847
|  `- File list:        /var/log/auth.log
`- Actions
   |- Currently banned: 15
   |- Total banned:     312
   `- Banned IP list:   203.0.113.1 198.51.100.5 ...

Common Mistakes

Banning yourself

If you mistype your SSH password 3 times, you get banned. Always set ignoreip to include your local subnet (192.168.1.0/24) and any static IP you connect from. If you do get banned, access the server via your VPS provider’s web console and run sudo fail2ban-client set sshd unbanip YOUR_IP.

Not checking the logpath

Fail2ban silently does nothing if the logpath does not exist. Verify the log file path exists and contains the expected failure messages:

# Check if the log file exists
ls -la /var/log/auth.log

# Check if it contains SSH failures
grep "Failed password" /var/log/auth.log | tail -5

Using iptables banaction with UFW

If you use UFW, set banaction = ufw in your jail.local. Using iptables-multiport alongside UFW creates conflicting firewall rules.

Setting bantime too short

A 5-minute ban barely slows down bots. They cycle through IPs and come back immediately. Use at least 1 hour (bantime = 3600) for SSH, and enable the recidive jail for repeat offenders.

Not restarting after config changes

Fail2ban does not auto-reload configuration. After editing jail.local:

sudo systemctl restart fail2ban

Next Steps

FAQ

Do I need fail2ban if I use SSH keys and disable password login?

It is still useful. Fail2ban reduces log noise and server load from bots hammering your SSH port. Without it, bots still consume CPU and bandwidth trying (and failing) to authenticate. Fail2ban bans them after a few attempts, stopping the traffic entirely.

Does fail2ban work with Cloudflare proxy?

Not directly for web jails. Cloudflare’s proxy means all traffic comes from Cloudflare’s IP ranges, not the real client IP. Use Cloudflare’s built-in WAF and rate limiting instead. Fail2ban still works for SSH and non-proxied services.

How many IPs will fail2ban typically ban?

A server with SSH on port 22 open to the internet typically bans 50-200+ unique IPs per day. This is normal. The internet is full of bots scanning every IP for open SSH ports.

Can fail2ban permanently ban IPs?

Set bantime = -1 for permanent bans. Be careful — this grows the ban list indefinitely and can slow down firewall rule processing over time. The recidive jail with week-long bans for repeat offenders is a better approach.