CrowdSec est souvent présenté comme « un fail2ban moderne ». C’est vrai en partie : lui aussi lit les journaux et bannit les adresses agressives. Mais il ajoute deux choses que fail2ban n’a pas. D’abord des scénarios plus riches, qui reconnaissent un balayage de chemins web, une attaque par dictionnaire répartie ou une exploitation de faille connue, au lieu de simplement compter des lignes. Ensuite une intelligence collective : chaque installation signale les attaques qu’elle voit, et reçoit en échange une liste d’adresses déjà repérées ailleurs, bloquées avant même qu’elles ne vous atteignent.
Ce tutoriel installe CrowdSec 1.8 sur Rocky Linux, le branche sur SSH et nginx, ajoute le blocage au pare-feu, et surtout explique comment ne pas se bannir soi-même, ce qui arrive plus vite qu’on ne le croit.

Les pièces du puzzle
| Élément | Rôle |
|---|---|
l’agent (crowdsec) | lit les journaux, les découpe (parseurs), les compare à des scénarios, émet des alertes |
| l’API locale (LAPI) | stocke les alertes et transforme celles qui le méritent en décisions (bannir telle adresse pendant tant de temps) |
| les bouncers | appliquent les décisions : pare-feu (nftables), serveur web, application |
| l’API centrale (CAPI) | reçoit vos signalements et vous renvoie une liste de blocage communautaire |
| le Hub | le catalogue de collections, parseurs, scénarios, installés par cscli |
Point important : l’agent ne bloque rien. Sans bouncer, CrowdSec détecte et prend des décisions, mais personne ne les applique. C’est l’erreur la plus fréquente lors d’une première installation.
Étape 1 : installer
CrowdSec publie ses paquets dans un dépôt packagecloud. Déclarez-le dans /etc/yum.repos.d/ avec sa clé GPG, puis :
dnf install crowdsec crowdsec-firewall-bouncer-nftables systemctl enable --now crowdsec cscli version
À l’installation, le paquet détecte les services présents (sshd, nginx…) et installe les collections correspondantes. Vérifiez ce qu’il a choisi plutôt que de lui faire confiance : cscli collections list.
Une précaution sur Debian : si vous installez CrowdSec et le bouncer dans la même commande sur une machine neuve, le script post-installation du bouncer peut s’exécuter avant que l’API locale soit prête et échouer à s’enregistrer. Installez l’agent, attendez qu’il démarre, puis le bouncer.
Étape 2 : les collections
cscli collections install crowdsecurity/linux crowdsecurity/sshd crowdsecurity/nginx cscli collections list systemctl reload crowdsec
Une collection regroupe des parseurs (qui savent lire un format de journal) et des scénarios (qui reconnaissent un comportement). crowdsecurity/nginx apporte ainsi le parseur des journaux nginx et une dizaine de scénarios : balayage de chemins, sondes de fichiers sensibles, recherche de pages d’administration, robots agressifs. Les collections se mettent à jour avec cscli hub update && cscli hub upgrade, à planifier chaque jour.
Étape 3 : dire à CrowdSec quoi lire
La configuration d’acquisition liste les sources et leur type, qui détermine le parseur utilisé :
# /etc/crowdsec/acquis.yaml filenames: - /var/log/secure labels: type: syslog --- filenames: - /var/log/messages labels: type: syslog --- # /etc/crowdsec/acquis.d/nginx.yaml filenames: - /var/log/nginx/*.log labels: type: nginx
- labels.type :
syslogpour les journaux système (sshd y écrit sur Rocky Linux),nginxpour les journaux d’accès et d’erreur ; - une source, une seule fois : le piège classique est de lister les journaux nginx à la fois dans
acquis.yamlet dansacquis.d/nginx.yaml. Chaque ligne est alors lue deux fois, chaque compteur double, et les bannissements tombent deux fois plus vite. Même chose avec une sourcejournalctlajoutée par-dessus un fichier qui contient déjà les mêmes lignes ; - les jokers (
*.log) suivent automatiquement les nouveaux sites.
Pour vérifier que les lignes sont bien lues et comprises :
cscli metrics show acquisition parsers # « Lines read » augmente, « Lines parsed » aussi ; beaucoup de « unparsed » = mauvais type
Étape 4 : ne jamais se bannir soi-même
C’est la partie à ne pas sauter. CrowdSec n’a aucune raison de faire confiance à vos propres adresses : un script de supervision qui reçoit une série de 403, des tests de connexion SSH ratés, un serveur qui interroge ses propres noms publics… et c’est votre propre serveur que le pare-feu rejette. J’ai vécu les deux : un serveur qui s’est banni lui-même pour « sondage HTTP », et un autre qui a banni l’adresse de mon poste d’administration pendant que je vérifiais des clés SSH.
La parade est une liste blanche locale, en parseur d’enrichissement :
# /etc/crowdsec/parsers/s02-enrich/local-whitelist.yaml
name: local/whitelists
description: "Adresses de confiance, jamais bannies"
whitelist:
reason: "mes serveurs et mon adresse d'administration"
ip:
- "203.0.113.25"
- "203.0.113.40"
- "2001:db8::25"
- "198.51.100.10"
Mettez-y les adresses publiques de chacun de vos serveurs (IPv4 et IPv6), celle de votre accès d’administration, et déployez la même liste sur toutes vos machines : une infrastructure personnelle où chaque serveur peut bannir les autres finit toujours par le faire. Une liste blanche s’applique avant les scénarios, donc aucune alerte n’est créée. Elle ne supprime pas une décision déjà prise : cscli decisions delete --ip … s’en charge.
Étape 5 : appliquer les décisions au pare-feu
cscli bouncers add firewall-bouncer # affiche une clé à copier dans la configuration du bouncer
# /etc/crowdsec/bouncers/crowdsec-firewall-bouncer.yaml (extrait)
mode: nftables
update_frequency: 10s
api_url: http://127.0.0.1:8081/
api_key: <clé générée par cscli bouncers add>
disable_ipv6: false
deny_action: DROP
supported_decisions_types:
- ban
nftables:
ipv4:
enabled: true
table: crowdsec
ipv6:
enabled: true
table: crowdsec6
- mode: nftables : le bouncer crée ses propres tables et ensembles nftables, indépendants de firewalld. Les adresses bannies sont dans un ensemble, ce qui reste rapide même avec des dizaines de milliers d’entrées ;
- update_frequency : toutes les 10 secondes, le bouncer récupère les nouvelles décisions ;
- deny_action: DROP : le paquet est jeté sans réponse, l’attaquant n’apprend rien ;
- IPv6 : à activer explicitement ; un attaquant qui dispose des deux familles d’adresses passera sinon par celle qui n’est pas filtrée.
systemctl enable --now crowdsec-firewall-bouncer cscli bouncers list # « Last API pull » récent nft list set ip crowdsec crowdsec-blacklists-cscli | head
Étape 6 : la liste communautaire
cscli capi register cscli console enroll <clé de la console> # facultatif : tableau de bord web cscli capi status systemctl restart crowdsec
Une fois inscrit, l’agent envoie les alertes de ses scénarios (adresse et scénario, jamais le contenu des journaux) et reçoit une liste de blocage d’environ quinze mille adresses, renouvelée régulièrement. C’est elle qui fait la plus grande différence au quotidien : la majorité des robots qui balaient Internet sont bloqués dès leur premier paquet. cscli decisions list --origin CAPI | wc -l donne la taille de la liste reçue.
Simuler avant de bannir
Un nouveau scénario, ou un nouveau serveur, peut être placé en simulation : les alertes et décisions sont enregistrées mais marquées « simulées », et aucun bouncer ne les applique. C’est le moyen de voir ce que CrowdSec aurait bloqué pendant une semaine, avant de lui confier la porte.
cscli simulation enable crowdsecurity/http-crawl-non_statics cscli simulation status cscli alerts list --since 24h
Exploiter au quotidien
cscli alerts list --since 24h # ce qui a été détecté cscli decisions list # qui est banni, pour combien de temps cscli decisions delete --ip 192.0.2.66 # lever un bannissement cscli decisions add --ip 192.0.2.99 --duration 72h --reason 'manuel' cscli metrics # sources, parseurs, scénarios, bouncers
La durée de bannissement par défaut est de quatre heures ; un profil (profiles.yaml) permet de l’allonger pour les récidivistes ou de bannir une plage entière plutôt qu’une adresse.
Une heure d’un vrai serveur
Voici les chiffres de mon serveur web le 2 novembre 2026, 75 minutes après un redémarrage (CrowdSec 1.8.2, bouncer nftables 0.0.36). D’abord, ce que l’agent a lu :
# cscli metrics show acquisition (extrait) | Source | Lines read | Lines parsed | Lines unparsed | Poured to bucket | Whitelisted | | appsec:appsec | 136 | 136 | - | 136 | - | | file:/var/log/messages | 1.31k | - | 1.31k | - | - | | file:/var/log/nginx/access.blog...log | 716 | 716 | - | 42 | 528 | | file:/var/log/nginx/access.log | 8.03k | 8.03k | - | 4.29k | 1.76k |
- Lines parsed égal à Lines read sur les journaux web : l’analyseur nginx comprend chaque ligne. Si un journal affiche tout en unparsed, la collection manque ou le format du journal a été personnalisé.
- /var/log/messages : 1,31k non analysées : c’est attendu. La plupart des lignes du journal système ne sont pas des événements de sécurité et aucun analyseur ne les revendique. Les événements SSH et d’authentification sont dans /var/log/secure, lu par sa propre entrée : 3 020 lignes lues, 39 analysées, et c’est bien le but, puisque seules les connexions échouées et événements semblables intéressent CrowdSec.
- Whitelisted : les requêtes venant de mes propres adresses et des sondes de supervision, ignorées volontairement (étape 4).
- Poured to bucket : les lignes qui ont alimenté un scénario. La plupart ne deviennent jamais une alerte ; un compteur ne déborde que si le comportement se répète.
Puis le côté application des décisions, la seule preuve que la protection est réelle :
# cscli bouncers list crowdsec-firewall-bouncer 127.0.0.1 ✔️ 2026-11-02T10:21:35+01:00 v0.0.36 openresty-appsec 127.0.0.1 ✔️ 2026-11-02T10:21:37+01:00 v1.2.3 # nft list set ip crowdsec crowdsec-blacklists-CAPI → 14 752 adresses IPv4 # nft list set ip6 crowdsec6 crowdsec6-blacklists-CAPI → 475 adresses IPv6 # nft list set ip crowdsec crowdsec-blacklists-crowdsec → 8 décisions locales # nft list counters table ip crowdsec counter processed packets 1286809 counter crowdsec-blacklists-CAPI packets 387 → jetés counter crowdsec-blacklists-crowdsec packets 60 → jetés
En 75 minutes, 447 paquets ont été jetés avant d’atteindre le moindre service : 387 venaient d’adresses de la liste communautaire, qui n’avaient jamais touché ce serveur, et 60 des 8 adresses bannies par mon propre serveur. La colonne Last API pull, vieille de quelques secondes pour les deux bouncers, est la première chose à regarder quand les bannissements semblent sans effet : un bouncer qui ne tire plus continue d’appliquer une liste figée.
Allonger les bannissements : profiles.yaml
Le profil décide de ce qu’une alerte devient. Celui par défaut bannit l’adresse quatre heures. On peut le rendre progressif, comme avec fail2ban, en s’appuyant sur le nombre de décisions déjà prises contre cette adresse :
# /etc/crowdsec/profiles.yaml
name: default_ip_remediation
filters:
- Alert.Remediation == true && Alert.GetScope() == "Ip"
decisions:
- type: ban
duration: 4h
duration_expr: Sprintf('%dh', (GetDecisionsCount(Alert.GetValue()) + 1) * 4)
on_success: break
- filters : le profil ne s’applique qu’aux alertes qui demandent une sanction et visent une adresse IP ;
- duration_expr : 4 heures la première fois, 8 la deuxième, 12 la troisième… Le calcul utilise l’historique de l’API locale, il survit donc aux redémarrages ;
- on_success: break : les profils suivants ne sont pas évalués. L’ordre des profils dans le fichier compte.
Les pannes fréquentes
Rien n’est jamais banni
Vérifiez dans l’ordre : cscli metrics show acquisition (les lignes sont-elles lues ?), cscli metrics show parsers (sont-elles comprises ?), cscli alerts list (des scénarios se déclenchent-ils ?), cscli bouncers list (le bouncer récupère-t-il les décisions ?). La panne se trouve presque toujours à la première étape qui reste à zéro.
Le bouncer ne démarre pas, « unauthorized »
La clé du bouncer ne correspond à aucune clé de l’API locale, typiquement après une réinstallation qui a vidé la base. Supprimez l’entrée (cscli bouncers delete), recréez-la et recopiez la clé.
L’API locale écoute sur un port déjà pris
Par défaut, l’API locale écoute sur 8080, port très demandé. Changez-le dans config.yaml (api.server.listen_uri) et dans local_api_credentials.yaml, puis dans la configuration de chaque bouncer. Oublier l’un des trois produit un agent qui démarre mais dont les outils ne parlent plus à personne.
Les parseurs ne comprennent pas les journaux SSH
Les versions récentes d’OpenSSH journalisent sous le nom sshd-session au lieu de sshd. Si les parseurs ne suivent pas, les échecs d’authentification ne sont plus reconnus du tout. Un cscli hub upgrade règle généralement la question ; vérifiez avec cscli explain --file /var/log/secure --type syslog, qui montre, ligne par ligne, quel parseur l’a acceptée ou rejetée.
Mettre à jour sans surprise
- mettez à jour l’agent, puis vérifiez
cscli metricset la liste des bouncers avant de toucher aux bouncers ; - lisez les notes de version : la version 1.8 a par exemple changé la façon dont certains champs sont exposés aux scénarios ;
- après une mise à jour du hub, un
cscli hub upgradepeut remplacer un scénario que vous aviez modifié à la main. Les modifications locales vont dans des fichiers à vous, jamais dans ceux du hub.
Et fail2ban ?
Les deux cohabitent sans problème, et c’est ce que je fais : fail2ban réagit vite à mes propres journaux avec des bannissements progressifs, CrowdSec ajoute les scénarios web et la liste communautaire. Seul inconvénient : quand une adresse reste bloquée, il faut regarder des deux côtés (fail2ban-client status et cscli decisions list).
Pour aller plus loin
CrowdSec sait aussi inspecter le contenu des requêtes HTTP grâce à son moteur AppSec, et bloquer les attaques directement dans le serveur web. C’est l’objet d’un prochain tutoriel de cette série, avec OpenResty.
Ce billet fait partie d’une série de tutoriels d’administration système (tag « pas à pas »).
Série « Pas à pas », tutoriel 9/77. · ← Tutoriel précédent · Tutoriel suivant → (à paraître le 9/11) · English version








