Skip to content

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
  1. On each request the middleware looks up the client IP in its local cache (populated from the CrowdSec decision stream).
  2. Ban: returns 403 immediately.
  3. Captcha: serves the EU CAPTCHA challenge page. On completion the token is verified server-side. A valid result marks the IP as clean for captchaGracePeriodSeconds and redirects the client to their original URL.
  4. 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

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

Source

github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin