Skip to main content
Traefik logo
TraefikThird Party Bouncer

Traefik middleware plugin that blocks IPs via CrowdSec decisions and applies AppSec remediations.

  • ●Stream
  • ✓WAF / AppSec
  • ×Captcha
  • ✓Bot Detection
  • ✓Remediation Metrics
  • ✓mTLS
  • ✓Prometheus
  • GitHub

Protect Traefik with CrowdSec IP Blocking & WAF

Plugin version

This page applies to every 1.8.x release of the plugin. The examples pin v1.8.1, the latest release when this page was written.

AppSec Support

This bouncer supports the AppSec Component for real-time WAF protection. Enable crowdsecAppsecEnabled: true in your middleware configuration to get virtual patching and defense against known CVEs, SQL injection, XSS, and other application-layer attacks.

For a full walkthrough, see the AppSec Quickstart for Traefik.

Traefik with Docker Compose​

The plugin is loaded by Traefik from the Traefik plugin catalog, and configured as a middleware through Docker labels.

Objectives​

  • Run CrowdSec, Traefik and a demo service with Docker Compose.
  • Block the IPs CrowdSec has decisions for.
  • Inspect requests with the AppSec Component (WAF).

Configure the acquisition​

CrowdSec reads the Traefik access log and runs the AppSec Component on port 7422:

acquis.yaml
YAMLacquis.yaml
filenames:
- /var/log/traefik/access.log
labels:
type: traefik
---
source: appsec
listen_addr: 0.0.0.0:7422
appsec_configs:
- crowdsecurity/appsec-default
labels:
type: appsec

Define the stack​

BOUNCER_KEY_traefik registers the bouncer key in CrowdSec at startup. Use the same value in the crowdseclapikey label:

docker-compose.yml
YAMLdocker-compose.yml
services:
crowdsec:
image: crowdsecurity/crowdsec:v1.8.1
environment:
COLLECTIONS: crowdsecurity/traefik crowdsecurity/appsec-virtual-patching crowdsecurity/appsec-generic-rules
BOUNCER_KEY_traefik: replace-with-a-long-random-key
volumes:
- ./acquis.yaml:/etc/crowdsec/acquis.yaml:ro
- traefik-logs:/var/log/traefik:ro
- crowdsec-db:/var/lib/crowdsec/data/
- crowdsec-config:/etc/crowdsec/
healthcheck:
test: ["CMD", "cscli", "lapi", "status"]
interval: 5s
retries: 30

traefik:
image: traefik:v3.7
command:
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--accesslog.filepath=/var/log/traefik/access.log"
- "--experimental.plugins.bouncer.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin"
- "--experimental.plugins.bouncer.version=v1.8.1"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- traefik-logs:/var/log/traefik
ports:
- "80:80"
depends_on:
crowdsec:
condition: service_healthy

whoami:
image: traefik/whoami
labels:
- "traefik.enable=true"
- "traefik.http.routers.whoami.rule=PathPrefix(`/`)"
- "traefik.http.routers.whoami.entrypoints=web"
- "traefik.http.routers.whoami.middlewares=crowdsec@docker"
- "traefik.http.middlewares.crowdsec.plugin.bouncer.enabled=true"
- "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdseclapikey=replace-with-a-long-random-key"
- "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdsecappsecenabled=true"

volumes:
traefik-logs:
crowdsec-db:
crowdsec-config:

The plugin reaches the LAPI on crowdsec:8080 and the AppSec Component on crowdsec:7422 by default, which matches the service name above. Set crowdsecLapiHost and crowdsecAppsecHost otherwise.

info

Traefik downloads the plugin when it starts, so it needs access to plugins.traefik.io. Behind a reverse proxy or a load balancer, set forwardedHeadersTrustedIps so the plugin checks the client IP instead of the proxy's.

Validate​

SH
docker compose up -d
curl -I http://localhost/
# Expected: HTTP/1.1 200 OK

curl -I http://localhost/.env
# Expected: HTTP/1.1 403 Forbidden, blocked by AppSec

To check IP blocking, ban the client IP Traefik sees for your requests, which is the first field of its access log. From the Docker host itself, it is the gateway of the Docker network:

SH
docker compose exec traefik tail -1 /var/log/traefik/access.log
docker compose exec crowdsec cscli decisions add --ip <ip-from-the-log> --duration 2m
curl -I http://localhost/
# Expected: HTTP/1.1 403 Forbidden, once the plugin no longer has the IP cached as clean (60 seconds at most)

Traefik on kubernetes​

important

