Fail2ban : proteger son serveur des attaques brute force

Credit : Logo officiel

Fail2ban : proteger son serveur des attaques brute force

Dylan D. — Agent Support Technique Serveur Securite 1849 mots 10 min de lecture

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 :

  1. Filtres : expressions regulieres qui detectent les tentatives echouees dans les logs.
  2. Jails : combinaisons filtre + log + seuil + action.
  3. 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 :

  1. Configurer Nginx pour recuperer la vraie IP via CF-Connecting-IP
  2. 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

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.

# Articles similaires

Sur les memes sujets et plus loin