I need help to mix Caddy + Coraza + Crowdsec in a reverse proxy configuration

1. The problem I’m having:

Hello again :slight_smile:

After having caddy working as a reverse proxy +coraza I decided to try adding crowdsec.

Crowdsec is working as a separate service and I have installed the caddy-bouncer with xcaddy.
Everything is connected and working, the problem is when I try to aggregate everything into one of my services.

According to the caddy-bouncer documentation you have to use routes but when I do it the application stops working.
I see the connection in the caddy log and it seems correct but it doesn’t work well, the app still gives me a connection error.

I have commented on both the crowdsec and coraza parts and I am very clear that the problem is the route but I don’t know how to integrate everything together without that directive.

Apart from the caddy log that I put below, I don’t see anything in the armor or crowdsec log.

I hope I have explained myself well and thank you in advance.

2. Error messages and/or full log output:

{"level":"info","ts":1755776321.5885115,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"172.18.0.1","remote_port":"54756","client_ip":"172.18.0.1","proto":"HTTP/1.1","method":"GET","host":"bvw.enunlugarignotoeinefable.com","uri":"/api/accounts/revision-date","headers":{"Cf-Ipcountry":["ES"],"Device-Type":["0"],"Authorization":["REDACTED"],"Cf-Connecting-Ip":["2a02:9130:8534:99d5:185d:beb7:4d5a:fb81"],"Cf-Warp-Tag-Id":["c71839d2-49ef-4c11-9688-5b7fcf09752a"],"Bitwarden-Client-Version":["2025.7.2"],"X-Forwarded-For":["2a02:9130:8534:99d5:185d:beb7:4d5a:fb81"],"Cf-Visitor":["{\"scheme\":\"https\"}"],"Accept-Encoding":["gzip, br"],"Bitwarden-Client-Name":["mobile"],"X-Forwarded-Proto":["https"],"Cdn-Loop":["cloudflare; loops=1"],"Connection":["keep-alive"],"X-Forwarded-Host":["bvw.XXX.com"],"User-Agent":["Bitwarden_Mobile/2025.7.2 (release/standard) (Android 16; SDK 36; Model Pixel 7)"],"Cf-Ray":["9729d879dad6036a-MAD"]},"tls":{"resumed":false,"version":772,"cipher_suite":4867,"proto":"","server_name":"bvw.enunlugarignotoeinefable.com"}},"bytes_read":0,"user_id":"","duration":0.000079063,"size":30,"status":200,"resp_headers":{"Content-Type":["text/plain; charset=utf-8"],"Server":["Caddy"],"Alt-Svc":["h3=\":443\"; ma=2592000"]}}

3. Caddy version:

2.10.0

4. How I installed and ran Caddy:

a. System environment:

Docker compose

b. Command:

FROM caddy:2.10.0-builder AS builder

RUN xcaddy build \
    --with github.com/corazawaf/coraza-caddy/v2@latest \
    --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 \
    --with github.com/WeidiDeng/caddy-cloudflare-ip \
    --with github.com/mholt/caddy-dynamicdns \
    --with github.com/caddy-dns/cloudflare \
    --with github.com/caddy-dns/duckdns

FROM caddy:2.10.0

COPY --from=builder /usr/bin/caddy /usr/bin/caddy

c. Service/unit/compose file:

services:
  caddy:
    build:
       context: .
       dockerfile: Dockerfile
    depends_on:
      crowdsec:
        condition: service_healthy
    container_name: caddy
    restart: unless-stopped
    cap_add:
      - NET_ADMIN
    environment:
      - CLOUDFLARE_API_TOKEN=$CLOUDFLARE_API_TOKEN
      - DUCKDNS_API_TOKEN=$DUCKDNS_API_TOKEN
      - CROWDSEC_API_KEY=${CROWDSEC_API_KEY}
    ports:
      - 80:80
      - 443:443
      - 443:443/udp #(optional)
      - 8123:8123 #Home Assistant
      - 8443:8443 #VaultWarden
      - 5443:5443 #Nextcloud
    volumes:
      - $DOCKERDIR/caddy/log:/var/log
      - $DOCKERDIR/caddy/ruleset:/ruleset
      - $DOCKERDIR/caddy/Caddyfile:/etc/caddy/Caddyfile
      - $DOCKERDIR/caddy/data:/data
      - $DOCKERDIR/caddy/config:/config
      - $DOCKERDIR/certs/:/config/rlyeh