This remediation component is community developed and maintained. You can see all the configuration options in the bouncer documentation. You can also refer to a full traefik and CrowdSec stack on kubernetes or our Appsec Traefik Quickstart.

Objectives​

This quickstart shows how to deploy the CrowdSec Traefik bouncer in Kubernetes and protect workloads exposed through Traefik using a middleware plugin.

At the end, you will have:

  • CrowdSec LAPI running in-cluster and reachable from Traefik
  • A Traefik middleware enforcing CrowdSec remediation decisions
  • A Kubernetes secret storing the shared bouncer key
  • The bouncer key mounted into the Traefik pod as a file
  • An operational pattern that avoids committing the LAPI key in plaintext

Prerequisites​

It is assumed that you already have:

warning

This integration currently relies on a community Traefik plugin, not on a first-party CrowdSec remediation component.

The upstream project used in this guide is:

  • maxlerebourg/crowdsec-bouncer-traefik-plugin

Store the Traefik bouncer key in a Kubernetes secret​

The practical approach is to choose a fixed shared key and store it in Kubernetes secrets instead of hardcoding it in Helm values.

Two secrets are needed because CrowdSec and Traefik run in different namespaces:

  • In the crowdsec namespace, CrowdSec LAPI reads BOUNCER_KEY_traefik from the crowdsec-keys secret.
  • In the traefik namespace, Traefik mounts the same key from the crowdsec-bouncer-key secret as a file.

Both secrets must contain the same BOUNCER_KEY_traefik value.

Create or update the secrets used by CrowdSec LAPI and Traefik:

crowdsec-keys.yaml
YAMLcrowdsec-keys.yaml
apiVersion: v1
kind: Secret
metadata:
name: crowdsec-keys
namespace: crowdsec
type: Opaque
stringData:
ENROLL_KEY: "<your-existing-enroll-key>"
BOUNCER_KEY_traefik: "<your-shared-traefik-bouncer-key>"
---
apiVersion: v1
kind: Secret
metadata:
name: crowdsec-bouncer-key
namespace: traefik
type: Opaque
stringData:
BOUNCER_KEY_traefik: "<your-shared-traefik-bouncer-key>"

Apply it:

SH
kubectl apply -f crowdsec-keys.yaml

Then reference BOUNCER_KEY_traefik from the CrowdSec Helm values:

crowdsec-values.yaml
YAMLcrowdsec-values.yaml
lapi:
env:
- name: BOUNCER_KEY_traefik
valueFrom:
secretKeyRef:
name: crowdsec-keys
key: BOUNCER_KEY_traefik

Apply the CrowdSec release again:

SH
helm upgrade --install crowdsec crowdsec/crowdsec --namespace crowdsec --create-namespace -f crowdsec-values.yaml

The crowdsec-bouncer-key secret in the traefik namespace is used later when mounting the key into the Traefik pod.

Required traefik configuration items​

Traefik configuration for source IPs​

To ensure remediation works correctly, Traefik must receive the real client IP for each request. When Traefik is deployed behind a load balancer, CDN, or another reverse proxy, the source IP may otherwise be replaced with the upstream address.

Traefik must first trust the upstream IP ranges. This is done with:

The CrowdSec middleware must then trust the same ranges:

YAML
spec:
plugin:
bouncer:
forwardedHeadersTrustedIps: <trusted-cidr>

If the client IP is forwarded through a header other than X-Forwarded-For, set it explicitly:

YAML
spec:
plugin:
bouncer:
forwardedHeadersCustomName: X-Real-Ip

Correctly forwarding and trusting these headers ensures that both Traefik and CrowdSec operate on the same client IP, which is required for IP-based remediation.

Side note about source IP with CrowdSec and Kubernetes

Source IP addresses are essential in a CrowdSec deployment for two reasons. First, the log processor must know which IPs are responsible for triggering scenarios. Second, the remediation component needs to identify the originating IP of incoming requests in order to apply the appropriate action.

In a Kubernetes environment, this requires disabling source NAT on nodes so that the CrowdSec-monitored service pods receive the real client IP. As a consequence, the Service's externalTrafficPolicy must be set to Local, and the workload must run either as a DaemonSet or as a Deployment ensuring one pod per node. This guarantees that no traffic, and therefore no security events, is missed.

Traefik Custom Resource Definitions​

Traefik's CRDs provide the custom resource types, such as Middleware, that are required to configure Traefik through the Kubernetes CRD provider. CrowdSec remediation relies on one of these resources to declare the middleware. Without the CRDs, this middleware cannot be created or used.

Install the Traefik CRDs via Helm:

