Why Fail2ban Belongs in Every Homelab
Within hours of exposing any SSH server or web application to the internet, you will see failed login attempts in your logs. Automated scanners cycle through credential lists, targeting SSH, web login pages, and API endpoints. Fail2ban is the simplest and most effective first line of defense — it reads log files, detects repeated failures, and temporarily bans the offending IP using the host firewall.
I run Fail2ban on every homelab host alongside Docker containers managed by Traefik. This guide covers the complete setup: SSH jail, Docker log integration for Traefik and Nginx, custom jails for specific applications, email alerts, and management commands.
What you will end up with:
- SSH brute-force attempts blocked after 5 failures
- Traefik and Nginx authentication failures banned automatically
- Custom jails for any service that writes to logs
- Email notifications when bans occur
Prerequisites
- A Debian or Ubuntu host (works on any Linux distribution)
- SSH server running on the host
- Docker with containers whose logs you want to monitor (Traefik, Nginx, etc.)
- Root or sudo access
Installing Fail2ban
Installation is straightforward on any Debian-based system:
|
|
Verify the service is running:
|
|
A successful ping returns pong.
Fail2ban ships with a default configuration file at /etc/fail2ban/jail.conf. Never edit this file directly — package updates will overwrite it. Instead, create a local override:
|
|
The jail.local file overrides the defaults. Set global values first, then configure individual jails.
SSH Jail Configuration
The SSH jail is the most important one. It monitors /var/log/auth.log for authentication failures and bans IPs that exceed the threshold.
Configure it in /etc/fail2ban/jail.local:
|
|
Key settings explained:
- bantime = 1h — initial ban duration
- bantime.increment = true — doubles ban time for repeat offenders (1h → 2h → 4h → …)
- banaction = nftables-multiport — uses nftables instead of legacy iptables (default on modern Debian/Ubuntu)
- action = %(action_mwl)s — sends email with whois and log lines (requires MTA, see below)
Activate and check:
|
|
The status output shows total bans, currently banned IPs, and the log file being monitored.
Docker Log Integration with Traefik Jail
Docker containers write logs to journald or to a JSON file under /var/lib/docker/containers/ by default — not where Fail2ban expects them. You have two options:
Option A: Configure the container’s logging driver to use local files.
Add a bind mount in your Traefik compose file so the access log is accessible to Fail2ban on the host:
|
|
Create the log directory on the host:
|
|
Option B: Use syslog or journald logging driver.
If you prefer not to write log files inside containers, configure journald as the log driver for Traefik:
|
|
Then use the journald backend in Fail2ban:
|
|
I recommend Option A — plain log files are easier to debug and work with fail2ban-regex for testing.
Traefik Jail Configuration
Create a jail configuration for Traefik:
|
|
|
|
Now create the filter file. Traefik access logs have a different format from standard Nginx or Apache logs:
|
|
|
|
This regex matches Traefik access log lines with HTTP 401 (Unauthorized) or 403 (Forbidden) status codes. These are the responses Traefik returns when authentication middleware rejects a request.
Test the regex with your actual logs:
|
|
Reload and verify:
|
|
Nginx Jail for Docker Containers
If you use Nginx as a reverse proxy instead of Traefik, the setup is similar — bind mount the Nginx log directory to the host.
Example Nginx compose log mount:
|
|
Jail configuration (uses the built-in filter):
|
|
Nginx also has a built-in filter for nginx-botsearch — useful for blocking bots that probe for vulnerabilities like wp-admin, phpmyadmin, or .env files:
|
|
The aggressive maxretry = 2 ensures bots get banned after just two suspicious requests.
Creating Custom Jails
Any service that writes failure events to a log file can be protected. The recipe is always the same:
- Determine the log file path and format
- Write a regex to match failure lines
- Create the jail and filter files
- Test with
fail2ban-regex
Example: Custom Jail for a Web Application
Suppose you run Nextcloud in Docker and want to ban IPs that fail authentication:
|
|
|
|
|
|
|
|
Always test before reloading:
|
|
The tool shows matched lines, unmatched lines, and a summary — invaluable for debugging regex patterns.
Email Alerts and Management
The %(action_mwl)s action sends an email with whois information and the most recent log lines from the offending IP. This requires a working MTA on the host.
Install msmtp for Lightweight Email
For a minimal MTA that works without a full mail server:
|
|
Configure /etc/msmtprc:
defaults
auth on
tls on
tls_trust_file /etc/ssl/certs/ca-certificates.crt
account default
host smtp.gmail.com
port 587
from [email protected]
user [email protected]
password your-app-password
Test it:
|
|
Then in /etc/fail2ban/jail.local, set the sender and recipient:
|
|
Fail2ban Management Commands
Essential fail2ban-client commands for day-to-day management:
|
|
Testing Your Setup
Test the SSH jail by intentionally failing SSH logins:
|
|
Then check:
|
|
You should see the localhost IP (or your IP) listed in the “Banned IP list”. Remember to unban it afterward:
|
|
Test the Traefik jail by hitting a protected endpoint with an invalid token or no authentication:
|
|
Monitoring Fail2ban with Prometheus
For visibility at scale, the fail2ban_exporter exposes per-jail ban counts as Prometheus metrics. Run it alongside your existing monitoring stack:
|
|
Then add a scrape target in Prometheus and a Grafana dashboard to track bans over time.
Putting It All Together
Fail2ban is a essential security layer for any homelab exposed to the internet. It integrates cleanly with Docker containers through bind-mounted log files, uses nftables for efficient firewall rules, and keeps you informed through email alerts.
Key takeaways:
- Install and configure the SSH jail first — it is the highest-impact change
- Use bind mounts or journald for Docker container log access
- Always test new jails with
fail2ban-regexbefore enabling them - Start with conservative thresholds (
maxretry = 5,bantime = 1h) and tighten over time - Combine with CrowdSec, Traefik rate limiting, and firewall rules for defense in depth
Your homelab will thank you the first time you see hundreds of banned IPs piling up in the fail2ban log instead of filling your auth log.