📍 Article principal du cluster : VPS hardening sécurité 2026 : guide complet
Cet article fait partie du cluster « Sécurité VPS Linux ». Pour la vue d’ensemble, lire d’abord le pilier.
Introduction
Un VPS exposé à Internet reçoit en permanence un bombardement de tentatives de connexion automatisées : SSH, formulaires de login WordPress, FTP, SMTP, et plus généralement tout port qui présente un mécanisme d’authentification. Sur un VPS Hetzner ou OVH lambda à Helsinki ou Strasbourg, le rythme observé est typiquement de 5 000 à 50 000 tentatives SSH par jour, sans compter les tentatives WordPress (/wp-login.php) qui peuvent grimper à 100 000 quand un site devient un peu visible. Sans protection automatisée, ces tentatives saturent les logs, consomment du CPU et de la bande passante, et augmentent statistiquement le risque qu’une combinaison login/mot de passe finisse par fonctionner.
Fail2ban est l’outil historique de protection IP : il scanne les fichiers de logs en temps réel, détecte les patterns d’échec d’authentification, et bannit automatiquement les IP fautives via iptables, nftables ou ipset. Il est disponible sur Debian/Ubuntu/RHEL, simple à configurer, et fonctionne sans dépendances externes. Ce tutoriel détaille la configuration recommandée 2026 pour un VPS de production : protection SSH agressive, jails WordPress et Nginx, exclusions des IPs internes (whitelist), durées de bannissement progressives, intégration avec Postfix pour notifications, et bonne pratique de monitoring. La configuration cible typiquement réduit de 99 % le bruit dans les logs et coupe court à toute campagne de brute force.
Prérequis
- VPS Linux avec accès sudo (Debian 11/12, Ubuntu 22.04/24.04 LTS, Rocky/Alma 9)
- Service SSH actif et configuration durcie déjà en place (cf. tutoriel frère SSH hardening)
- Pour les jails WordPress/Nginx : un site WordPress accessible et logs Nginx (ou Apache) actifs avec format combiné
- iptables ou nftables installé (généralement présent par défaut)
- Postfix configuré comme relay si l’on veut les notifications email (cf. tutoriel mises à jour automatiques)
- Niveau attendu : intermédiaire
- Temps estimé : 30 à 45 minutes
Étape 1 — Installation et configuration de base
Fail2ban se trouve dans les dépôts standards de toutes les distributions. Sur Debian/Ubuntu, le paquet s’appelle simplement fail2ban. Sur Rocky/Alma, il faut activer le dépôt EPEL (dnf install epel-release) avant l’installation. Le service tourne en tant que démon (fail2ban-server) qui scanne les logs en temps réel, et un utilitaire CLI (fail2ban-client) pour l’interaction.
# Debian/Ubuntu
sudo apt update
sudo apt install -y fail2ban
# Rocky/AlmaLinux
sudo dnf install -y epel-release
sudo dnf install -y fail2ban
# Activer et démarrer le service
sudo systemctl enable --now fail2ban
sudo systemctl status fail2ban
# Vérifier la version (≥ 1.0 recommandé pour le support correct des distributions modernes)
fail2ban-client version
La version recommandée minimale est Fail2ban 1.0+ (Debian 12 fournit 1.0.2, Ubuntu 24.04 fournit 1.0.2, Rocky 9 EPEL fournit 1.0.2). Les versions antérieures à 0.11 ont des bugs connus avec Python 3 et certaines distributions modernes. Si fail2ban-client version retourne une version inférieure, mettre à jour avant de continuer. Ne jamais éditer directement /etc/fail2ban/jail.conf : ce fichier est écrasé à chaque mise à jour. Toute personnalisation va dans /etc/fail2ban/jail.local qui surcharge le défaut.
Étape 2 — Définir les paramètres globaux dans jail.local
Le fichier /etc/fail2ban/jail.local définit les paramètres globaux et active les « jails » individuels. Une « jail » est une règle qui surveille un fichier de log donné, détecte les échecs selon un pattern (le « filter »), et applique une action (le « ban »). La section [DEFAULT] définit les valeurs qui s’appliquent à toutes les jails sauf override explicite.
# /etc/fail2ban/jail.local
sudo nano /etc/fail2ban/jail.local
[DEFAULT]
# IPs jamais bannies (whitelist) — important pour ne pas se locker soi-même
ignoreip = 127.0.0.1/8 ::1 192.168.1.0/24 41.214.0.0/16
# Temps d'observation : si N échecs apparaissent dans cette fenêtre, ban
findtime = 10m
# Nombre d'échecs avant ban
maxretry = 5
# Durée du bannissement initial
bantime = 1h
# Bannissement progressif : à chaque récidive, le bantime augmente exponentiellement
bantime.increment = true
bantime.factor = 2
bantime.maxtime = 30d
# Backend : préférer "systemd" si journald est utilisé (moderne), sinon "polling" sur fichiers
backend = systemd
# Action par défaut : ban + notification email
destemail = ops@votre-entreprise.com
sender = fail2ban@votre-entreprise.com
mta = postfix
action = %(action_mwl)s
# Timezone (important pour les logs cohérents)
usedns = warn
Trois choix structurants. ignoreip liste les sources jamais bannies — y inclure absolument l’IP de votre poste local et celle de votre VPN d’entreprise pour éviter le scénario « je me suis banni moi-même ». Sur Hetzner, ajouter aussi 10.0.0.0/8 si vous utilisez Hetzner Networks pour une communication inter-VPS interne. bantime.increment = true active le bannissement progressif (1h → 2h → 4h → 8h → 16h → 32h… jusqu’à 30 jours max) : un bot qui revient n’est pas re-banni 1h, mais bien plus longtemps, ce qui décourage les retours. backend = systemd est crucial sur les distributions modernes — l’ancien backend « polling » lit les fichiers /var/log/auth.log qui peuvent ne plus exister sous systemd-journald.
Étape 3 — Activer la jail SSH
La jail SSH est la plus importante et la plus universellement utile. Elle surveille les échecs d’authentification SSH (mot de passe ou clé refusée) et bannit l’IP fautive. La configuration par défaut est conservative — on l’agressive un peu plus pour un VPS de production exposé.
# À la suite de [DEFAULT] dans /etc/fail2ban/jail.local
[sshd]
enabled = true
port = 22
filter = sshd
backend = systemd
maxretry = 3
findtime = 10m
bantime = 24h
# Sur un VPS exposé, on peut être plus agressif :
# bantime = 7d
# maxretry = 2
# Si vous avez changé le port SSH (déconseillé mais courant)
# port = 2222
# Pour Debian 12+ : journald a remplacé /var/log/auth.log
# Le filter sshd détecte automatiquement les bons patterns dans journald.
Avec maxretry = 3 et findtime = 10m, une IP qui échoue 3 fois dans 10 minutes est bannie pour 24h (puis 48h, 96h, etc. avec incrément). Les bots qui scannent en distribuant sur des centaines d’IP sources échappent à cette règle (chaque IP n’essaie qu’une fois ou deux), mais leur efficacité globale chute drastiquement quand chaque tentative individuelle ne peut pas répéter. Les bots brute-force « concentrés » (une seule IP, des milliers de tentatives) sont coupés en moins de 10 minutes.
Étape 4 — Jails WordPress et Nginx pour les sites web
Si le VPS héberge un site WordPress, la page /wp-login.php est en permanence brute-forcée. La jail dédiée détecte les POST en échec sur cette page et bannit. Pour les autres sites web, on peut détecter les tentatives d’accès à des paths suspects (/.env, /wp-admin/setup-config.php, /.git/config) qui révèlent des bots de reconnaissance.
# Jail WordPress login dans /etc/fail2ban/jail.local
[wordpress-login]
enabled = true
port = http,https
filter = wordpress-login
logpath = /var/log/nginx/access.log
maxretry = 3
findtime = 10m
bantime = 12h
# Filter custom à créer : /etc/fail2ban/filter.d/wordpress-login.conf
# (créer le fichier ci-dessous)
# Jail Nginx scan de paths suspects
[nginx-noscript]
enabled = true
port = http,https
filter = nginx-noscript
logpath = /var/log/nginx/access.log
maxretry = 6
bantime = 48h
# Jail Nginx requêtes 404 répétées (signe de scan automatisé)
[nginx-noproxy]
enabled = true
port = http,https
filter = nginx-noproxy
logpath = /var/log/nginx/access.log
maxretry = 2
bantime = 24h
Le filter wordpress-login n’existe pas par défaut, il faut le créer. La regex matche les requêtes POST sur wp-login.php qui retournent un code 200 et 401 (échec) — pas trivial à distinguer dans le log Nginx car POST réussi et POST échoué retournent souvent un 200. Une approche plus fiable est d’installer un plugin WordPress comme « WP fail2ban » (de Charles Lecklider) qui logge explicitement chaque échec dans le syslog avec un format dédié.
# /etc/fail2ban/filter.d/wordpress-login.conf
# (Filter générique sans plugin — détection sur les POST 200 vers wp-login.php
# qui peuvent être des échecs ; pas parfait mais utile)
[Definition]
failregex = ^<HOST>.*"POST /wp-login.php HTTP/[\d.]+" 200
ignoreregex =
# Pour le filter avec plugin WP fail2ban :
# /etc/fail2ban/filter.d/wordpress-hard.conf — regex plus fiable basée sur syslog
Pour un site WordPress sérieux, la solution complète combine : (1) plugin WP fail2ban actif, (2) filter custom wordpress-hard.conf qui matche le format syslog du plugin, (3) logpath = /var/log/auth.log dans la jail au lieu du log Nginx. Cette combinaison logge précisément chaque tentative d’auth WordPress, distingue échec par mot de passe vs échec captcha, et permet des bantimes différenciés.
Étape 5 — Application et test de la configuration
Avant tout redémarrage, valider la syntaxe avec fail2ban-client --test. Une erreur de regex dans un filter empêche le démon de démarrer ; il vaut mieux le détecter en test qu’en production. Une fois validé, recharger la configuration sans interruption avec reload.
# Test syntaxique
sudo fail2ban-client --test
# Recharger la config (sans interrompre les bannissements actifs)
sudo fail2ban-client reload
# Vérifier que les jails sont actives
sudo fail2ban-client status
# Sortie attendue :
# Status
# |- Number of jail: 4
# `- Jail list: sshd, wordpress-login, nginx-noscript, nginx-noproxy
# Voir les détails d'une jail spécifique
sudo fail2ban-client status sshd
# Status for the jail: sshd
# |- Filter
# | |- Currently failed: 2
# | |- Total failed: 1247
# | `- File list: /var/log/auth.log
# `- Actions
# |- Currently banned: 12
# |- Total banned: 85
# `- Banned IP list: 45.x.x.x 91.x.x.x 134.x.x.x ...
# Tester en simulant un ban manuel
sudo fail2ban-client set sshd banip 192.0.2.42
# Vérifier que la règle iptables est créée :
sudo iptables -L f2b-sshd -n
# Lever le ban
sudo fail2ban-client set sshd unbanip 192.0.2.42
Les sorties fail2ban-client status sshd sont l’indicateur clé de bon fonctionnement. Le compteur « Total banned » reflète l’activité depuis le démarrage du démon — sur un VPS exposé, attendez plusieurs dizaines de bans dans les premières 24h, voire des centaines selon le bruit Internet. Si après 24h le compteur reste à zéro, c’est qu’il y a un problème : (1) ignoreip trop large, (2) backend mal configuré, (3) logs SSH inexistants ou rotés sans suivi par fail2ban.
Étape 6 — Notifications email et intégration alertes
L’action par défaut %(action_mwl)s (m=ban, w=whois lookup, l=log dump) envoie un email à chaque ban avec : IP fautive, geolocation whois (pays/ISP), et extrait du log montrant les échecs. Ces emails permettent de détecter rapidement une attaque ciblée (ex: 100 emails de ban depuis un même /24 en 5 minutes = botnet concentré sur votre VPS) versus le bruit de fond aléatoire.
# Vérifier que mail/mta est fonctionnel (cf. tutoriel mises à jour auto pour Postfix relay)
echo "Test fail2ban notification" | mail -s "Test" ops@votre-entreprise.com
# Pour limiter le volume de mails (ne notifier que les bans hors-jail SSH normale) :
# Dans une jail spécifique, override l'action :
[sshd]
action = %(action_)s
# (action_ sans le m,w,l = juste ban silencieux, pas d'email)
# Pour intégrer Slack/Mattermost à la place de l'email :
# Créer une action custom /etc/fail2ban/action.d/slack.conf
# avec actionban = curl -X POST https://hooks.slack.com/services/... -d "..."
Pour un parc de plusieurs VPS, centraliser les notifications devient nécessaire : 100 emails par jour saturent la boîte ops. Solution recommandée : pousser les bans vers un canal Slack/Mattermost dédié, ou centraliser via Wazuh (qui a un module Active Response équivalent). Une autre option : ne notifier que sur les bans « récidivistes » via bantime.increment et un filter custom — les bots one-shot sont silencieux, les attaquants persistants déclenchent une alerte.
Étape 7 — Vérification après quelques jours et tuning
Après 3-7 jours d’opération, faire le bilan et ajuster. Les indicateurs clés : nombre de bans/jour par jail, distribution géographique des IPs bannies (via geoip), récidives (mêmes IPs revenues). Cette analyse permet d’ajuster bantime, maxretry, et de détecter des oublis (jail manquante pour un service exposé).
# Stats globales
sudo fail2ban-client status sshd
sudo fail2ban-client status wordpress-login
# Top 10 IPs bannies historiquement (depuis le démarrage)
sudo fail2ban-client banned
# Liste des IPs actuellement bannies avec leurs jails
sudo fail2ban-client status | head -20
# Logs détaillés
sudo journalctl -u fail2ban -n 100
sudo tail -50 /var/log/fail2ban.log
# Distribution géographique (si geoiplookup installé)
for ip in $(sudo fail2ban-client status sshd | grep "Banned IP list" | cut -d: -f2); do
geoiplookup $ip
done | sort | uniq -c | sort -rn | head -20
Pattern fréquent observé : 60-70 % des bans viennent d’une dizaine de pays (CN, RU, US, BR, IN, VN, ID, NL — souvent des hébergeurs cloud « bulletproof » — UA, KR). Cette information peut nourrir une politique géographique de blocage en amont, via le firewall Hetzner Cloud par exemple, qui interdit l’accès SSH depuis certains AS connus pour héberger des botnets. Mais attention : ce blocage géographique ne doit jamais être strict (toujours autoriser l’accès depuis votre propre zone), et doit être combiné avec fail2ban — pas le remplacer.
Erreurs fréquentes
| Erreur | Cause | Solution |
|---|---|---|
| Aucun ban après 24h sur VPS exposé | backend incorrect ou logs SSH redirigés vers journald sans config dédiée | Forcer backend = systemd et installer python3-systemd ; vérifier journalctl -u ssh ne nécessite pas de droits supplémentaires |
| « FAIL2BAN: ERROR No file(s) found » | Fichier de log spécifié dans logpath n’existe pas (ex: Apache au lieu de Nginx) | Adapter logpath à votre serveur web réel |
| Sur VPS très exposé : iptables saturé après quelques jours | Trop de règles individuelles ; perf dégradées | Activer banaction = iptables-ipset-proto6 qui regroupe les bans dans un ipset (perf O(1)) |
| Faux positifs (utilisateur légitime banni) | maxretry trop bas + clé SSH oubliée localement | Augmenter maxretry à 5-6 ; ajouter l’IP statique au ignoreip |
| Action email échoue (« smtp: connection refused ») | Postfix non configuré comme relay ou MTA absent | Configurer Postfix relay (cf. tutoriel mises à jour auto) ou désactiver l’action mwl |
| Bannissements ne survivent pas au redémarrage | Fail2ban ne restaure pas l’état par défaut ; reboot vide les bans | Acceptable en pratique : à chaque reboot, les bots reprennent et sont rebannis dans les minutes |
Adaptation au contexte ouest-africain
Trois aspects pratiques. Premièrement, gérer les IPs dynamiques des opérateurs locaux : les FAI ouest-africains (Sonatel, Orange CI, MTN) attribuent souvent des IPs en CGNAT (Carrier Grade NAT) — des centaines d’utilisateurs partagent une même IP publique. Si votre maxretry est trop bas, un utilisateur qui se trompe 3 fois de mot de passe peut bannir tout son village. Recommandation : maxretry = 5 pour la jail SSH, et toujours documenter une procédure de déblocage manuel par l’admin (whatsapp d’urgence côté ops).
Deuxièmement, intégrer le whitelist des plages connues : si l’équipe technique opère depuis Dakar/Abidjan/Bamako, ajouter les plages d’IP des FAI fixes locaux à ignoreip. Pour Sonatel : 41.214.0.0/16, 196.1.95.0/24. Pour Orange CI : 41.66.0.0/16. Ces plages varient — faire un whois sur l’IP publique de votre bureau pour confirmer. Pour les commerciaux/itinérants, mieux vaut un VPN d’entreprise (WireGuard sur un VPS dédié) qui leur donne une IP fixe d’entrée.
Troisièmement, alterner Fail2ban avec CrowdSec : CrowdSec est un projet français plus récent qui pousse l’idée plus loin : non seulement il bannit localement, mais il partage les IPs malveillantes avec une base communautaire (LAPI), ce qui permet de bénéficier de la détection sur d’autres serveurs avant même qu’on ne soit attaqué. Pour un parc de 10+ VPS, CrowdSec est plus efficace ; pour 1-3 VPS isolés, Fail2ban suffit largement. Voir le tutoriel frère sur l’installation CrowdSec.
Tutoriels frères
- VPS SSH hardening complet : tutoriel 2026 — durcir SSH avant d’ajouter Fail2ban
- VPS mises à jour de sécurité automatiques — couvre la configuration Postfix relay nécessaire aux notifications
- Installer CrowdSec sur VPS Debian/Ubuntu — alternative collaborative à Fail2ban
- Firewall Hetzner Cloud — restreindre l’exposition réseau en amont
FAQ
Fail2ban vs CrowdSec, lequel choisir ?
Fail2ban est éprouvé, simple, fonctionne hors ligne, dépend de zéro service externe — parfait pour un VPS isolé. CrowdSec apporte le partage communautaire de signatures (votre VPS reçoit la liste des 10 millions d’IPs malveillantes connues mondialement) et une UI centralisée pour superviser un parc. Pour 1-3 VPS, Fail2ban. Pour 10+ VPS ou ambition de contribuer/profiter de la base partagée, CrowdSec. Cohabitation possible mais peu courante.
Comment réinitialiser tous les bans ?
Trois options selon le besoin. (1) sudo fail2ban-client unban --all lève tous les bans actifs (Fail2ban 1.0+). (2) sudo fail2ban-client reload recharge la config — préserve les bans actifs. (3) sudo systemctl restart fail2ban redémarre le démon, ce qui vide les bans en mémoire. Cas typique : on veut redémarrer après changement de config ; reload est presque toujours suffisant.
Faut-il bannir IPv6 séparément ?
Non, Fail2ban 1.0+ gère IPv4 et IPv6 conjointement et automatiquement via les actions iptables-multiport et nftables-multiport. Pas de configuration spécifique nécessaire. Vérifier que les filters listent bien <HOST> dans la regex (qui matche IPv4 et IPv6) et non <ADDR> obsolète.
Ma jail compte des « Failed » mais zéro « Banned », pourquoi ?
Trois causes typiques. (1) maxretry n’est pas atteint dans findtime — chaque tentative est isolée. (2) L’IP fautive est dans ignoreip (vérifier avec fail2ban-client get sshd ignoreip). (3) Bug dans l’action de ban (iptables refuse la règle, ipset non créé). Vérifier journalctl -u fail2ban -n 50 pour les erreurs d’action.
Pour aller plus loin
- 🔝 Retour au pilier : VPS hardening sécurité 2026 : guide complet
- Documentation officielle Fail2ban : fail2ban.readthedocs.io
- Wiki Fail2ban (filters, actions, exemples) : github.com/fail2ban/fail2ban/wiki
- WP fail2ban (plugin WordPress) : wordpress.org/plugins/wp-fail2ban
- Tutoriel suivant suggéré : passer à CrowdSec pour la détection communautaire
Mots-clés secondaires : Fail2ban, jail.local, anti-bruteforce, brute force SSH, WordPress wp-login.php protection, iptables ban, nftables, banaction, CrowdSec, journald backend, hardening Linux.