Hardening SSH and Web Services with Fail2ban: Protect Against Brute‑Force Attacks
This guide explains how to use Fail2ban on Linux to automatically detect and block brute‑force login attempts for SSH, web authentication pages, APIs, and mail services, covering installation, configuration hierarchy, custom filters, progressive banning, performance‑optimized actions, high‑availability options, and troubleshooting steps.
Overview
Brute‑force attacks are one of the most common threats to publicly exposed servers, targeting SSH, web login pages, and API endpoints. Fail2ban is a mature intrusion‑prevention tool that monitors log files, matches failure patterns with regular expressions, and bans offending IPs via iptables, nftables, or firewalld without manual intervention.
Core Architecture
Jail : defines the target service, log paths, filter, and ban actions.
Filter : a set of Python regular expressions that extract offending IPs from logs.
Action : the concrete operation performed when a ban is triggered (e.g., add an iptables rule, send a webhook).
The three components are orchestrated by the jail: Jail → Filter → Action.
Tool Comparison
Fail2ban is compared with DenyHosts and CrowdSec. Fail2ban supports SSH, HTTP, mail and custom services, works with iptables/nftables/firewalld, and is highly mature. CrowdSec adds distributed community threat intelligence, while DenyHosts is limited to SSH only.
Installation and Basic Configuration
# Ubuntu / Debian
sudo apt update && sudo apt install -y fail2ban
# CentOS 8+ / RHEL (EPEL)
sudo dnf install -y epel-release && sudo dnf install -y fail2ban
# Verify version
fail2ban-client version # Expected output: Fail2Ban v1.1.xConfiguration files follow a clear priority hierarchy:
/etc/fail2ban/
├── jail.conf # default settings (updated on upgrade)
├── jail.local # user overrides (highest priority)
├── jail.d/ # per‑jail snippets (highest priority)
│ ├── sshd.conf
│ └── nginx.conf
├── filter.d/ # filter definitions
├── action.d/ # action definitionsOnly jail.local or files under jail.d/ should be edited to avoid losing changes on package upgrades.
SSH Brute‑Force Protection
# /etc/fail2ban/jail.d/sshd.conf
[sshd]
enabled = true
port = ssh,22222 # add custom ports if SSH runs on a non‑standard port
filter = sshd
backend = systemd # use journal when available
maxretry = 3
findtime = 300 # 5 minutes
bantime = 600 # initial ban 10 minutes (progressive ban will increase)
ignoreip = 127.0.0.1/8 ::1 10.0.0.100 # whitelist admin IPsThe built‑in sshd filter already covers most log formats. Enabling mode = aggressive adds extra patterns such as connection resets and protocol mismatches:
# /etc/fail2ban/filter.d/sshd.conf (excerpt)
[Definition]
mode = aggressive
failregex = ^<HOST> -.*"Failed password for .* from <HOST> port .* ssh2"
^<HOST> -.*"Invalid user .* from <HOST>"Web Service Protection (Nginx/Apache)
Fail2ban includes a filter for HTTP Basic Auth failures. To protect custom login APIs, create a custom filter that matches POST requests returning 401 or 403:
# /etc/fail2ban/filter.d/nginx-login.conf
[Definition]
failregex = ^<HOST> -.*"POST /api/login.*" (401|403)
ignoreregex = ^<HOST> -.*"GET /health" # /etc/fail2ban/jail.d/nginx-login.conf
[nginx-login]
enabled = true
port = http,https
filter = nginx-login
logpath = /var/log/nginx/access.log
maxretry = 10
findtime = 300
bantime = 1800 # 30 minutes
action = nftables-multiport[name=nginx-login, port="http,https", protocol=tcp] webhook-notifyCustom Jail Development
When writing a new filter, remember: <HOST> matches IPv4/IPv6 addresses.
Each line is an OR‑combined regex. ignoreregex uses the same syntax to exclude false positives.
Validate the filter with fail2ban-regex before deploying:
# Test against a real log line
fail2ban-regex "Jun 15 08:23:01 server sshd[12345]: Failed password for root from 203.0.113.5 port 22 ssh2" /etc/fail2ban/filter.d/sshd.confBest Practices
Progressive Ban : enable bantime.increment = true and tune bantime.factor and bantime.maxtime so repeated offenses receive exponentially longer bans.
Permanent Blacklist : write repeatedly offending IPs to a persistent blacklist file and load it with ipset for O(1) lookups.
GeoIP Filtering : block whole countries upstream of Fail2ban when the service is intended for a single region.
Performance‑Optimized Actions : prefer nftables-multiport or firewallcmd-ipset over raw iptables for large ban lists.
Whitelist Carefully : mis‑configured ignoreip can lock out administrators; always include management and monitoring IPs.
Troubleshooting & Monitoring
Common issues and their fixes:
Jail not banning: verify the filter matches the log format with fail2ban-regex.
Banned IP still reachable: ensure the banaction rule is inserted before any ACCEPT rules (check iptables -L INPUT --line-numbers or nft list ruleset).
Service crashes: look for SQLite lock errors; rotate the database or switch to MySQL if concurrency is high.
Key monitoring commands:
# Show overall status
fail2ban-client status
# Show details for a jail
fail2ban-client status sshd
# Manually ban/unban an IP
fail2ban-client set sshd banip 203.0.113.100
fail2ban-client set sshd unbanip 203.0.113.100
# Reload configuration without restarting all jails
fail2ban-client reload sshdFor production monitoring, export metrics with fail2ban_exporter and set Prometheus alerts on ban rate, total bans, and client latency.
Backup & Restore
Regularly back up /etc/fail2ban and the SQLite database. A sample backup script creates a tarball, backs up custom blacklists, and purges old archives. Restoration involves stopping the service, extracting the archive, and verifying the configuration with fail2ban-client -d before restarting.
Conclusion
Fail2ban provides an automated, low‑overhead defense against brute‑force attacks across SSH, web services, and mail servers. By combining accurate filters, progressive banning, and performance‑aware actions, operators can turn manual log inspection into real‑time protection while retaining the ability to integrate with higher‑level security platforms such as CrowdSec, WAFs, or SIEMs.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Raymond Ops
Linux ops automation, cloud-native, Kubernetes, SRE, DevOps, Python, Golang and related tech discussions.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
