
Credit : Logo officiel
Fail2ban : proteger son serveur des attaques brute force
Fail2ban : proteger son serveur des attaques brute force
Nouveau VPS Debian 12 mis en prod un jeudi, IP attribuee par IONOS. Le lendemain matin je consulte les logs : 4 200 tentatives SSH echouees en 14 heures, 380 tentatives sur /wp-login.php, et un bot qui scanne /admin.php, /phpmyadmin/, /wp-config.php.bak. C'est pas exceptionnel, c'est le quotidien d'un serveur expose. Sans fail2ban, j'aurais des milliers de lignes de bruit dans mes logs et un risque non nul qu'un mot de passe finisse par passer.
Fail2ban surveille les logs en temps reel et bannit automatiquement les IP qui depassent un seuil de tentatives echouees. Configure correctement, il bloque 99% des attaques automatisees sans intervention humaine. Voici la config que je deploie sur tous mes serveurs.
Comment ca marche
Fail2ban a trois composants :
- Filtres : expressions regulieres qui detectent les tentatives echouees dans les logs.
- Jails : combinaisons filtre + log + seuil + action.
- Actions : ce qu'il fait quand le seuil est atteint (banir avec iptables, ufw, nftables, envoyer un mail, etc.).
Quand un filtre matche maxretry fois en findtime secondes, l'action de ban s'applique pour bantime secondes.
Installation et premiere config
apt update
apt install fail2ban -y
systemctl enable --now fail2ban
Regle d'or absolue : ne modifie JAMAIS /etc/fail2ban/jail.conf. Il sera ecrase au prochain apt upgrade. Cree un fichier .local qui surcharge :
cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
Configuration globale
Edite /etc/fail2ban/jail.local et regle la section [DEFAULT] :
[DEFAULT]
# Duree du bannissement (1 heure)
bantime = 3600
# Fenetre d'observation (10 minutes)
findtime = 600
# Tentatives avant ban
maxretry = 5
# Backend (lit les logs systemd directement)
backend = systemd
# Action : bannir + email avec extrait de log
action = %(action_mwl)s
# Email de notification
destemail = admin@mondomaine.fr
sender = fail2ban@mondomaine.fr
mta = sendmail
# IPs a ne JAMAIS bannir (mets la tienne, sinon tu vas te bannir)
ignoreip = 127.0.0.1/8 ::1 203.0.113.50 198.51.100.0/24
# Mode de bannissement (iptables par defaut, nftables sur Debian 12)
banaction = nftables-multiport
banaction_allports = nftables-allports
Mets ton IP personnelle dans ignoreip avant de lancer fail2ban. Sinon une faute de frappe sur ton mot de passe SSH t'enferme dehors. Pour la trouver :
curl -s ifconfig.me
Jail SSH (la plus importante)
[sshd]
enabled = true
port = 2222
logpath = %(sshd_log)s
backend = %(sshd_backend)s
maxretry = 3
bantime = 7200
findtime = 300
3 tentatives en 5 minutes = ban pendant 2 heures. Sur un VPS IONOS, j'ai mesure le trafic SSH : avant fail2ban, 200 tentatives/heure de bots. Apres : zero, parce que ces bots ne reviennent pas apres un timeout.
Adapte le port a ton port SSH reel. Si tu utilises le port 22 standard, mets port = ssh.
Jails Nginx
Jail nginx-http-auth
Detecte les echecs d'authentification Basic Auth :
[nginx-http-auth]
enabled = true
port = http,https
filter = nginx-http-auth
logpath = /var/log/nginx/error.log
maxretry = 3
bantime = 3600
Jail nginx-badbots (anti-scan)
Cree un filtre dans /etc/fail2ban/filter.d/nginx-badbots.conf :
[Definition]
failregex = ^<HOST> -.*"(GET|POST|HEAD).*(wp-login\.php|xmlrpc\.php|phpmyadmin|admin\.php|/\.env|/\.git|wp-config\.php\.bak).*" (404|403|444)
ignoreregex =
Active la jail :
[nginx-badbots]
enabled = true
port = http,https
filter = nginx-badbots
logpath = /var/log/nginx/access.log
maxretry = 5
bantime = 86400
findtime = 600
5 scans en 10 minutes = ban pour 24 heures. Tres efficace contre les bots.
Jail nginx-noscript (executable indesirable)
[nginx-noscript]
enabled = true
port = http,https
filter = nginx-noscript
logpath = /var/log/nginx/access.log
maxretry = 6
Filtre dans /etc/fail2ban/filter.d/nginx-noscript.conf :
[Definition]
failregex = ^<HOST> -.*GET.*(\.php|\.asp|\.exe|\.pl|\.cgi|\.scgi).*
ignoreregex =
A reserver aux sites statiques uniquement.
Jail WordPress
Cree /etc/fail2ban/filter.d/wordpress-login.conf :
[Definition]
failregex = ^<HOST> .* "POST /wp-login\.php
^<HOST> .* "POST /xmlrpc\.php
ignoreregex =
Active la jail :
[wordpress-login]
enabled = true
port = http,https
filter = wordpress-login
logpath = /var/log/nginx/access.log
maxretry = 5
bantime = 3600
findtime = 300
5 POSTs sur wp-login en 5 minutes = ban 1 heure.
Idealement, tu utilises aussi un plugin WP comme Limit Login Attempts qui logue les echecs avec l'utilisateur tente, mais le filtre Nginx suffit pour bloquer les bots les plus agressifs.
Jail PostgreSQL / MariaDB
Si MariaDB est expose ou que tu veux te proteger contre les acces depuis localhost compromis :
[mariadb-auth]
enabled = true
port = 3306
filter = mariadb-auth
logpath = /var/log/mysql/error.log
maxretry = 3
Le filtre matche typiquement Access denied for user 'xxx'@'IP'.
Bannissement progressif (recidive)
La jail recidive est mon arme ultime. Elle observe le log de fail2ban lui-meme et bannit beaucoup plus longtemps les IPs qui se font rebannir plusieurs fois :
[recidive]
enabled = true
filter = recidive
logpath = /var/log/fail2ban.log
backend = auto
banaction = %(banaction_allports)s
bantime = 604800
findtime = 86400
maxretry = 5
5 bannissements en 24h = ban sur tous les ports pendant 7 jours. Sur un serveur en prod depuis 6 mois, j'avais 1 200 IPs en recidive. Que des bots persistants.
Actions disponibles
Fail2ban supporte plusieurs methodes de ban. Choisis selon ton firewall :
# UFW (simple, Ubuntu/Debian)
banaction = ufw
# iptables multi-ports (par defaut)
banaction = iptables-multiport
# nftables (Debian 12+, recommande)
banaction = nftables-multiport
# Tous les ports (pour la jail recidive)
banaction_allports = nftables-allports
Sur Debian 12, je conseille nftables-multiport qui est natif et performant.
Commandes utiles au quotidien
# Demarrer/redemarrer/recharger
systemctl restart fail2ban
fail2ban-client reload
# Statut global
fail2ban-client status
# Status
# |- Number of jail: 6
# `- Jail list: nginx-badbots, nginx-http-auth, recidive, sshd, wordpress-login, mariadb-auth
# Statut detaille d'une jail
fail2ban-client status sshd
# Status for the jail: sshd
# |- Filter
# | |- Currently failed: 2
# | |- Total failed: 1247
# | `- Journal matches: _SYSTEMD_UNIT=sshd.service + _COMM=sshd
# `- Actions
# |- Currently banned: 4
# |- Total banned: 89
# `- Banned IP list: 45.156.86.42 80.94.95.115 185.224.128.83 218.92.0.247
# Ban manuel
fail2ban-client set sshd banip 192.168.1.100
# Debannir
fail2ban-client set sshd unbanip 192.168.1.100
# Tester un filtre contre un log
fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf
fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/wordpress-login.conf
# Voir toutes les IPs bannies
fail2ban-client banned
Surveillance et metriques
Script de monitoring rapide :
#!/bin/bash
# /usr/local/bin/f2b-status.sh
echo "=== Fail2ban Status ==="
for jail in $(fail2ban-client status | grep "Jail list" | sed 's/.*://;s/,//g'); do
banned=$(fail2ban-client status $jail 2>/dev/null | grep "Currently banned" | awk '{print $NF}')
total=$(fail2ban-client status $jail 2>/dev/null | grep "Total banned" | awk '{print $NF}')
echo "$jail: $banned actuellement / $total total"
done
Logs en temps reel :
tail -f /var/log/fail2ban.log
# 2026-05-07 14:32:18,847 fail2ban.actions [891]: NOTICE [sshd] Ban 45.156.86.42
# 2026-05-07 14:35:42,193 fail2ban.actions [891]: NOTICE [wordpress-login] Ban 185.224.128.83
# 2026-05-07 14:42:18,847 fail2ban.actions [891]: NOTICE [sshd] Unban 45.156.86.42
Erreurs courantes et leur fix
ERROR No file(s) found for glob /var/log/auth.log
Cause : Debian 12 utilise par defaut journald sans rsyslog. Le fichier /var/log/auth.log n'existe plus.
Fix : utilise backend = systemd dans la section [DEFAULT] ou la jail concernee. Fail2ban lira alors directement journald.
Failed during configuration: Have not found any log file for sshd jail
Cause : meme probleme que ci-dessus, ou logpath mal defini.
Fix :
# Installer rsyslog si tu veux les fichiers classiques
apt install rsyslog -y
systemctl enable --now rsyslog
# Ou utiliser le backend systemd dans jail.local
Tu te fais bannir toi-meme
Cause : ton IP n'est pas dans ignoreip, et tu as fait quelques erreurs de mot de passe.
Fix :
# Si tu peux encore te connecter (autre IP, console IONOS) :
fail2ban-client set sshd unbanip TON_IP
# Ajoute TON_IP dans ignoreip dans jail.local
fail2ban-client reload
En cas d'urgence sans acces, utilise la console KVM de ton interface IONOS pour te connecter en local et debannir.
Le filtre marche pas
Cause : la regex ne matche pas le format reel de tes logs.
Fix :
fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/wordpress-login.conf
La sortie te dit combien de lignes ont matche et te montre des exemples. Si zero match, ta regex est cassee.
Jail demarre mais ne ban personne
Cause : maxretry trop eleve, ou findtime trop court, ou les logs sont rotates avant que fail2ban ait le temps de les lire.
Fix : baisse maxretry a 3, augmente findtime a 1200, et verifie que logrotate ne purge pas trop vite.
Integration avec Cloudflare et un WAF
Si ton serveur est derriere Cloudflare, fail2ban va voir l'IP de Cloudflare au lieu de celle du visiteur. Il faut donc :
- Configurer Nginx pour recuperer la vraie IP via
CF-Connecting-IP - Adapter les filtres pour matcher cette IP
Dans /etc/nginx/conf.d/cloudflare-realip.conf :
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
# ... voir https://www.cloudflare.com/ips-v4 pour la liste complete
real_ip_header CF-Connecting-IP;
real_ip_recursive on;
Fail2ban verra alors la vraie IP dans les logs Nginx et bannira correctement.
Pour aller plus loin, tu peux aussi pousser les bans vers l'API Cloudflare pour bloquer au niveau du CDN avant meme que la requete arrive sur ton serveur. Action custom dans /etc/fail2ban/action.d/cloudflare-block.conf :
[Definition]
actionban = curl -s -X POST "https://api.cloudflare.com/client/v4/user/firewall/access_rules/rules" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
--data '{"mode":"block","configuration":{"target":"ip","value":"<ip>"},"notes":"fail2ban"}'
Logs et metriques sur le long terme
Pour comprendre les tendances d'attaques, j'agrege les logs fail2ban dans un script qui tourne le dimanche soir :
#!/bin/bash
# /usr/local/bin/f2b-weekly-report.sh
LOG=/var/log/fail2ban.log
echo "=== Rapport hebdomadaire fail2ban ==="
echo
echo "Total bans cette semaine :"
grep "Ban " "$LOG" | grep -E "$(date -d 'last week' +%Y-%m)" | wc -l
echo
echo "Top 10 IPs bannies :"
grep "Ban " "$LOG" | awk '{print $NF}' | sort | uniq -c | sort -rn | head -10
echo
echo "Bans par jail :"
grep "Ban " "$LOG" | grep -oE '\[[a-z-]+\]' | sort | uniq -c | sort -rn
Sur un VPS expose depuis 6 mois, j'ai typiquement entre 8000 et 15000 bans/mois, dont 80% sur sshd. C'est l'echelle de ce qui se passe sur un serveur ordinaire de monsieur tout-le-monde sur Internet.
Pour aller plus loin
- Securiser SSH avec sshd_config : durcir SSH avant meme que fail2ban entre en jeu.
- Configurer CrowdSec sur Linux : une alternative moderne a fail2ban avec partage communautaire de blacklists.
- Configurer iptables sur Linux : mieux comprendre les regles que fail2ban applique.
- Hardening Linux : guide complet : pour aller au-dela du fail2ban.
- Configurer un WAF applicatif sur Nginx : ajoute une couche applicative au-dessus de fail2ban.
Le rempart minimum
Fail2ban c'est le minimum syndical de toute installation serveur exposee a Internet. Combine avec UFW ou nftables, des mises a jour unattended-upgrades et un SSH durci, ca reduit drastiquement la surface d'attaque face aux bots automatises. Mais attention : ca ne remplace pas une vraie politique de securite. Mots de passe forts, MFA, principle of least privilege, audits reguliers : fail2ban c'est juste le filtre anti-bots, pas une armure complete.
Et n'oublie pas de mettre ton IP dans ignoreip. Vraiment.