{"id":4508,"date":"2026-09-09T19:32:03","date_gmt":"2026-09-09T17:32:03","guid":{"rendered":"https:\/\/domo.rem81.com\/?p=4508"},"modified":"2026-09-10T09:25:08","modified_gmt":"2026-09-10T07:25:08","slug":"securiser-ses-sous-domaines-exposes-fail2ban-et-crowdsec-en-tandem-derriere-nginx-proxy-manager","status":"publish","type":"post","link":"https:\/\/domo.rem81.com\/index.php\/2026\/09\/09\/securiser-ses-sous-domaines-exposes-fail2ban-et-crowdsec-en-tandem-derriere-nginx-proxy-manager\/","title":{"rendered":"S\u00e9curiser ses sous-domaines expos\u00e9s : Fail2ban et CrowdSec en tandem derri\u00e8re Nginx Proxy Manager"},"content":{"rendered":"\n\n\n\n<h1 class=\"wp-block-heading\">Intro<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Exposer des services sur internet, m\u00eame via un reverse proxy comme Nginx Proxy Manager, c&rsquo;est ouvrir une porte sur le quartier le plus mal fr\u00e9quent\u00e9 du monde. Dans cet article, je d\u00e9taille comment j&rsquo;ai mis en place une double protection \u2014 Fail2ban et CrowdSec \u2014 sur mon LXC NPM, pourquoi les deux se compl\u00e8tent plut\u00f4t que de faire doublon, et comment je surveille tout \u00e7a au quotidien depuis Home Assistant.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Le point de d\u00e9part<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Mon NPM (Nginx Proxy Manager) expose plusieurs sous-domaines sur <code>rem81.com<\/code> : mon blog (domo), ma phototh\u00e8que (Immich), mes statistiques de fr\u00e9quentation (Matomo) et ma station m\u00e9t\u00e9o (WeeWX). Chacun de ces services re\u00e7oit en permanence du trafic non d\u00e9sir\u00e9 : scans automatis\u00e9s, tentatives de connexion, sondage de vuln\u00e9rabilit\u00e9s connues.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Fail2ban \u00e9tait d\u00e9j\u00e0 en place depuis un moment, avec une jail d\u00e9di\u00e9e par sous-domaine. Mais je voulais une protection plus fine, capable de d\u00e9tecter des types d&rsquo;attaques que Fail2ban ne couvre pas (injections, CVE sp\u00e9cifiques, scans de chemins sensibles). C&rsquo;est l\u00e0 qu&rsquo;intervient CrowdSec.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Fail2ban : le sp\u00e9cialiste des \u00e9checs de connexion<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Fail2ban surveille des motifs pr\u00e9cis dans les logs \u2014 typiquement les \u00e9checs de connexion \u00e0 une interface d&rsquo;administration (WordPress, par exemple) \u2014 et bannit l&rsquo;IP via iptables apr\u00e8s un certain nombre d&rsquo;\u00e9checs dans une fen\u00eatre de temps donn\u00e9e.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Chez moi, la jail sur mon blog WordPress est la plus active de loin :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Status for the jail: npm-domo\n|- Filter\n|  |- Currently failed:    6\n|  |- Total failed:        8043\n`- Actions\n   |- Currently banned:    5\n   |- Total banned:        70<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Plus de 8000 tentatives de connexion cumul\u00e9es, 70 IP bannies au total. C&rsquo;est le lot commun de tout WordPress expos\u00e9 sur internet \u2014 les robots essaient en boucle des combinaisons de mots de passe sur <code>wp-login.php<\/code>.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">CrowdSec : la d\u00e9tection comportementale communautaire<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">CrowdSec fonctionne diff\u00e9remment : il installe des <strong>collections<\/strong> de sc\u00e9narios (des r\u00e8gles de d\u00e9tection maintenues par la communaut\u00e9) qui rep\u00e8rent des comportements suspects bien plus vari\u00e9s que \u00ab trop d&rsquo;\u00e9checs de login \u00bb :<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Scan de plusieurs URL diff\u00e9rentes en peu de temps<\/li>\n\n\n\n<li>Tentatives d&rsquo;exploitation de CVE connues (Log4Shell, failles WordPress, Fortinet, etc.)<\/li>\n\n\n\n<li>Injections SQL ou XSS<\/li>\n\n\n\n<li>Sondage de fichiers sensibles (<code>.env<\/code>, <code>\/wp-admin<\/code>&#8230;)<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">L&rsquo;installation se r\u00e9sume \u00e0 deux commandes :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cscli collections install crowdsecurity\/nginx\ncscli collections install crowdsecurity\/base-http-scenarios<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Ces collections embarquent le parser (qui comprend le format des logs Nginx) et l&rsquo;ensemble des sc\u00e9narios de d\u00e9tection. Une fois qu&rsquo;une IP d\u00e9passe le seuil d\u00e9fini par un sc\u00e9nario, CrowdSec cr\u00e9e une d\u00e9cision de bannissement, transmise \u00e0 un <strong>bouncer<\/strong> \u2014 dans mon cas, un bouncer firewall qui traduit la d\u00e9cision en r\u00e8gle iptables.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Le bonus inattendu : la blocklist communautaire<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">En creusant les m\u00e9triques Prometheus de CrowdSec, une surprise : mon bouncer bloquait bien plus d&rsquo;IP que ce que mes propres logs avaient d\u00e9tect\u00e9. La m\u00e9trique <code>cs_active_decisions<\/code> distingue en fait deux origines bien diff\u00e9rentes :<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Origine<\/th><th>IP actuellement bloqu\u00e9es<\/th><th>Explication<\/th><\/tr><\/thead><tbody><tr><td><code>crowdsec<\/code><\/td><td>2<\/td><td>D\u00e9tections issues de mes propres logs (agent local)<\/td><\/tr><tr><td><code>CAPI<\/code><\/td><td>15 470<\/td><td>Blocklist communautaire partag\u00e9e par tous les utilisateurs CrowdSec<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Ces 15 470 IP n&rsquo;ont jamais mis les pieds sur mes serveurs \u2014 elles sont bloqu\u00e9es <strong>pr\u00e9ventivement<\/strong>, parce qu&rsquo;elles ont \u00e9t\u00e9 signal\u00e9es comme malveillantes par d&rsquo;autres utilisateurs CrowdSec ailleurs dans le monde. C&rsquo;est exactement la promesse de l&rsquo;outil : mutualiser la d\u00e9tection entre tous ses utilisateurs pour se prot\u00e9ger d&rsquo;attaques qu&rsquo;on n&rsquo;aurait jamais vues venir soi-m\u00eame.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Pourquoi garder les deux : la preuve par les logs<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">La question s&rsquo;est pos\u00e9e naturellement : Fail2ban et CrowdSec font-ils doublon ? La r\u00e9ponse est non, et j&rsquo;ai pu le v\u00e9rifier concr\u00e8tement. En comparant les IP bannies par les deux outils sur mon blog au m\u00eame moment :<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Fail2ban avait banni<\/strong> : <code>195.178.110.223<\/code>, <code>35.88.92.81<\/code>, <code>64.89.163.227<\/code>, <code>34.123.220.148<\/code>, <code>100.58.205.11<\/code> \u2014 toutes suite \u00e0 des tentatives de connexion WordPress.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>CrowdSec avait banni<\/strong> : <code>160.176.150.31<\/code>, une IP marocaine, pour une tentative d&rsquo;injection XSS (<code>crowdsecurity\/http-xss-probing<\/code>).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Aucun recoupement.<\/strong> Chaque outil a d\u00e9tect\u00e9 un type d&rsquo;attaque que l&rsquo;autre n&rsquo;aurait pas vu. C&rsquo;est la meilleure d\u00e9monstration que ce n&rsquo;est pas de la redondance, mais deux lignes de d\u00e9fense compl\u00e9mentaires.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Un pi\u00e8ge d&rsquo;installation \u00e0 conna\u00eetre<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Si votre Nginx Proxy Manager tourne en installation native (pas via Docker), le bouncer CrowdSec d\u00e9di\u00e9 \u00e0 Nginx (bas\u00e9 sur un module Lua) ne fonctionnera pas \u2014 il n\u00e9cessite OpenResty, que NPM n&rsquo;utilise pas par d\u00e9faut. La solution : passer par le <strong>bouncer firewall<\/strong> (<code>crowdsec-firewall-bouncer-iptables<\/code>), qui bloque au niveau du syst\u00e8me plut\u00f4t qu&rsquo;au niveau applicatif. Le r\u00e9sultat est identique \u2014 la requ\u00eate n&rsquo;atteint jamais sa destination \u2014 mais l&rsquo;installation est plus simple et ind\u00e9pendante de la stack web utilis\u00e9e.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>apt install crowdsec-firewall-bouncer-iptables -y<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Attention aussi \u00e0 l&#8217;emplacement r\u00e9el des logs si vous utilisez NPM en dehors de Docker : ils ne sont pas forc\u00e9ment dans <code>\/var\/log\/nginx\/<\/code>, mais dans <code>\/data\/logs\/proxy-host-X_access.log<\/code>. Un petit <code>find \/ -newermt \"-10 minutes\"<\/code> permet de rep\u00e9rer rapidement les fichiers qui re\u00e7oivent effectivement du trafic.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Le cas particulier de Home Assistant : Nabu Casa plut\u00f4t qu&rsquo;un sous-domaine<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Pour l&rsquo;acc\u00e8s distant \u00e0 HA, j&rsquo;ai fait un autre choix : l&rsquo;abonnement <strong>Home Assistant Cloud<\/strong>, propos\u00e9 par Nabu Casa.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Les avantages sont nombreux par rapport \u00e0 une exposition classique via reverse proxy :<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Aucun port \u00e0 ouvrir<\/strong> sur la Freebox \u2014 le flux part de chez moi vers Nabu Casa, jamais l&rsquo;inverse<\/li>\n\n\n\n<li><strong>Chiffrement de bout en bout<\/strong> : m\u00eame Nabu Casa ne peut pas lire mes donn\u00e9es<\/li>\n\n\n\n<li><strong>Certificat SSL et s\u00e9curit\u00e9 g\u00e9r\u00e9s automatiquement<\/strong>, sans avoir \u00e0 s&rsquo;en occuper soi-m\u00eame<\/li>\n\n\n\n<li><strong>Int\u00e9gration Google Assistant et Amazon Alexa<\/strong> en quelques clics<\/li>\n\n\n\n<li><strong>Sauvegarde cloud<\/strong> de la configuration, restaurable d\u00e8s le premier d\u00e9marrage sur un nouveau mat\u00e9riel<\/li>\n\n\n\n<li>Support des <strong>webhooks<\/strong> et d&rsquo;un WebRTC am\u00e9lior\u00e9<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Pour quelques euros par mois, je m&rsquo;\u00e9vite ainsi toute la m\u00e9canique de s\u00e9curisation qu&rsquo;on vient de d\u00e9tailler pour mes autres sous-domaines \u2014 et accessoirement, je participe au financement de l&rsquo;<strong>Open Home Foundation<\/strong>, la fondation \u00e0 but non lucratif dont d\u00e9pendent Home Assistant et ESPHome. Nabu Casa reverse une partie de ses revenus au d\u00e9veloppement de ces projets open source : un abonnement Cloud, c&rsquo;est autant un service pratique qu&rsquo;un petit geste de soutien \u00e0 un travail formidable.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Superviser sans multiplier les interfaces<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Une fois la protection en place, la vraie question devient : comment savoir que tout fonctionne, sans devoir taper des commandes <code>cscli<\/code> ou <code>fail2ban-client<\/code> \u00e0 chaque fois ?<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">J&rsquo;ai choisi d&rsquo;exposer les m\u00e9triques des deux outils via Prometheus :<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>CrowdSec expose nativement un endpoint <code>\/metrics<\/code> (\u00e0 activer dans <code>\/etc\/crowdsec\/config.yaml<\/code>)<\/li>\n\n\n\n<li>Fail2ban n&rsquo;a pas cet export natif, mais un exporter d\u00e9di\u00e9 (<code>fail2ban_exporter<\/code>) fait le pont via le socket Fail2ban<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Prometheus collecte ces deux endpoints, puis alimente deux endroits :<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Grafana<\/strong>, qui l&rsquo;utilise comme source de donn\u00e9es pour une vue d\u00e9taill\u00e9e et historique<\/li>\n\n\n\n<li><strong>Home Assistant<\/strong>, via des capteurs REST qui interrogent directement l&rsquo;API Prometheus, pour avoir la s\u00e9curit\u00e9 de mes sites au m\u00eame endroit que le reste de ma maison connect\u00e9e<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">Exemple de capteur HA pour le nombre de bans actifs sur mon blog :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>rest:\n  - resource: \"http:\/\/&lt;ip_prometheus&gt;:9090\/api\/v1\/query?query=f2b_jail_banned_current{jail=%22npm-domo%22}\"\n    method: GET\n    scan_interval: 60\n    sensor:\n      - name: \"Fail2ban Domo Bans Actifs\"\n        value_template: \"{{ value_json.data.result&#091;0].value&#091;1] | default(0) | int }}\"\n        state_class: measurement<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Un d\u00e9tail qui m&rsquo;a fait tiquer au d\u00e9but : Prometheus stocke toutes ses valeurs en <code>float<\/code>, y compris des compteurs entiers comme un nombre de bans. Le <code>| int<\/code> dans le template s&rsquo;occupe de la conversion \u00e0 l&rsquo;affichage. Et pour que ces capteurs g\u00e9n\u00e8rent de vraies courbes exploitables dans l&rsquo;historique (au lieu d&rsquo;une simple frise d&rsquo;\u00e9tats), il faut penser \u00e0 leur donner un <code>state_class<\/code> adapt\u00e9 \u2014 <code>measurement<\/code> pour une valeur qui monte et descend, <code>total_increasing<\/code> pour un compteur qui ne fait qu&rsquo;augmenter.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Ce que \u00e7a donne au quotidien<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Aujourd&rsquo;hui, une simple page dans mon dashboard Home Assistant me suffit pour voir d&rsquo;un coup d&rsquo;\u0153il :<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Si l&rsquo;un des deux services est en panne (<code>f2b_up<\/code>, <code>cs_info<\/code>)<\/li>\n\n\n\n<li>Combien d&rsquo;IP sont actuellement bannies, par site et par outil<\/li>\n\n\n\n<li>Le volume de tentatives d\u00e9tect\u00e9es, en cumul\u00e9 et sur la derni\u00e8re heure<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">La s\u00e9curit\u00e9 de mes sites est d\u00e9sormais un indicateur comme un autre, au m\u00eame titre que ma consommation \u00e9lectrique ou la m\u00e9t\u00e9o du jour \u2014 plus besoin de sortir le terminal SSH pour sentir qu&rsquo;un chiffre d\u00e9rape.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">En r\u00e9sum\u00e9<\/h1>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Fail2ban<\/strong> reste pertinent pour surveiller finement les \u00e9checs de connexion applicatifs (login WordPress, etc.)<\/li>\n\n\n\n<li><strong>CrowdSec<\/strong> couvre un spectre de menaces bien plus large (CVE, injections, scans), avec en prime une intelligence communautaire<\/li>\n\n\n\n<li>Les deux peuvent tourner en parall\u00e8le sans conflit ni vrai doublon<\/li>\n\n\n\n<li>Exposer leurs m\u00e9triques via Prometheus permet de tout centraliser, y compris dans Home Assistant, sans multiplier les interfaces \u00e0 surveiller<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"><em>Article r\u00e9dig\u00e9 avec l&rsquo;assistance de Claude AI (Anthropic) \u00e0 partir de mes donn\u00e9es et retours d&rsquo;exp\u00e9rience.<\/em><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Intro Exposer des services sur internet, m\u00eame via un reverse proxy comme Nginx Proxy Manager, c&rsquo;est ouvrir une porte sur le quartier le plus mal fr\u00e9quent\u00e9 du monde. Dans cet &hellip; <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4,98,68],"tags":[104,101],"class_list":["post-4508","post","type-post","status-publish","format-standard","hentry","category-homeassistant","category-linux","category-proxmox","tag-graphana","tag-securite"],"_links":{"self":[{"href":"https:\/\/domo.rem81.com\/index.php\/wp-json\/wp\/v2\/posts\/4508","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/domo.rem81.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/domo.rem81.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/domo.rem81.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/domo.rem81.com\/index.php\/wp-json\/wp\/v2\/comments?post=4508"}],"version-history":[{"count":14,"href":"https:\/\/domo.rem81.com\/index.php\/wp-json\/wp\/v2\/posts\/4508\/revisions"}],"predecessor-version":[{"id":4522,"href":"https:\/\/domo.rem81.com\/index.php\/wp-json\/wp\/v2\/posts\/4508\/revisions\/4522"}],"wp:attachment":[{"href":"https:\/\/domo.rem81.com\/index.php\/wp-json\/wp\/v2\/media?parent=4508"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/domo.rem81.com\/index.php\/wp-json\/wp\/v2\/categories?post=4508"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/domo.rem81.com\/index.php\/wp-json\/wp\/v2\/tags?post=4508"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}