1. The problem I’m having:
Hello again ![]()
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
}
}
}
}