Fail2Ban
Fail2Ban is a log-driven intrusion prevention tool that detects repeated authentication failures (and other suspicious patterns) and applies temporary IP bans using the host firewall. On WordPress VPS servers, it is most commonly used to reduce brute-force pressure against SSH and other exposed services by automatically blocking abusive sources after a configurable number of failures.
Background and history
As SSH and web services became routinely exposed on public servers, automated bots began attempting credential stuffing and brute-force logins at scale. Administrators needed a lightweight way to react automatically without manually updating firewall rules. Fail2Ban emerged as a practical solution: tail logs, match patterns, and ban abusive IPs for a period. Over time it added support for multiple log backends, many service-specific filters, and integration with common firewall frameworks.
Adoption and where it’s commonly used
Fail2Ban is commonly used on:
- VPS and cloud Linux servers with public SSH
- Web servers that expose admin endpoints or HTTP auth
- Mail servers and other credentialed services
- Small-to-medium fleets where a host-local ban mechanism complements perimeter controls
Maintained by
- Maintained by the Fail2Ban project community.
Best when to use
- SSH is exposed to the internet and logs show frequent failed logins.
- You want automated temporary bans without maintaining manual allow/deny lists.
- You already have a host firewall (UFW, nftables/iptables, firewalld) and want Fail2Ban to drive it.
- You can review logs and tune jails to match your stack.
Not suitable when
- You need centralized, fleet-wide enforcement and reporting as the primary control (use perimeter controls plus centralized security tooling).
- Your services do not log reliably (Fail2Ban requires logs with consistent patterns).
- You need Layer 7 bot mitigation, WAF rules, or DDoS protection (use WAF/CDN controls).
Compatibility notes
-
Log paths and service names differ by distro and web stack:
- Debian/Ubuntu SSH auth logs often live at
/var/log/auth.log - RHEL-based systems often use
/var/log/secure
- Debian/Ubuntu SSH auth logs often live at
-
With systemd, Fail2Ban can read from journald; this is often more portable than file paths.
-
Firewall integration varies:
- UFW:
banaction = ufw - firewalld: appropriate firewalld action
- nftables/iptables: appropriate iptables/nftables actions
- UFW:
-
If you use a cloud/provider firewall, Fail2Ban bans on the host do not block traffic before it reaches the server’s network interface.
Misconfiguring Fail2Ban can ban your own IP. Before enabling jails, ensure you have console/serial/KVM access or a safe allowlist strategy. Keep at least one active SSH session open while testing changes.
Concepts and how it works
Fail2Ban consists of:
- Filters: regex patterns that detect failures in logs.
- Jails: service-specific policies that bind filters to log sources and define thresholds.
- Actions: what happens when a ban triggers (typically add a firewall rule).
Installation
Debian/Ubuntu
sudo apt update
sudo apt install -y fail2ban
Enable and start:
sudo systemctl enable --now fail2ban
Verify:
systemctl status fail2ban --no-pager
RHEL/Fedora/Rocky/AlmaLinux
sudo dnf install -y fail2ban
sudo systemctl enable --now fail2ban
Key files and directories
| Path | Purpose |
|---|---|
| -- | |
/etc/fail2ban/jail.conf | Default config (do not edit) |
/etc/fail2ban/jail.local | Main local override file (preferred) |
/etc/fail2ban/jail.d/*.conf | Drop-in jail overrides (clean per-service approach) |
/etc/fail2ban/filter.d/ | Filters (regex definitions) |
/var/log/fail2ban.log | Fail2Ban activity log (when enabled) |
Do not edit jail.conf. Put changes in jail.local or /etc/fail2ban/jail.d/*.conf so package upgrades do not overwrite your settings.
Baseline configuration for daily VPS use
Step 1: Create local configuration
If you prefer a single file:
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
A cleaner production approach is to keep /etc/fail2ban/jail.local minimal and use /etc/fail2ban/jail.d/*.conf drop-ins per service. Both are valid; pick one approach and be consistent.
Step 2: Set global defaults
In /etc/fail2ban/jail.local (or a drop-in such as /etc/fail2ban/jail.d/00-defaults.conf), define defaults appropriate for internet-exposed hosts:
[DEFAULT]
backend = systemd
findtime = 10m
maxretry = 5
bantime = 1h
Firewall action examples:
- UFW environments:
[DEFAULT]
banaction = ufw
- firewalld environments:
[DEFAULT]
banaction = firewallcmd-ipset
If banaction does not match your firewall stack, bans may not take effect even though Fail2Ban reports activity. Verify bans by inspecting firewall status after a test ban.
Step 3: Enable the SSH jail
Create a dedicated drop-in:
sudo tee /etc/fail2ban/jail.d/sshd.conf >/dev/null <<'EOF'
[sshd]
enabled = true
# Match your SSH port (22, 2222, 2581, etc.)
port = 22
# Debian/Ubuntu file log path; prefer systemd backend when possible
logpath = %(sshd_log)s
maxretry = 5
findtime = 10m
bantime = 1h
EOF
If you changed SSH to port 2581, set:
port = 2581
Apply changes safely
Validate service and reload:
sudo systemctl restart fail2ban
sudo systemctl status fail2ban --no-pager
List enabled jails:
sudo fail2ban-client status
Check SSH jail:
sudo fail2ban-client status sshd
Operational commands (daily use)
Show all jails
sudo fail2ban-client status
Show one jail
sudo fail2ban-client status sshd
Manually ban and unban (useful for validation)
sudo fail2ban-client set sshd banip 198.51.100.10
sudo fail2ban-client set sshd unbanip 198.51.100.10
Unban your own IP if needed
sudo fail2ban-client set sshd unbanip <your_public_ip>
If you cannot reach the server via SSH, use provider console access to unban and adjust configuration.
Logs and verification
Fail2Ban log
sudo tail -n 200 /var/log/fail2ban.log
Follow live:
sudo tail -f /var/log/fail2ban.log
Verify bans reached the firewall
UFW
sudo ufw status numbered
You should see deny entries corresponding to banned IPs.
nftables / iptables / firewalld
Verification method depends on your firewall stack; confirm the action you chose (banaction) and then inspect the active rules.
Protecting services beyond SSH
Fail2Ban works best when a service logs consistent, parseable failures and you have a filter/jail that matches your stack.
Common built-in jails (availability varies)
| Service area | Typical jail name | Notes |
|---|---|---|
| - | - | |
| SSH | sshd | Most common baseline jail |
| HTTP basic auth | nginx-http-auth / apache-auth | Protects endpoints using basic auth |
| Recidive abusers | recidive | Re-bans repeat offenders across jails |
WordPress-specific guidance
Fail2Ban is not a full web application firewall. For WordPress login and bot pressure:
- Prefer upstream controls (CDN/WAF rules, rate limiting).
- If you still want host-side protection, base it on web server access logs and be conservative to avoid false positives.
Blocking by IP based on HTTP requests can ban legitimate users behind shared IPs (office NAT, mobile carriers). Use conservative thresholds and consider allowlisting trusted admin IPs.
Allowlisting and reducing false positives
Ignore trusted IPs (recommended)
In [DEFAULT]:
ignoreip = 127.0.0.1/8 ::1 203.0.113.10
Add your fixed admin IPs (office/VPN). Avoid allowlisting broad residential ranges.
Do not add wide ranges (such as your ISP’s entire CIDR) unless you fully understand the security tradeoff.
Tune thresholds
Guidance for SSH on internet-exposed VPS:
maxretry: 3–5findtime: 5m–15mbantime: 1h–24h (shorter for dynamic IP environments, longer for persistent attackers)
Example stricter SSH profile:
[sshd]
enabled = true
port = 2581
maxretry = 3
findtime = 10m
bantime = 6h
Troubleshooting
Jail shows activity but no bans appear
Common causes:
- Wrong
banactionfor the firewall stack - Running in a container or restricted environment
- Missing privileges to modify firewall rules
Checks:
sudo fail2ban-client status sshd
sudo fail2ban-client get sshd banaction
sudo systemctl status fail2ban --no-pager
SSH jail not detecting failures
Common causes:
- Wrong log backend or log path
- SSH logs go to journald only
- Different distro log file location
Checks:
sudo fail2ban-client get sshd logpath
sudo journalctl -u ssh --no-pager -n 200 2>/dev/null || true
sudo journalctl -u sshd --no-pager -n 200 2>/dev/null || true
sudo tail -n 200 /var/log/auth.log 2>/dev/null || true
sudo tail -n 200 /var/log/secure 2>/dev/null || true
You got banned (self-lockout prevention and recovery)
Prevention:
- Add your trusted admin IP to
ignoreip. - Test from a secondary session before tightening thresholds.
Recovery (requires server access):
- Unban your IP:
sudo fail2ban-client set sshd unbanip <your_public_ip>
If you cannot SSH in:
- Use provider console access to unban and adjust
ignoreip.
Security notes
-
Fail2Ban reduces brute-force pressure but does not replace:
- SSH key-based authentication
- Disabling root SSH login
- Restricting SSH to trusted IPs where feasible
- Regular patching and log monitoring
-
For WordPress web-layer abuse, prefer upstream controls (WAF/CDN) and application hardening.
Quick reference
Minimal production baseline (SSH + UFW)
/etc/fail2ban/jail.d/00-defaults.conf:
[DEFAULT]
backend = systemd
banaction = ufw
findtime = 10m
maxretry = 5
bantime = 1h
ignoreip = 127.0.0.1/8 ::1
/etc/fail2ban/jail.d/sshd.conf:
[sshd]
enabled = true
port = 2581
Apply:
sudo systemctl restart fail2ban
sudo fail2ban-client status
sudo fail2ban-client status sshd
Common commands
| Goal | Command |
|---|---|
| -- | |
| Start/enable | sudo systemctl enable --now fail2ban |
| Restart | sudo systemctl restart fail2ban |
| List jails | sudo fail2ban-client status |
| Jail status | sudo fail2ban-client status sshd |
| Ban IP | sudo fail2ban-client set sshd banip <ip> |
| Unban IP | sudo fail2ban-client set sshd unbanip <ip> |
| View logs | sudo tail -f /var/log/fail2ban.log |