Sécuriser Caddy avec CrowdSec : Mon setup Podman

Dans mon homelab Fedora + Podman, j’ai remplacé Fail2ban par CrowdSec pour protéger Caddy. Voici comment je l’ai configuré, étape par étape.

Caddy + CrowdSec : Pourquoi ce duo ?

Caddy, c’est le serveur web moderne écrit en Go, ultra-simple à configurer (adieu les galères Nginx/Apache !).

CrowdSec ? Un IPS open-source communautaire qui détecte les attaques via les logs et partage les IP malveillantes en temps réel. Il bloque proactivement grâce à ses “collections” de règles et son réseau communautaire.

Pourquoi les associer ? Fail2ban faisait le job de base (ban IP après X tentatives de login foireuses), mais il est réactif seulement. CrowdSec ajoute l’intelligence collective : il connaît déjà 99% des bots/scans avant qu’ils touchent vos logs.

Résultat : sécurité proactive, zéro config compliquée, et scalable pour mon setup conteneurisé.

Documentation officielle : Secure Caddy with CrowdSec

Installation : CrowdSec sur l’hôte

Mon Caddy tourne en Podman rootless (réseau isolé). CrowdSec ? Directement sur l’hôte Fedora pour monitorer pods + services natifs.

1. CrowdSec sur Fedora

curl -s https://install.crowdsec.net | sudo sh
sudo dnf install crowdsec

Autres distros : Doc officielle.

2. Caddy custom avec xcaddy

J’utilise un Dockerfile Podman pour compiler Caddy avec les plugins CrowdSec :

FROM caddy:2.11.2-builder-alpine AS builder

RUN --mount=type=cache,target=/go/pkg/mod \
    --mount=type=cache,target=/root/.cache/go-build \
    xcaddy build \
    --with github.com/caddy-dns/duckdns \
    --with github.com/mholt/caddy-l4 \
    --with github.com/caddyserver/transform-encoder \
    --with github.com/hslatman/caddy-crowdsec-bouncer/http@main \
    --with github.com/hslatman/caddy-crowdsec-bouncer/appsec@main \
    --with github.com/hslatman/caddy-crowdsec-bouncer/layer4@main

FROM caddy:2.11.2-alpine
COPY --from=builder /usr/bin/caddy /usr/bin/caddy

Build :

podman build -t localhost/caddy-custo:latest .

Connexion API : Premier test de ban

Clé API

cscli bouncers add caddy-bouncer
# Notez bien la clé générée !

Astuce Podman : Mes conteneurs ne voient pas 127.0.0.1 de l’hôte. Je configure donc CrowdSec sur 0.0.0.0 :

# /etc/crowdsec/config.yaml
api:
  listen_addr: 0.0.0.0:8080  # ⚠️ Sécurisez vos ports !

Caddyfile de base

{
  crowdsec {
    api_url http://host.containers.internal:8080
    api_key VOTRE_CLE
    ticker_interval 15s
  }
}

ton_site.com {
  route {
    crowdsec
    respond "✅ CrowdSec OK!" 200
  }
}

Test fatidique :

cscli decisions add -i $(hostname -I | awk '{print $1}') -d 2m
curl ton_site.com  # → 403 Forbidden !

Logs Caddy : "initializing streaming bouncer". Parfait ✅

Parser les logs Caddy

Collection Caddy

cscli collections install crowdsecurity/caddy

Logs + acquis.yaml

Caddyfile :

{
  # ... crowdsec ...
  log {
    output file /var/log/caddy/access.log {
      roll_size 30MiB
      roll_keep 5
    }
  }
}

Acquisition :

# /etc/crowdsec/acquis.d/caddy.yaml
filename: /var/log/caddy/access.log
labels:
  type: caddy
systemctl restart crowdsec
cscli metrics show acquisition  # Parsing actif !

AppSec : Protection L7 avancée

AppSec = WAF-like intégré. Bloque SQLi, XSS, path traversal avant que l’app les voie.

Collections

cscli collections install crowdsecurity/appsec-virtual-patching crowdsecurity/appsec-generic-rules

Config AppSec

# /etc/crowdsec/acquis.d/appsec.yaml
appsec_config: crowdsecurity/appsec-default
labels:
  type: appsec
listen_addr: 0.0.0.0:7422
source: appsec
systemctl restart crowdsec

Caddyfile :

{
  crowdsec {
    # ...
    appsec_url http://host.containers.internal:7422
  }
}

ton_site.com {
  route {
    crowdsec    # IP ban d'abord
    appsec      # Puis règles L7
    respond "🚀 Tout est sécurisé !" 200
  }
}

Test : curl ton_site.com/.envBloqué instantanément par vpatch-env-access !

cscli alerts list  # Confirmez l'alerte

Mon Caddyfile de prod (extrait)

{
  order crowdsec first
  crowdsec {
    api_url http://host.containers.internal:8080
    api_key {env.CROWDSEC_API_KEY}
    appsec_url http://host.containers.internal:7422
    ticker_interval 15s
  }
  log { /* ... */ }
}

# Snippets réutilisables
(crowdsec_protect) {
  route { crowdsec appsec }
}
(lan_only) {
  @denied not remote_ip private_ranges VOS_RANGES_IPV6
  abort @denied
}

# Exemples
exemple.com {
  import lan_only logging crowdsec_protect
  reverse_proxy ton_pod:8096
}

Verdict

CrowdSec > Fail2ban pour un homelab moderne :

  • Communautaire & proactif
  • AppSec L7 gratuit
  • Podman-friendly
  • Logs → Alertes → Bans automatisés

Merci d’avoir lu. Config perso, adaptez à votre stack.

Ka-chow 🚀


Page last modified: Mar 22 2026 at 12:00 AM.