Fail2ban Configuration Tutorial: Runbook

by Liam Foster
Fail2ban Configuration Tutorial: Runbook

Fail2ban watches log files, detects repeated failed login attempts, and bans offending IPs by modifying firewall rules. It's your first line of defense against brute-force SSH attacks. This tutorial walks you through the model, the configuration, and the gotchas.

How Fail2ban Works

Fail2ban operates in three layers:

  1. Filter: regex pattern that extracts failure events from logs (e.g., "Failed password for user X").
  2. Action: command executed when threshold is crossed (usually an iptables or ufw rule to drop traffic).
  3. Jail: the binding of a filter to an action, with timing rules (how many failures in how long, ban duration).

When fail2ban detects N failures in M seconds, it triggers the action and bans the source IP for T seconds. After the ban expires, the IP is unblocked automatically.

Installation and Basic Setup

On Debian/Ubuntu:

sudo apt update && sudo apt install fail2ban
sudo systemctl enable fail2ban
sudo systemctl start fail2ban

Verify it's running:

sudo fail2ban-client status

Fail2ban ships with a default jail configuration at /etc/fail2ban/jail.conf. Never edit this file directly. Create an override at /etc/fail2ban/jail.local instead; fail2ban merges .local on top of .conf.

Configuring the SSH Jail

Create /etc/fail2ban/jail.local:

[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 5
destemail = ops@example.com
sender = fail2ban@example.com

[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s
maxretry = 3
bantime = 7200

What each setting does:

  • bantime: seconds the IP stays blocked (3600 = 1 hour).
  • findtime: time window in seconds (600 = 10 minutes). Failures must occur within this window to count.
  • maxretry: number of failures before ban is triggered.
  • port: service port (ssh = 22, or specify a custom port).
  • logpath: log file to monitor (sshd_log is a variable pointing to /var/log/auth.log on Debian).

The [sshd] section overrides [DEFAULT] for SSH only. Here, we set maxretry = 3 (stricter than the default 5) and bantime = 7200 (2 hours for SSH specifically).

Custom Filters and Actions

For non-standard services, you'll write a filter. Create /etc/fail2ban/filter.d/myapp.conf:

[Definition]
failregex = ^\[<TIMESTAMP>\] Authentication failed for user <USER> from <HOST>$
datepattern = %%Y-%%m-%%d %%H:%%M:%%S

The regex must match the log line. Use <HOST> and <USER> as placeholders; fail2ban extracts the IP. Test the regex before deploying:

fail2ban-regex /var/log/myapp.log /etc/fail2ban/filter.d/myapp.conf

Then bind it in jail.local:

[myapp]
enabled = true
filter = myapp
port = 8080
logpath = /var/log/myapp.log
maxretry = 5
bantime = 1800

Reload and check:

sudo fail2ban-client reload
sudo fail2ban-client status myapp

Monitoring and Debugging

View active bans:

sudo fail2ban-client status sshd

Output shows banned IPs and recent matches. To unban manually:

sudo fail2ban-client set sshd unbanip 192.0.2.10

Check logs for fail2ban's own activity:

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

If a jail isn't matching, enable debug mode in jail.local:

[DEFAULT]
debugging = true

Reload and check /var/log/fail2ban.log for regex mismatches.

When This Breaks

Legitimate users locked out: If your office IP gets banned, unban it immediately and increase maxretry or findtime. Consider whitelisting trusted IPs in jail.local:

[DEFAULT]
ignoreip = 127.0.0.1/8 192.0.2.0/24

Regex doesn't match: Test with fail2ban-regex first. Common mistake: forgetting to escape dots in IP regexes, or using \b word boundaries that don't work with dots.

Ban doesn't persist across reboot: Fail2ban bans are in-memory. If you need persistent blocking, use a separate firewall rule or database-backed action. For most cases, in-memory is fine—most brute-forcers don't retry after a reboot.

High CPU from regex evaluation: If you're monitoring a busy log file with a slow regex, fail2ban will lag. Optimize the regex to fail fast (anchor to the start of the line, avoid backtracking). If you're also working on leaner infrastructure, the comparison on devbox.id covers how to minimize Docker image size so builds stay fast alongside your security tooling.

Action doesn't fire: Verify the action exists. Default actions live in /etc/fail2ban/action.d/. If using a custom action, test it manually:

sudo /etc/fail2ban/action.d/myaction.conf start

Trade-offs

  • Responsiveness vs. false positives: Lower maxretry bans faster but risks locking out users with typos. Start at 5, tune down only if you see real attacks.
  • Ban duration: Longer bans (24+ hours) are safer for production but frustrate legitimate users. 1–2 hours is a reasonable middle ground.
  • Regex complexity: Precise regexes catch only real failures but are harder to maintain. Loose regexes are forgiving but may miss edge cases.
  • Multiple jails: Each jail adds overhead. Only enable what you need.

Deployment Checklist

  1. Install fail2ban and verify it starts on boot.
  2. Create /etc/fail2ban/jail.local with [DEFAULT] and [sshd] sections.
  3. Set ignoreip to whitelist your office/monitoring IPs.
  4. Test the SSH jail: sudo fail2ban-client status sshd.
  5. Trigger a test ban from a test IP, verify unban works.
  6. Check /var/log/fail2ban.log for errors.
  7. Monitor for 24 hours; adjust maxretry or findtime if needed.
  8. Document your settings in a runbook for the next on-call engineer.

One-line Takeaway

Fail2ban is a stateful rate limiter for log-based attacks: configure a filter, set thresholds, and let it auto-ban and auto-unban IPs. Start with SSH, test thoroughly, and whitelist your own infrastructure.