SH
helm repo add traefik https://traefik.github.io/charts
helm repo update
helm upgrade --install traefik-crds traefik/traefik-crds -n traefik --create-namespace

Enable experimental plugin loading​

The CrowdSec Traefik plugin cannot be enabled only through CLI flags. You must also enable experimental plugin loading in the Traefik chart values:

traefik-values.yaml
YAMLtraefik-values.yaml
experimental:
plugins:
bouncer:
moduleName: github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin
version: v1.8.1
volumes:
- name: crowdsec-bouncer-key
mountPath: /etc/traefik/crowdsec
type: secret
secretName: crowdsec-bouncer-key

Apply or upgrade your Traefik release:

SH
helm upgrade --install traefik traefik/traefik -n traefik --create-namespace -f traefik-values.yaml

Verify CrowdSec LAPI access​

The Traefik middleware only needs access to CrowdSec LAPI. Make sure the CrowdSec release exposes the LAPI service, that the bouncer key is available through lapi.env, and that Traefik has the same key mounted from the crowdsec-bouncer-key secret.

Verify the CrowdSec pods and services:

SH
kubectl -n crowdsec get pods
kubectl -n crowdsec get svc crowdsec-service

You should see:

  • crowdsec-lapi in Running
  • crowdsec-service exposing port 8080

Deploy the Traefik middleware​

To achieve remediation in a Traefik environment, create a Middleware resource.

important

The Traefik Middleware CRD does not have a native secretKeyRef field for the plugin configuration. In Kubernetes, the key can be mounted from a Secret into the Traefik pod and referenced with crowdsecLapiKeyFile.

Mount the Traefik-side secret into the pod and let the middleware read it from a file.

Use a Traefik chart values file like this:

traefik-values.yaml
YAMLtraefik-values.yaml
experimental:
plugins:
bouncer:
moduleName: github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin
version: v1.8.1
volumes:
- name: crowdsec-bouncer-key
mountPath: /etc/traefik/crowdsec
type: secret
secretName: crowdsec-bouncer-key

Apply or upgrade your Traefik release:

SH
helm upgrade --install traefik traefik/traefik -n traefik --create-namespace -f traefik-values.yaml

Then create the middleware:

bouncer-middleware.yaml
YAMLbouncer-middleware.yaml
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: bouncer
namespace: traefik
spec:
plugin:
bouncer:
enabled: true
crowdsecMode: stream
crowdsecLapiScheme: http
crowdsecLapiHost: crowdsec-service.crowdsec.svc.cluster.local:8080
crowdsecLapiPath: /
crowdsecLapiKeyFile: /etc/traefik/crowdsec/BOUNCER_KEY_traefik

Apply it:

SH
kubectl apply -f bouncer-middleware.yaml

This keeps the source-of-truth key in Kubernetes secrets and avoids storing the literal key in the middleware manifest.

Show direct crowdsecLapiKey example

You can apply a middleware manifest with an inline key as well:

bouncer-middleware.yaml
YAMLbouncer-middleware.yaml
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: bouncer
namespace: traefik
spec:
plugin:
bouncer:
enabled: true
crowdsecMode: stream
crowdsecLapiScheme: http
crowdsecLapiHost: crowdsec-service.crowdsec.svc.cluster.local:8080
crowdsecLapiPath: /
crowdsecLapiKey: <your-shared-traefik-bouncer-key>

Apply it:

SH
kubectl apply -f bouncer-middleware.yaml

This is useful for quick validation, but crowdsecLapiKeyFile with a mounted Kubernetes secret seems to be a more secure approach.

Once the middleware exists, attach it to your IngressRoute or other Traefik route resources.

Traefik with WAF (AppSec) on Kubernetes​

If you want remediation and WAF protection together, first enable AppSec in the CrowdSec chart while still sourcing the bouncer key from the Kubernetes secret:

crowdsec-values.yaml
YAMLcrowdsec-values.yaml
config:
config.yaml.local: |
api:
server:
auto_registration:
enabled: true
token: "${REGISTRATION_TOKEN}"
allowed_ranges:
- "127.0.0.1/32"
- "192.168.0.0/16"
- "10.0.0.0/8"
- "172.16.0.0/12"
appsec:
enabled: true
acquisitions:
- source: appsec
listen_addr: "0.0.0.0:7422"
path: /
appsec_configs:
- crowdsecurity/appsec-default
- crowdsecurity/crs
labels:
type: appsec
env:
- name: COLLECTIONS
value: "crowdsecurity/appsec-virtual-patching crowdsecurity/appsec-crs crowdsecurity/appsec-generic-rules"
lapi:
env:
- name: BOUNCER_KEY_traefik
valueFrom:
secretKeyRef:
name: crowdsec-keys
key: BOUNCER_KEY_traefik

