CrowdSec n'est pas un fail2ban moderne. C'est un système où chaque serveur détecte localement ce qui l'attaque et partage le signal avec une communauté, qui à son tour redistribue les bans validés à tous ses membres. Quand une IP frappe un blog en Australie, elle peut être bannie sur le serveur d'un solo dev français dans la minute, avant même d'avoir tenté quoi que ce soit chez lui. Ce billet explique la mécanique. Le déploiement concret viendra dans le prochain.
Le problème que fail2ban ne résout plus
Fail2ban a été conçu en 2004. Le modèle est simple : on parse ses propres logs, on détecte un pattern d'attaque (cinq mauvais mots de passe SSH consécutifs), on ban l'IP coupable dans iptables pour une durée arbitraire.
Trois limites apparaissent quand on regarde un serveur public en 2026.
Première : chaque serveur est isolé. Si une IP malicieuse passe trois mois à scanner des milliers de WordPress avant d'arriver chez toi, fail2ban ne le sait pas. Elle obtient ses cinq tentatives pleines avant que tu réagisses. La défense n'apprend rien des autres victimes.
Deuxième : les attaques se distribuent. Un botnet moderne tape avec dix mille IPs différentes, chacune faisant deux ou trois requêtes seulement avant de tourner. Les seuils fail2ban (« cinq tentatives en une minute ») ne déclenchent jamais. Le motif global est invisible depuis un seul serveur.
Troisième : le coût de la détection est silencieux. Avant que ton fail2ban ne se déclenche, les paquets sont passés par tout le stack : kernel, firewall, Caddy, PHP-FPM, peut-être même Symfony qui a booté pour répondre 404. Pour un site éditorial avec quelques milliers de visites jour, cinq tentatives d'attaque par minute c'est un bruit gérable. Pour un service plus exposé, ça devient un poste de coût.
Le modèle CrowdSec
CrowdSec sépare proprement trois rôles que fail2ban confondait.
L'Agent est un processus local qui lit les logs (Caddy, nginx, auth.log SSH, …) et applique des scenarios — des fichiers YAML qui décrivent un pattern d'attaque (« 5 erreurs 401 en 30 secondes depuis la même IP, sur le path /wp-login.php »). Quand un scenario match, l'Agent émet une décision : ban cette IP pour 4 heures.
Le Bouncer est un composant indépendant qui consomme les décisions et les applique. Il peut vivre à plusieurs endroits du stack :
- Au niveau réseau (nftables, iptables) — drop les paquets avant qu'ils n'atteignent l'app.
- Au niveau reverse proxy (Caddy, nginx, HAProxy, Cloudflare) — renvoie un 403 / une page captcha avant de proxifier.
- Au niveau application (WordPress, Drupal) — bloque à la couche métier.
L'Agent et le Bouncer communiquent via une LAPI (Local API) — un endpoint HTTP qui tourne sur la même machine ou en LAN.
La CAPI (Central API) est la vraie nouveauté. C'est un service mutualisé hébergé par CrowdSec SAS. Chaque Agent enrôlé y pousse ses propres décisions (anonymisées) et pull en retour la community blocklist — une liste d'IPs blacklistées qui a passé le filtre de consensus.
Le consensus communautaire, en pratique
Pousser une décision à la CAPI ne suffit pas pour bannir une IP chez les autres. La CAPI attend qu'N instances indépendantes signalent la même IP, sur des scenarios cohérents, avant de l'inscrire dans la blocklist distribuée. Le nombre exact est ajusté par CrowdSec selon le scenario, mais la philosophie est claire : un seul faux positif d'un seul serveur ne propage pas.
Ce filtre élimine deux risques classiques :
- Un dev qui se trompe d'IP en testant son scenario custom et qui bannerait son IP perso pour la planète.
- Un acteur malicieux qui essaierait de polluer la blocklist mondiale en signalant les IPs de Google, Cloudflare ou GitHub pour casser le web.
Le consensus rend l'attaque sur la CAPI elle-même coûteuse : il faudrait opérer des dizaines d'instances coordonnées en signalant la même IP avec un timing crédible.
Ce que ton serveur partage et ce qu'il garde
Quand on parle de « signal partagé », il est utile de regarder précisément ce qui transite vers la CAPI :
- L'IP malicieuse elle-même.
- Le scenario qui a match (
crowdsecurity/ssh-bf,crowdsecurity/http-bad-user-agent, etc.). - La durée de la décision et son type (ban, captcha, …).
- Des métadonnées : ASN de l'IP, géoloc grossière, horodatage.
Ce qui n'est pas envoyé :
- Le contenu des requêtes ou des logs (les payloads HTTP, les noms d'utilisateurs SSH testés, etc.).
- L'IP du serveur qui signale.
- Aucune donnée d'utilisateur légitime.
C'est une trace minimale, suffisante pour identifier l'attaquant, mais pas pour reconstituer ce qui se passe chez l'opérateur.
L'écosystème : agents, bouncers, hub
CrowdSec n'est pas qu'un binaire. C'est un écosystème ouvert.
Le Hub (hub.crowdsec.net) est une marketplace open source des scenarios, parsers et collections. Chaque scenario est un fichier YAML versionné, auditable. On installe ce dont on a besoin avec cscli collections install crowdsecurity/sshd, cscli scenarios install crowdsecurity/http-wordpress-scan, etc. Tout est auditable, ligne par ligne.
Les bouncers existent pour la plupart des reverse proxies et frameworks web : Caddy, nginx, Apache, Traefik, HAProxy, Cloudflare, AWS WAF, WordPress, Wazuh, et bien sûr crowdsec-firewall-bouncer qui pose des règles directement dans iptables ou nftables. Chacun est un binaire dédié qui s'enregistre auprès de la LAPI avec une clé d'API et qui pull les décisions à intervalle régulier (toutes les 10 secondes par défaut).
Le code de l'agent est en Go, sous licence MIT. La société CrowdSec est française, basée à Paris. Le modèle économique est freemium : le moteur, les scenarios communautaires, les bouncers, et la CAPI sont gratuits. Les offres payantes ciblent l'entreprise : feed CTI premium, console centralisée multi-instances, support SLA. Le solo dev qui héberge son blog n'a aucune raison de payer.
Ce que ça change pour un petit serveur public
La défense d'un VPS solo aujourd'hui n'a plus besoin d'attendre d'être attaquée pour banner. Au moment où l'agent démarre, il pull les dizaines de milliers d'IPs déjà connues comme malicieuses depuis la CAPI. Le firewall les pose immédiatement. Un scraper qui a déjà fait du bruit ailleurs n'arrive même pas jusqu'à Caddy.
La détection locale, elle, sert deux choses : protéger contre les IPs nouvelles (que la communauté n'a pas encore signalées) et alimenter le pool commun. Chaque ban prononcé localement contribue au signal qui finira par protéger d'autres.
La suite
Cette mécanique posée, le prochain billet documentera l'installation concrète sur Symfony + FrankenPHP + Docker Compose. On verra les pièges quand l'agent et le reverse proxy vivent dans des containers séparés.