CrowdSec Bouncer for Caddy

A Caddy module that blocks malicious traffic based on decisions made by CrowdSec.
This is a fork of hslatman/caddy-crowdsec-bouncer, maintained to patch the upstream code and keep its dependencies up to date. It is used in joeldroid/caddy-builds, which builds custom Caddy images that include this module. Credit for the original work goes to Herman Slatman and the upstream contributors.
Table of Contents
Description
The Caddy CrowdSec Bouncer consists of four components:
- Caddy App: Responsible for communicating with CrowdSec via the Local API and keeping track of its decisions. It supports both StreamBouncer (HTTP polling) and LiveBouncer (a request is made on every incoming connection).
- Bouncer HTTP Handler: Checks client IPs of incoming HTTP requests against the decisions stored by the App. Multiple independent HTTP Handlers can share the storage exposed by the App.
- AppSec HTTP Handler: Communicates with an AppSec component configured on your CrowdSec deployment, seamlessly checking incoming HTTP requests against configured rulesets.
caddy crowdsec Command: Offers useful Caddy CLI commands for your CrowdSec integration.
What is CrowdSec?
CrowdSec is a free and open source security automation tool that uses local logs and a set of scenarios to infer malicious intent.
In addition to operating locally, an optional community integration is also available, through which crowd-sourced IP reputation lists are distributed.
The architecture of CrowdSec is very modular.
At its core is the CrowdSec Security Engine, which keeps track of all data and related systems.
Bouncers are pieces of software that perform specific actions based on the decisions of the Security Engine.
Usage
[!TIP]
You can find full setup examples at examples
You can use the bouncer by either building a custom Caddy image with Docker or by fetching the required Go modules directly into your own build.
Note: You will need Caddy (v2.11.7+) and Go (1.26.1+).
Option 1: Docker Build
To include the bouncer in a Docker Image using xcaddy. Create a Dockerfile:
ARG CADDY_VERSION=2
FROM caddy:${CADDY_VERSION}-builder-alpine AS builder
RUN xcaddy build \
--with github.com/joeldroid/caddy-crowdsec-bouncer/http@main \
--with github.com/joeldroid/caddy-crowdsec-bouncer/appsec@main
FROM caddy:${CADDY_VERSION}
COPY --from=builder /usr/bin/caddy /usr/bin/caddy
Option 2: Custom Go Build
If you are compiling outside of Docker, you can fetch the modules using go get:
# get the CrowdSec Bouncer HTTP handler
go get github.com/joeldroid/caddy-crowdsec-bouncer/http
# get the AppSec HTTP handler (only required for CrowdSec AppSec support)
go get github.com/joeldroid/caddy-crowdsec-bouncer/appsec
Configuration
Configuration using a Caddyfile is supported for the HTTP and AppSec handlers.
Configuration Options
| Directive |
Description |
Default |
api_url |
The URL of the CrowdSec Local API. |
http://127.0.0.1:8080/ |
api_key |
The API key to authenticate with the Local API. |
<empty> (required) |
disable_streaming |
Falls back to LiveBouncer mode (queries API per request). |
false |
metrics_interval |
Interval for pushing metrics to the Local API. |
0s (disabled) |
enable_caddy_metrics |
Enables emitting bouncer metrics at Caddy's /metrics endpoint. |
false |
ticker_interval |
Interval for pulling decisions from the Local API. |
60s |
enable_hard_fails |
Caddy fails to start if CrowdSec API is unreachable. |
false |
appsec_url |
The URL of the CrowdSec AppSec component. |
<empty> (disabled) |
appsec_max_body_bytes |
Maximum request body size sent to AppSec. |
0 (full request) |
appsec_timeout |
Maximum time for request to AppSec component. |
2s |
appsec_fail_open |
Ignore AppSec component connection errors. |
false |
enable_caddy_error |
Propagates decisions as Caddy errors to allow custom error pages. Warning: Ensure handle_errors routes are strictly static to avoid resource exhaustion (DoS). |
false |
Variables
When a request is blocked, the HTTP and AppSec handlers record why in the request context, so that Caddyfile matchers and logs can tell blocks apart:
| Variable |
Description |
Examples |
{http.vars.crowdsec.module} |
The handler that blocked the request. |
http, appsec |
{http.vars.crowdsec.remediation} |
The remediation that was applied. |
ban, captcha, throttle |
{http.vars.crowdsec.origin} |
The origin of the decision |
appsec, crowdsec, lists:foo |
{http.vars.crowdsec.duration} |
The duration of the decision. |
3h59m58s |
handle_errors {
log_append blocked-by {http.vars.crowdsec.module}
}
Example
{
debug
crowdsec {
api_url http://localhost:8080
api_key <api_key>
ticker_interval 15s
appsec_url http://localhost:7422
#disable_streaming
#enable_hard_fails
#enable_caddy_error
}
}
localhost:8443 {
route {
crowdsec
respond "Allowed by Bouncer!"
}
}
localhost:7443 {
route {
appsec
respond "Allowed by AppSec!"
}
}
localhost:6443 {
route {
crowdsec
appsec
respond "Allowed by Bouncer and AppSec!"
}
}
Run the Caddy server
# with a Caddyfile
caddy run --config Caddyfile
Demo
This repository also contains an example using Docker inside the examples/demo folder.
Steps to run this demo are as follows:
# run CrowdSec container
docker compose up -d crowdsec
# add the Caddy bouncer, generating an API key
docker compose exec crowdsec cscli bouncers add caddy-bouncer
# copy and paste the API key in the ./examples/demo/config.json file
# run Caddy; at first run a custom build will be created using xcaddy
docker compose up -d caddy
# tail the logs
docker compose logs -tf
You can then access https://localhost:9443.
Utilities
When Caddy is built with this module enabled, a new caddy crowdsec command will be enabled.
Its subcommands allow you to interact with your CrowdSec integration at runtime using Caddy's Admin API.
This is useful to verify the status of your integration, and check if it's configured and working properly.
The command requires the Admin API to be reachable from the system it is run from.
Support for the command is currently experimental, and will be improved and extended in future releases.
Output of the commands should not be relied upon in automated processes (yet).
Usage
$ caddy crowdsec
Commands related to the CrowdSec integration (experimental)
Usage:
caddy crowdsec [command]
Available Commands:
check Checks an IP to be banned or not
health Checks CrowdSec integration health
info Shows CrowdSec runtime information
ping Pings the CrowdSec LAPI endpoint
Flags:
-a, --adapter string Name of config adapter to apply (when --config is used)
--address string The address to use to reach the admin API endpoint, if not the default
-c, --config string Configuration file to use to parse the admin address, if --address is not used
-h, --help help for crowdsec
-v, --version version for crowdsec
Use "caddy crowdsec [command] --help" for more information about a command.
Full documentation is available at:
https://caddyserver.com/docs/command-line
Client IP
When your Caddy server is deployed behind a proxy (like a CDN or load balancer), the actual client IP is masked. Starting with v0.3.1, this module relies on Caddy's native logic (introduced in Caddy v2.7.0 via caddy#5104) to determine the correct client IP before checking it against CrowdSec decisions.
Important: Caddy uses the X-Forwarded-For header by default. To trust this header, you must configure the trusted_proxies global directive in your Caddyfile.
You can override the default header using the client_ip_headers directive.
Note: For Caddy versions up to v2.4.6 and older versions of this module, the realip module is required.
Things That Can Be Done
- Add integration tests for the HTTP handler
- Implement tests for IPv6 support
- Add support for captcha actions
- Implement support for custom actions (currently defaults to block)
- Integrate with Caddy metrics
- Integrate with Caddy profiling
- Implement caching for the LiveBouncer
...and more things to come!
Contributing
Contributions are welcome. Whether it's fixing bugs, improving documentation, or picking up an item from the list above, feel free to open an issue or submit a pull request.
Changes that are not specific to this fork are best proposed upstream, so that everyone benefits. Upstream changes are merged into this fork when needed.
License
Licensed under the Apache License 2.0. Files derived from upstream retain the original copyright notice of Herman Slatman.