If you add this config to the CrowdSec values, don't forget to upgrade the release:

SH
helm upgrade --install crowdsec crowdsec/crowdsec \
--namespace crowdsec \
--create-namespace \
-f crowdsec-values.yaml

Traefik must also mount the crowdsec-bouncer-key secret so the middleware can read the bouncer key from a file:

traefik-values.yaml
YAMLtraefik-values.yaml
experimental:
plugins:
bouncer:
moduleName: github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin
version: v1.8.1
volumes:
- name: crowdsec-bouncer-key
mountPath: /etc/traefik/crowdsec
type: secret
secretName: crowdsec-bouncer-key

If needed, upgrade the Traefik release as well:

SH
helm upgrade --install traefik traefik/traefik -n traefik --create-namespace -f traefik-values.yaml

Then use an AppSec-enabled middleware:

bouncer-middleware.yaml
YAMLbouncer-middleware.yaml
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: bouncer
namespace: traefik
spec:
plugin:
bouncer:
enabled: true
crowdsecMode: stream
crowdsecLapiScheme: http
crowdsecLapiHost: crowdsec-service.crowdsec.svc.cluster.local:8080
crowdsecLapiPath: /
crowdsecLapiKeyFile: /etc/traefik/crowdsec/BOUNCER_KEY_traefik
crowdsecAppsecEnabled: true
crowdsecAppsecHost: crowdsec-appsec-service.crowdsec.svc.cluster.local:7422
crowdsecAppsecPath: /
crowdsecAppsecFailureBlock: true
crowdsecAppsecUnreachableBlock: true
crowdsecAppsecBodyLimit: 10485760

Apply it:

SH
kubectl apply -f bouncer-middleware.yaml
Validate AppSec​

Test that malicious requests are blocked:

SH
curl -I http://<your-ingress-ip>/.env
# Expected: HTTP/1.1 403 Forbidden
Monitoring AppSec

Once AppSec is enabled, use cscli metrics show appsec to view processed vs. blocked requests and individual rule triggers. These metrics also appear in the CrowdSec Console after enrollment.

Bot detection​

Prerequisites

Bot detection is an AppSec feature that needs CrowdSec 1.8.0 or later and plugin v1.8.0 or later. First enable AppSec as described above, then follow the bot detection guide.

Install the bot challenge collection next to the AppSec ones, for example with COLLECTIONS: crowdsecurity/traefik crowdsecurity/appsec-virtual-patching crowdsecurity/appsec-generic-rules crowdsecurity/appsec-bot-challenge, and load its configurations in the AppSec acquisition:

acquis.yaml
YAMLacquis.yaml
source: appsec
listen_addr: 0.0.0.0:7422
appsec_configs:
- crowdsecurity/appsec-default
- crowdsecurity/appsec-bot-*
labels:
type: appsec

The plugin relays the challenge returned by AppSec: the page, its headers and its cookies. Nothing else has to be enabled on the plugin side, but three settings can break the challenge without any error in the logs:

  • The router must match /crowdsec-internal. The challenge page loads its scripts and posts its proof under /crowdsec-internal/challenge/. A router scoped to your application prefix alone never sends these requests to the plugin, so the challenge can never be solved:

    YAML
    - "traefik.http.routers.my-router.rule=PathPrefix(`/my-app`) || PathPrefix(`/crowdsec-internal`)"
  • List your Content-Security-Policy after the plugin. Traefik's headers middleware sets its headers when the response is written. Listed before the plugin, it overwrites the challenge policy with yours, which usually forbids the inline script and the blob: worker the challenge needs. Put your CSP in a headers middleware listed after the plugin:

    YAML
    - "traefik.http.routers.my-router.middlewares=security-headers@docker,crowdsec@docker,csp@docker"

    See Content-Security-Policy for the policy the challenge needs.

  • Keep crowdsecAppsecBodyLimit above 0 and large enough for the proof. The proof is posted as a form, and AppSec only validates it if it receives the whole body. With a limit of 0 the plugin sends no body, and with a smaller limit the proof is cut short: either way the visitor is challenged forever. The default of 10MB is enough.

To check the setup, request a page with curl. It cannot solve the challenge, so it gets the challenge page instead of your application:

SH
curl -s http://localhost/ | grep -o '<title>.*</title>'
# Expected: <title>CrowdSec Challenge</title>
CrowdSec Docs
We use cookies

This site uses cookies to help us improve your experience. You can accept or decline below.