Fail2ban monitors your service logs for repeated failures and blocks offending IPs using your firewall. This guide explains the concept and gives copy paste jails for SSH on 22, HTTP on 80 or 443, and MySQL or MariaDB on 3306.
How Fail2ban works
- Reads logs from services (for example: /var/log/auth.log, web server error or access logs, MySQL error log).
- Filters match suspicious lines.
- If failures exceed maxretry within findtime, Fail2ban bans the source IP for bantime via nftables on Debian 12.
Key terms:
- jail: the rule that ties a filter and an action to specific logs and ports
- filter: regex rules that match bad events in logs
- action: how to ban (we use nftables-multiport)
Install Fail2ban
0 1 2 3 4 5 |
sudo apt update sudo apt install -y fail2ban sudo systemctl enable --now fail2ban sudo systemctl status fail2ban |
Base configuration
Do not edit jail.conf. Create your own overrides.
/etc/fail2ban/jail.local
0 1 2 3 4 5 6 7 8 |
[DEFAULT] bantime = 1h findtime = 10m maxretry = 5 ignoreip = 127.0.0.1/8 ::1 banaction = nftables-multiport logtarget = /var/log/fail2ban.log |
Reload after changes:
0 1 2 |
sudo fail2ban-client reload |
Jail 1, protect SSH on port 22
/etc/fail2ban/jail.d/sshd.local
0 1 2 3 4 5 6 7 8 9 |
[sshd] enabled = true port = 22 backend = auto logpath = /var/log/auth.log maxretry = 5 findtime = 10m bantime = 1h |
Check status:
0 1 2 3 |
sudo fail2ban-client status sshd sudo tail -f /var/log/fail2ban.log |
Jail 2, protect HTTP on ports 80 and 443
Fail2ban reacts to patterns in logs. Two common web auth cases are below. Use only the one that applies to your stack.
Nginx basic auth:/etc/fail2ban/jail.d/nginx-http-auth.local
0 1 2 3 4 5 6 7 8 9 |
[nginx-http-auth] enabled = true port = http,https filter = nginx-http-auth logpath = /var/log/nginx/error.log maxretry = 3 findtime = 10m bantime = 1h |
Apache basic auth:/etc/fail2ban/jail.d/apache-auth.local
0 1 2 3 4 5 6 7 8 9 |
[apache-auth] enabled = true port = http,https filter = apache-auth logpath = /var/log/apache2/error.log maxretry = 3 findtime = 10m bantime = 1h |
Tip: run ls /etc/fail2ban/filter.d
to see other web filters such as nginx-noscript or apache-badbots, then point a jail at the filter and the correct log path.
Jail 3, watch MySQL or MariaDB on port 3306
Best practice is not to expose 3306 publicly. Bind to localhost or a private network and restrict with your firewall. If you must watch for brute force on 3306:
/etc/fail2ban/jail.d/mysqld-auth.local
0 1 2 3 4 5 6 7 8 9 |
[mysqld-auth] enabled = true port = 3306 filter = mysqld-auth logpath = /var/log/mysql/error.log maxretry = 3 findtime = 10m bantime = 6h |
Confirm the error log path inside MariaDB or MySQL:
0 1 2 |
SHOW VARIABLES LIKE 'log_error'; |
If your DB logs only to the journal, you can switch a jail to use systemd:
0 1 2 3 4 5 6 7 8 9 10 |
[mysqld-auth] enabled = true port = 3306 filter = mysqld-auth backend = systemd journalmatch = _SYSTEMD_UNIT=mariadb.service + _COMM=mysqld maxretry = 3 findtime = 10m bantime = 6h |
Optional, recidive jail for repeat offenders
/etc/fail2ban/jail.d/recidive.local
0 1 2 3 4 5 6 7 |
[recidive] enabled = true logpath = /var/log/fail2ban.log bantime = 1d findtime = 1d maxretry = 5 |
Apply and verify
Check configuration syntax:
0 1 2 |
sudo fail2ban-client -d |
Reload Fail2ban:
0 1 2 |
sudo fail2ban-client reload |
List jails and show bans:
0 1 2 3 |
sudo fail2ban-client status sudo fail2ban-client status sshd |
Unban a specific IP:
0 1 2 |
sudo fail2ban-client set sshd unbanip 203.0.113.45 |
Check nftables sets for banned IPs:
0 1 2 |
sudo nft list ruleset | grep -A3 'f2b' |
Troubleshooting
- Filters available:
0 1 2 |
ls /etc/fail2ban/filter.d |
- Test a filter against a log:
0 1 2 |
sudo fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf |
- Reverse proxy or CDN:
Ensure your web server logs the real client IP, not the proxy, otherwise you will ban the proxy’s IP. - Dockerized services:
Make sure containers write logs to files on the host or to journald so Fail2ban can read them.