← Recipes

阅读中文版本

Block SSH Brute Force with fail2ban

Match auth failures with a jail, ban repeat offenders with iptables or nftables, whitelist your own networks, and verify with a test ban.

~25 minsecuritysshoperations

Task

Deploy automatic banning of brute-forcing addresses and prove the ban triggers, expires, and never locks out legitimate operators.

When to use this

The auth log shows one address attempting thousands of passwords nightly. Manual blocking decays as soon as the attacker rotates to a new address.

Prerequisites

  • A host with sshd logs and fail2ban installed
  • nftables or iptables available for enforcement
  • Knowledge of the operator IP ranges that must never be banned

Steps

1. Configure the sshd jail with sane thresholds.

[sshd]
enabled = true
maxretry = 4
findtime = 10m
bantime = 1h
bantime.increment = true
bantime.factor = 4
ignoreip = 127.0.0.1/8 203.0.113.0/24

Expected output: Four failures in ten minutes earns an hour-long ban, doubling per repeat up the factor, while operator networks are exempt.

2. Point the jail at your actual log source.

backend = systemd
journalmatch = _SYSTEMD_UNIT=sshd.service

Expected output: The jail reads failures from journald directly, robust against log rotation and format drift.

3. Verify the jail sees failures.

fail2ban-client status sshd

Expected output: The status output shows currently failed and total failed counts climbing as attempts occur.

4. Test the ban path with a deliberate failure.

ssh -o PubkeyAuthentication=no -o PasswordAuthentication=yes wronguser@host 2>/dev/null; fail2ban-client status sshd | grep "Currently banned"

Expected output: After the threshold, the address appears in the banned list and further connections from it drop at the firewall.

5. Confirm bans expire and escalations work.

sleep 3600 && fail2ban-client status sshd | grep -E "Banned|Total banned" ; fail2ban-client get sshd banip 203.0.113.77 2>/dev/null

Expected output: The test ban lifts after its duration, and repeat offenders accumulate progressively longer sentences.

Troubleshooting

Problem

Jail shows failures but no bans

Solution

The banaction backend mismatches the firewall in use; set banaction to nftables-multiport (or the iptables equivalent) matching the host.

Problem

Legitimate operator got banned

Solution

ignoreip missed their range; add it, then unban immediately with fail2ban-client set sshd unbanip, and audit the whitelist source of truth.

Problem

Attacker rotates addresses faster than bans

Solution

Bans cannot beat a distributed botnet alone; pair with key-only auth, which removes the password attack surface entirely.

Limitations

  • fail2ban reacts to log patterns; log format changes on upgrade can blind it, so monitor its own error log.
  • Address-based banning is powerless against legitimate users behind a shared NAT the attacker also uses; whitelists must be precise.

Make repeated guessing expensive for the guesser

Brute force is cheap because failure is free: an address can try thousands of passwords at zero cost. fail2ban inverts the economics by watching auth logs and handing repeat offenders to the firewall, so their next packets never reach sshd. Incremental ban times make recidivism progressively expensive, and the ignore list keeps the mechanism from ever punishing its own operators.

[sshd]
enabled = true
maxretry = 4
findtime = 10m
bantime = 1h
bantime.increment = true
bantime.factor = 4
ignoreip = 127.0.0.1/8 203.0.113.0/24

Bans are the symptom metric

Track banned addresses and repeat-ban counts as an operational metric: a climbing curve means the exposure is real, and a flat one after switching to key-only auth confirms the surface closed. The ban list is also incident data, feeding the blocklists and the conversation about whether password auth deserves to exist at all.

Whitelist from a source of truth

The ignoreip list is the one line that can turn hardening into self-inflicted outage. Populate it from your documented operator ranges and CI runners, review it whenever infrastructure changes, and verify every entry with a deliberate failed attempt from inside the whitelist. Precision here is what makes the aggressive defaults safe.