SSH brute-force traffic never really stops; it just shifts source IPs.
A useful baseline on Linux is:
- Keep SSH key-based auth as your primary control.
- Use Fail2ban to detect repeated auth failures.
- Enforce bans through your host firewall (nftables on modern distros).
- Test and verify every step so you know bans are real.
This guide is intentionally practical: copyable config, test commands, and rollback paths.
Why this stack?
- Fail2ban watches logs and bans abusive IPs for a configurable time window.
- nftables is the modern packet filtering framework in Linux, and Fail2ban can call nftables ban actions.
-
OpenSSH still allows multiple auth attempts per connection by default (
MaxAuthTries 6), so reducing noise and shortening brute-force windows is worth it.
Important: Fail2ban lowers risk and log noise. It does not replace strong authentication. Keep SSH keys (and MFA if available) as primary defense.
Lab assumptions
- Debian/Ubuntu-style layout (adjust package manager/log paths for your distro)
- SSH service name:
ssh(sometimessshdon other distros) - You have sudo access
1) Install and enable Fail2ban
sudo apt update
sudo apt install -y fail2ban
sudo systemctl enable --now fail2ban
sudo systemctl status fail2ban --no-pager
Quick sanity check:
sudo fail2ban-client ping
# Expected: Server replied: pong
2) Create a local jail file (donโt edit upstream defaults)
Fail2ban upstream and Debian manpages recommend overriding with .local files rather than editing distributed .conf directly.
Create /etc/fail2ban/jail.local:
[DEFAULT]
# Ban window policy
findtime = 10m
maxretry = 5
bantime = 1h
# Use nftables actions instead of legacy iptables defaults
banaction = nftables-multiport
banaction_allports = nftables-allports
# Never ban local/admin ranges you trust
ignoreip = 127.0.0.1/8 ::1
[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s
backend = %(sshd_backend)s
Then test config and reload:
sudo fail2ban-client -t
sudo systemctl restart fail2ban
sudo fail2ban-client status
sudo fail2ban-client status sshd
3) Tighten SSH daemon settings (small changes, big effect)
Edit /etc/ssh/sshd_config and confirm at least:
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
MaxAuthTries 3
Validate and apply safely:
sudo sshd -t
sudo systemctl reload ssh
If your service name is sshd:
sudo systemctl reload sshd
Tip: Keep one existing SSH session open while testing changes.
4) Verify that Fail2ban is actually banning
Watch jail state:
sudo fail2ban-client status sshd
Tail logs while you test:
sudo journalctl -u fail2ban -f
Inspect nftables ruleset:
sudo nft list ruleset
Look for Fail2ban-created chains/sets after a ban event.
To simulate and confirm unban/ban behavior in a controlled lab:
# Manual temporary ban test
sudo fail2ban-client set sshd banip 203.0.113.10
sudo fail2ban-client status sshd
# Remove test IP
sudo fail2ban-client set sshd unbanip 203.0.113.10
5) Operational guardrails (what keeps this maintainable)
-
Use
.localoverrides only (jail.local,jail.d/*.local) to survive package upgrades cleanly. -
Version your config (e.g., with etckeeper or git in
/etc). - Alert on lockout risk: if you disable password auth, confirm key login from a second terminal before closing the first.
-
Avoid over-banning: tune
findtime,maxretry, andbantimebased on your own auth patterns.
6) Fast rollback plan (if you lock yourself out)
From console/KVM/local access:
sudo systemctl stop fail2ban
sudo mv /etc/fail2ban/jail.local /etc/fail2ban/jail.local.bak
sudo systemctl restart fail2ban
# Revert SSH config if needed, then:
sudo sshd -t && sudo systemctl reload ssh
If firewall rules look wrong:
sudo nft list ruleset
# restore your known-good nft config if you keep one, e.g.:
# sudo nft -f /etc/nftables.conf
Final notes
This setup is not โset and forget.โ Revisit ban policy quarterly and after major distro upgrades.
A good target is fewer false positives, visible ban telemetry, and zero surprise lockouts.
Thatโs what production hardening should feel like.
References
- Fail2ban project README (purpose, behavior, docs links): https://github.com/fail2ban/fail2ban
- Fail2ban default
jail.conf(recommended.localoverride model, default timings/backends): https://raw.githubusercontent.com/fail2ban/fail2ban/master/config/jail.conf - Debian
jail.conf(5)manpage (config precedence,.localguidance, jail options): https://manpages.debian.org/bookworm/fail2ban/jail.conf.5.en.html - Debian
fail2ban-client(1)manpage (validation/status/ban/unban commands): https://manpages.debian.org/bookworm/fail2ban/fail2ban-client.1.en.html - OpenSSH
sshd_config(5)manpage (MaxAuthTriessemantics/default): https://man7.org/linux/man-pages/man5/sshd_config.5.html - nftables wiki + official manpage index: https://wiki.nftables.org/wiki-nftables/index.php/Main_Page
Top comments (0)