# Aquí irían las webs si siquiera meterlas o las configuraciones específicas para cada URL, como reglas personalizadas CRS
#      - $DOCKERDIR/caddy/site:/srv
    networks:
      crowdsec:
    security_opt:
      - no-new-privileges=true

  crowdsec:
   image: docker.io/crowdsecurity/crowdsec:latest
   container_name: crowdsec
   healthcheck:
     test:
       - CMD
       - cscli
       - lapi
       - status
     interval: 10s
     timeout: 5s
     retries: 3
     start_period: 30s
   restart: unless-stopped
   environment:
     - GID=1000
     - COLLECTIONS=crowdsecurity/caddy crowdsecurity/base-http-scenarios crowdsecurity/appsec-generic-rules crowdsecurity/linux
 crowdsecurity/sshd crowdsecurity/whitelist-good-actors crowdsecurity/iptables crowdsecurity/endlessh  barnoux/caddy-coraza
     - BOUNCER_KEY_CADDY=${CROWDSEC_API_KEY}
   ports:
     - 8080:8080
     - 7422:7422
   volumes:
     - $DOCKERDIR/caddy/crowdsec_config:/etc/crowdsec
     - crowdsec_db:/var/lib/crowdsec/data/
#Añadimos los log de caddy en modo lectura para las colecciones instaladas
     - $DOCKERDIR/caddy/log/caddy:/var/log/caddy:ro
#Añadimos el /var/log del sistema en modo lectura para las colleciones instaladas
     - /var/log/:/var/log:ro
#Añadimos los log de caddy en modo lectura para las colecciones instaladas
#     - $DOCKERDIR/caddy/log/caddy:/var/log/caddy:ro
   networks:
     crowdsec:
   security_opt:
     - no-new-privileges=true
volumes:
  crowdsec_db:

networks:
  crowdsec:
    driver: bridge

d. My complete Caddy config:

{
#Esta es la zona de la definición de configuración global

#Se puedes deshabitar el auto_https para usar certificados propios, sino por defecto Caddy usa unos autofirmados para>
#auto_https off

#Token de la API Cloudflare que usa el módulo para la gestión ACME de Cloudflare
#Nota: Lo dejo comentado porque hago la definición dentro de los dominios junto con otras variables y si hay doble definición f
alla el challenge.
#  acme_dns cloudflare {env.CLOUDFLARE_API_TOKEN}

#Configuración para el módulo de aceptar solo el tráfico proveniente del rango de IPs de cloudflare
  servers {
    trusted_proxies cloudflare {
         interval 12h
         timeout 15s
      }
  }

# Definición de duckdns como proveedor DNS par el módulo "caddy-dns/duckdns". Ver documento sobre porque está comentado.
#  dns duckdns {env.DUCKDNS_API_TOKEN}

#Configuración para el módulo de gestión de duckdns
  dynamic_dns {
       provider duckdns {env.DUCKDNS_API_TOKEN}
       domains {
             duckdns.org zzz
       }
  }

#Mail para recibir los avisos de la gestión de certificados con  ACME

  email XXX@YYY.com

# Ponemos el log de debug (crowsec usa los log para detectar anomalías)
  log {
   level DEBUG
#Ponemos el nombre de fichero que la integración crowdsec-coraza necesita.
   output file /var/log/caddy/caddy-runtime.log
  }

#Elegimos que se ejecuta antes de cualquier otro HTTP handlers
#Creo que mejor crowdsec porque si es una IP maliciosa que directamente ni entre a evaluarla coraza
#    order coraza_waf first
    order crowdsec first
    order coraza_waf after crowdsec

#Habilitamos y configuramos crowdsec
  crowdsec {
    api_url http://crowdsec:8080
    api_key {env.CROWDSEC_API_KEY}
    ticker_interval 15s
    appsec_url http://crowdsec:7422
#    disable_streaming
#    enable_hard_fails
    }
}
#Fin de la zona de configuración global
#Esta es la zona de definición de sites

#Configuración de reserve proxy (esto incluye la configuración para la inspección de coraza)

#VaultWarden
https://bvw.XXX.com {
   tls {
     dns cloudflare {env.CLOUDFLARE_API_TOKEN}
     resolvers 1.1.1.1
     propagation_timeout 10m
   }

#Habilitamos el log del dominio

   log {
      output file /var/log/caddy/vaultwarden-access.log
   }

   route {
       crowdsec
#       appsec
       respond "Allowed by Bouncer and AppSec!"

       coraza_waf {
              load_owasp_crs
              directives `
                 Include @coraza.conf-recommended
                 Include @crs-setup.conf.example
                 Include /config/vaultwarden_exclusions.conf
                 Include @owasp_crs/*.conf
#                 SecAuditEngine RelevantOnly
                 SecAuditEngine On
                 SecAuditLog /var/log/coraza/vaultwarden_audit.log
                 SecAuditLogParts ABCFHKZ
                 SecRuleEngine On
              `
      }

      reverse_proxy https://192.168.8.20:8443 {
#Esta directiva la he puesto por recomendación del creador de Vaultwarden.
              header_up X-Real-IP {remote_host}
              transport http {
                      tls_trust_pool file /config/rlyeh/rlyeh-ca.pem
#                     tls_insecure_skip_verify
              }
      }
   }
}

5. Links to relevant resources:

Hello again

I don’t know what I’ve done but I ithink that now the route directive is working whith exactly the same Caddyfile I’ve post in this topic.

I’ll do test and when everytihing works (if I ever get it) I’ll share the configuration in the Showcase.
May be it can help somebody

Thanks and congratullation for this community and Caddy :slight_smile:

2 Likes

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.