✕
Drop-in Caddy reverse proxy with CrowdSec WAF protection
A pre-compiled Caddy Docker image shipping the following modules:
| Module | Purpose |
|---|---|
| caddy-crowdsec-bouncer (http/appsec/layer4) | Block malicious IPs using CrowdSec reputation data |
| caddy-l4 | TCP/UDP Layer 4 proxying |
| transform-encoder | Custom log output formatting |
Paired with docker-compose.yml, spin up a full Caddy + CrowdSec protection stack in one command.
. ├── Dockerfile # Multi-stage: xcaddy build → slim runtime ├── docker-compose.yml # Caddy + CrowdSec orchestration ├── .env.example # Environment variable template ├── config/ │ ├── caddy/ │ │ └── Caddyfile # Caddy config (bouncer example included) │ └── crowdsec/ │ └── acquis.yaml # CrowdSec log acquisition config ├── LICENSE ├── CONTRIBUTING.md └── README_en.md
Internet ──▶ Caddy (:80/:443) ──▶ CrowdSec LAPI lookup │ │ │ IP clean ─────────▶ proxy to upstream │ IP malicious ─────▶ 403 Forbidden │ └──▶ access logs ──▶ CrowdSec Agent ──▶ update decisions
Pre-built images support linux/amd64 and linux/arm64:
docker.cnb.cool/hakurei/caddy-crowdsec-bouncer
| Tag | Caddy Version | Notes |
|---|---|---|
latest / 2 | Caddy 2.x latest | Recommended |
2.11 | Caddy 2.11.x | Current stable |
Note:
caddy-l4module requires Caddy >= 2.11, so images for 2.10 and earlier are not provided.
# Pull latest
docker pull docker.cnb.cool/hakurei/caddy-crowdsec-bouncer:latest
# Pull specific version
docker pull docker.cnb.cool/hakurei/caddy-crowdsec-bouncer:2.11
# ARM devices (Raspberry Pi, Apple Silicon) auto-pull arm64
docker pull docker.cnb.cool/hakurei/caddy-crowdsec-bouncer:latest
# 1. Copy env template
cp .env.example .env
# 2. Tweak the Caddyfile to your needs
vim config/caddy/Caddyfile
# 3. Bring it up
docker compose up -d
# 4. Register the bouncer & grab the API key
docker compose exec crowdsec cscli bouncers add caddy-bouncer
# Paste the key into .env as CROWDSEC_BOUNCER_API_KEY
# 5. Restart caddy to pick up the key
docker compose restart caddy
First run note: CrowdSec will pull collections (
crowdsecurity/caddy,crowdsecurity/http-cve,crowdsecurity/base-http-scenarios) on first boot — allow ~30 s for initialization.
| Variable | Default | Description |
|---|---|---|
CADDY_VERSION | 2 | Caddy base image tag |
CROWDSEC_BOUNCER_API_KEY | — | Bouncer API key (required) |
CROWDSEC_LAPI_URL | http://crowdsec:8080 | CrowdSec LAPI endpoint |
CADDY_HTTP_PORT | 80 | Published HTTP port |
CADDY_HTTPS_PORT | 443 | Published HTTPS port |
Located at config/caddy/Caddyfile. Key snippet:
{ order crowdsec first order appsec after crowdsec crowdsec { api_url {$CROWDSEC_LAPI_URL} api_key {$CROWDSEC_BOUNCER_API_KEY} ticker_interval 15s appsec_url http://crowdsec:7422 # Uncomment to enable AppSec } } example.com { route { crowdsec # Layer 1: IP reputation blocking appsec # Layer 2: AppSec WAF deep inspection reverse_proxy localhost:8080 } log { output file /var/log/caddy/access.log format json } }
⚠️ Log format must be
format json. CrowdSec'scaddy-logsparser only supports JSON-formatted logs. Usingtransform-encoderor other custom formats will cause logs to be unparsed, and CrowdSec will not detect any attacks.
Full syntax: Caddyfile docs · bouncer options
config/crowdsec/acquis.yaml tells CrowdSec which logs to analyze:
filenames:
- /var/log/caddy/access.log
labels:
type: caddy
Persistent data (decision DB, hub items) lives in Docker volumes — safe across container recreates.
AppSec inspects request content for attacks (SQL injection, XSS, known CVE exploits, etc.) — a second defense layer on top of IP reputation blocking.
docker compose exec crowdsec cscli collections install crowdsecurity/appsec-virtual-patching
docker compose exec crowdsec cscli collections install crowdsecurity/appsec-generic-rules
Append to config/crowdsec/acquis.yaml:
---
listen_addr: 0.0.0.0:7422
appsec_config: crowdsecurity/appsec-default
name: appsec
source: appsec
labels:
type: appsec
Add appsec_url to the global block and appsec directive to your site:
{ order crowdsec first order appsec after crowdsec crowdsec { api_url {$CROWDSEC_LAPI_URL} api_key {$CROWDSEC_BOUNCER_API_KEY} appsec_url http://crowdsec:7422 } } example.com { route { crowdsec appsec reverse_proxy localhost:8080 } }
docker compose restart crowdsec docker compose restart caddy
# Normal request should return 200
curl http://your-server/
# CVE exploit should return 403
curl http://your-server/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
# Check AppSec metrics
docker compose exec crowdsec cscli metrics show appsec
# Default
docker build -t caddy-crowdsec-bouncer .
# Pin Caddy version
docker build --build-arg CADDY_VERSION=2.9 -t caddy-crowdsec-bouncer .
Need extra modules? Append --with lines to xcaddy build in the Dockerfile.
Already running CrowdSec elsewhere? Skip the compose CrowdSec service:
docker run -d \ -p 80:80 -p 443:443 \ -v ./config/caddy/Caddyfile:/etc/caddy/Caddyfile \ -e CROWDSEC_LAPI_URL=http://your-crowdsec:8080 \ -e CROWDSEC_BOUNCER_API_KEY=<your-key> \ caddy-crowdsec-bouncer
CrowdSec Console is the official web management platform for monitoring alerts, managing blocklists, and pushing rules to your instances.
Sign up at https://app.crowdsec.net/ and log in.
Go to Security Engines → Add Security Engine to generate an enrollment key.
docker compose exec crowdsec cscli console enroll <your-enrollment-key>
Back on the Console, find the new engine in the Security Engines list and click Accept.
docker compose restart crowdsec
Once enrolled, the Console gives you:
# List active bans
docker compose exec crowdsec cscli decisions list
# List registered bouncers
docker compose exec crowdsec cscli bouncers list
# Manually ban an IP (for testing)
docker compose exec crowdsec cscli decisions add -i 1.2.3.4 -d 10m -t ban
# Tail logs
docker compose logs -f caddy
docker compose logs -f crowdsec
PRs and issues welcome! See CONTRIBUTING.md.