Traefik Plugin (CrowdSec Bouncer)
Protect services running behind Traefik with CrowdSec and EU CAPTCHA together. The crowdsec-bouncer-traefik-plugin is a Traefik middleware that checks every incoming request against the CrowdSec Local API (LAPI) and, for IPs with a captcha decision, presents the EU CAPTCHA widget before allowing access.
IPs with a ban decision receive a 403 Forbidden response. All other traffic is forwarded transparently to your service.
How it works
Client → Traefik (bouncer middleware) → Your service
↕
CrowdSec LAPI
- On each request the middleware looks up the client IP in its local cache (populated from the CrowdSec decision stream).
- Ban: returns 403 immediately.
- Captcha: serves the EU CAPTCHA challenge page. On completion the token is verified server-side. A valid result marks the IP as clean for
captchaGracePeriodSecondsand redirects the client to their original URL. - No decision: forwards the request to your service unchanged.
At startup, if eucaptcha is configured, the plugin calls the /verify-credentials API and logs whether the sitekey and secret are valid.
Requirements
- Traefik v2.9+ or v3
- A running CrowdSec agent with an accessible Local API
- An EU CAPTCHA account with a sitekey and secret
- CrowdSec configured to issue
captchadecisions (see CrowdSec configuration)
Installation
The plugin is loaded by Traefik at startup. Add the following to your static configuration (traefik.yml or CLI flags):
experimental:
plugins:
bouncer:
moduleName: github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin
version: v1.3.5 # use the latest release
Then declare the middleware in your dynamic configuration (Docker labels, docker-compose.yml, or a file provider):
http:
middlewares:
crowdsec:
plugin:
bouncer:
crowdsecLapiKey: "<YOUR_CROWDSEC_API_KEY>"
crowdsecLapiHost: "crowdsec:8080"
captchaProvider: eucaptcha
captchaSiteKey: "EUCAPTCHA_SITE_KEY"
captchaSecretKey: "EUCAPTCHA_SECRET_KEY"
captchaGracePeriodSeconds: 1800
captchaHTMLFilePath: /captcha.html
Download the captcha template
The captcha HTML page must be present in the Traefik container. Download it and mount it as a volume:
curl -o captcha.html \
https://raw.githubusercontent.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/main/captcha.html
# docker-compose.yml
services:
traefik:
image: traefik:v3.0
volumes:
- ./captcha.html:/captcha.html
Register the bouncer with CrowdSec
docker exec crowdsec cscli bouncers add traefik-bouncer
# Copy the printed API key into crowdsecLapiKey
CrowdSec configuration
By default CrowdSec issues ban decisions. To enable captcha decisions, override /etc/crowdsec/profiles.yaml in the CrowdSec container:
# profiles.yaml — captcha only
name: default_ip_remediation
filters:
- Alert.Remediation == true && Alert.GetScope() == "Ip"
decisions:
- type: captcha
duration: 4h
on_success: break
A mixed mode — captcha on first offence, ban after repeated violations — is also supported. See the CrowdSec profiles documentation for details.
Docker Compose example
services:
traefik:
image: traefik:v3.0
command:
- --experimental.plugins.bouncer.moduleName=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin
- --experimental.plugins.bouncer.version=v1.3.5
volumes:
- ./captcha.html:/captcha.html
labels:
- "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdsecLapiKey=<API_KEY>"
- "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdsecLapiHost=crowdsec:8080"
- "traefik.http.middlewares.crowdsec.plugin.bouncer.captchaProvider=eucaptcha"
- "traefik.http.middlewares.crowdsec.plugin.bouncer.captchaSiteKey=EUCAPTCHA_SITE_KEY"
- "traefik.http.middlewares.crowdsec.plugin.bouncer.captchaSecretKey=EUCAPTCHA_SECRET_KEY"
- "traefik.http.middlewares.crowdsec.plugin.bouncer.captchaGracePeriodSeconds=1800"
- "traefik.http.middlewares.crowdsec.plugin.bouncer.captchaHTMLFilePath=/captcha.html"
crowdsec:
image: crowdsecurity/crowdsec:latest
volumes:
- ./profiles.yaml:/etc/crowdsec/profiles.yaml:ro
myapp:
image: myapp:latest
labels:
- "traefik.http.routers.myapp.middlewares=crowdsec"
Configuration reference
| Key | Default | Description |
|---|---|---|
crowdsecLapiKey |
(required) | Bouncer API key from cscli bouncers add |
crowdsecLapiHost |
localhost:8080 |
CrowdSec LAPI host and port |
captchaProvider |
(required) | Set to eucaptcha for EU CAPTCHA |
captchaSiteKey |
(required) | EU CAPTCHA public sitekey |
captchaSecretKey |
(required) | EU CAPTCHA secret key |
captchaGracePeriodSeconds |
1800 |
Seconds a solved challenge is valid before re-challenging |
captchaHTMLFilePath |
/captcha.html |
Path to the captcha template inside the Traefik container |
crowdsecLapiScheme |
http |
http or https |
updateIntervalSeconds |
10 |
How often to poll the CrowdSec decision stream |
defaultDecisionSeconds |
60 |
TTL for cached decisions |
httpTimeoutSeconds |
10 |
HTTP client timeout for LAPI and captcha API calls |
banHTMLFilePath |
(none) | Optional custom HTML page for banned IPs |
customIPHeader |
(none) | Header to read the real client IP from (e.g. X-Real-Ip) |
remediationCustomHeader |
(none) | Response header set to ban, captcha, or solved-captcha |
For the full list of options see the plugin README.
See also
- Caddy Middleware — gate any Caddy route behind EU CAPTCHA without CrowdSec
- CrowdSec Bouncer — same EU CAPTCHA + CrowdSec integration as a standalone reverse proxy binary