π Documentation π Hub π¬ Discourse
A Remediation Component for haproxy.
What it does ?β
The cs-haproxy-spoa-bouncer allows CrowdSec to enforce blocking, CAPTCHA, or
allow actions directly within HAProxy using the SPOE
protocol.
This remediation component is meant to obsolete the old lua-based haproxy bouncer.
It supports IP-based decisions, CAPTCHA challenges, GeoIP-based headers, and integrates cleanly with CrowdSecβs LAPI using the stream bouncer protocol.
Supported features:
- Stream mode (pull the local API for new/old decisions every X seconds)
- Ban remediation (can ban an IP address by redirecting or returning a custom HTML page)
- Captcha remediation (can return a captcha)
- Works with IPv4/IPv6
- Support IP ranges (can apply a remediation on an IP range)
- We are working on supporting AppSec
Installationβ
We strongly encourage the use of our packages.
Using packagesβ
You will have to setup crowdsec repositories first setup crowdsec repositories.
- Debian/Ubuntu
- RHEL/Centos/Fedora
sudo apt install crowdsec-haproxy-spoa-bouncer
sudo dnf install crowdsec-haproxy-spoa-bouncer
Bouncer configurationβ
If you are using packages, and have a lapi on the same server the following
configuration file /etc/crowdsec/bouncers/crowdsec-spoa-bouncer.yaml should
already be in a working state, and you can skip this section and begin with HAProxy
Configuration.
If your CrowdSec Engine is installed on another server, you'll need to update
the /etc/crowdsec/bouncers/crowdsec-spoa-bouncer.yaml file.
HAProxy Configurationβ
HAProxy requires two configuration files for integration with the bouncer. The
primary file is /etc/haproxy/haproxy.cfg, which must be modified to enable
communication with the SPOE engineβour documentation will guide you through
this. The second file is /etc/haproxy/crowdsec.cfg, which contains the SPOE
agent configuration. This file is automatically installed along with the bouncer
package on the condition that /etc/haproxy exists.
If you are using packages, you will find the haproxy configuration
snippets in /usr/share/doc/crowdsec-haproxy-spoa-bouncer/examples.
SPOE Filterβ
Add a SPOE agent configuration to /etc/haproxy/crowdsec.cfg:
/etc/haproxy/crowdsec.cfg
[crowdsec]
spoe-agent crowdsec-agent
messages crowdsec-ip crowdsec-http
option var-prefix crowdsec
option set-on-error error
timeout hello 100ms
timeout idle 30s
timeout processing 500ms
use-backend crowdsec-spoa
log global
## This message is used to customise the remediation from crowdsec-ip based on the host header
## src-ip is included as fallback in case crowdsec-ip message didn't fire
spoe-message crowdsec-http
args remediation=var(txn.crowdsec.remediation) crowdsec_captcha_cookie=req.cook(crowdsec_captcha_cookie) id=unique-id host=hdr(Host) method=method path=path query=query version=req.ver headers=req.hdrs body=req.body url=url ssl=ssl_fc src-ip=src src-port=src_port
event on-frontend-http-request
## This message should be the first to trigger in the chain
spoe-message crowdsec-ip
args id=unique-id src-ip=src src-port=src_port
event on-client-session
If you installed the haproxy spoe bouncer through package, you will find this
configuration file in /usr/share/doc/crowdsec-haproxy-spoa-bouncer/examples
This crowdsec spoe agent configuration is then referenced in the main haproxy
configuration file /etc/haproxy/haproxy.cfg and may be added at the bottom of
the haproxy configuration file.
/etc/haproxy/haproxy.cfg
[...]
frontend http-in
bind *:80
filter spoe engine crowdsec config /etc/haproxy/crowdsec.cfg
http-request set-header X-Crowdsec-Remediation %[var(txn.crowdsec.remediation)]
## Handle 302 redirect for successful captcha validation (native HAProxy redirect)
http-request redirect code 302 location %[var(txn.crowdsec.redirect)] if { var(txn.crowdsec.remediation) -m str "allow" } { var(txn.crowdsec.redirect) -m found }
## Call lua script only for ban and captcha remediations (performance optimization)
http-request lua.crowdsec_handle if { var(txn.crowdsec.remediation) -m str "captcha" }
http-request lua.crowdsec_handle if { var(txn.crowdsec.remediation) -m str "ban" }
## Handle captcha cookie management via HAProxy (new approach)
## Set captcha cookie when SPOA provides captcha_status (pending or valid)
http-after-response set-header Set-Cookie %[var(txn.crowdsec.captcha_cookie)] if { var(txn.crowdsec.captcha_status) -m found } { var(txn.crowdsec.captcha_cookie) -m found }
## Clear captcha cookie when cookie exists but no captcha_status (Allow decision)
http-after-response set-header Set-Cookie %[var(txn.crowdsec.captcha_cookie)] if { var(txn.crowdsec.captcha_cookie) -m found } !{ var(txn.crowdsec.captcha_status) -m found }
use_backend <whatever>
backend crowdsec-spoa
mode tcp
server s1 127.0.0.1:9000
In the global section of your haproxy.cfg, lua path configuration is also mandatory:
global
[...]
lua-prepend-path /usr/lib/crowdsec-haproxy-spoa-bouncer/lua/?.lua
lua-load /usr/lib/crowdsec-haproxy-spoa-bouncer/lua/crowdsec.lua
setenv CROWDSEC_BAN_TEMPLATE_PATH /var/lib/crowdsec-haproxy-spoa-bouncer/html/ban.html
setenv CROWDSEC_CAPTCHA_TEMPLATE_PATH /var/lib/crowdsec-haproxy-spoa-bouncer/html/captcha.html
An example that includes this snippet can also be found in
/usr/share/doc/crowdsec-haproxy-spoa-bouncer/examples/haproxy.cfg.
Specific featuresβ
To enable CAPTCHA for a domain:β
hosts:
- host: "example.com"
captcha:
site_key: "<your-site-key>"
secret_key: "<your-secret-key>"
provider: "hcaptcha"
The following captcha providers are supported:
hcaptcha
recaptcha
turnstile
HAProxy Behind a CDNβ
When HAProxy is deployed behind an upstream Content Delivery Network (CDN), the source IP seen by HAProxy will be the CDN's edge server IP, not the real client IP. To properly evaluate and apply security rules based on the actual client IP, you need to configure the SPOA to extract the real IP from the CDN-provided header.
Configuration Changesβ
When HAProxy is behind a CDN, modify your /etc/haproxy/crowdsec.cfg to:
- Use only the
crowdsec-httpmessage (thecrowdsec-ipmessage will capture the CDN edge IP, which is not useful) - Extract the real client IP from the CDN header using
req.hdr_ip()to convert it to HAProxy's IP type - Pass the real IP to the bouncer via the SPOE message
/etc/haproxy/crowdsec.cfg (CDN Configuration)
# /etc/haproxy/spoe/crowdsec.cfg
# SPOE section for CDN deployments
# - Uses a single message: crowdsec-http
# - Extracts real client IP from X-Real-IP header (adjust if needed)
# - Falls back to IP remediation if 'remediation' var is not set
[crowdsec]
spoe-agent crowdsec-agent
messages crowdsec-http
option var-prefix crowdsec
option set-on-error error
timeout hello 100ms
timeout idle 30s
timeout processing 500ms
use-backend crowdsec-spoa
log global
# This message extracts the real IP via X-Real-IP and includes all arguments.
# IMPORTANT: req.hdr_ip() returns an IP type (required by SPOE protocol).
# If 'remediation' isn't provided by HAProxy, the bouncer will check IP remediation.
spoe-message crowdsec-http
args remediation=var(txn.crowdsec.remediation) \
crowdsec_captcha_cookie=req.cook(crowdsec_captcha_cookie) \
id=unique-id host=hdr(Host) method=method path=path query=query \
version=req.ver headers=req.hdrs body=req.body url=url ssl=ssl_fc \
src-ip=req.hdr_ip(x-real-ip) src-port=src_port
event on-frontend-http-request
Key Changes Explainedβ
- Single message: Only
crowdsec-httpis used. Thecrowdsec-ipmessage would run aton-client-sessionand capture the CDN's IP, not the real client IP, so it's omitted. - IP extraction: The
req.hdr_ip(x-real-ip)function extracts the IP from theX-Real-IPheader and converts it to HAProxy's IP type, which is required by the SPOE protocol. - Header name: If your CDN uses a different header (e.g.,
X-Forwarded-For,CF-Connecting-IPfor Cloudflare), adjust the header name accordingly. For Cloudflare specifically, usereq.hdr_ip(cf-connecting-ip).
HAProxy Configurationβ
Your /etc/haproxy/haproxy.cfg frontend configuration remains mostly the same, but ensure the CDN header is being passed through:
frontend http-in
bind *:80
# Ensure the CDN header is preserved (may already be done by your CDN)
# You can optionally add debugging with set-header
# http-request set-header X-Real-IP %[req.hdr(X-Real-IP)]
filter spoe engine crowdsec config /etc/haproxy/crowdsec.cfg
http-request set-header X-Crowdsec-Remediation %[var(txn.crowdsec.remediation)]
## Handle 302 redirect for successful captcha validation (native HAProxy redirect)
http-request redirect code 302 location %[var(txn.crowdsec.redirect)] if { var(txn.crowdsec.remediation) -m str "allow" } { var(txn.crowdsec.redirect) -m found }
## Call lua script only for ban and captcha remediations (performance optimization)
http-request lua.crowdsec_handle if { var(txn.crowdsec.remediation) -m str "captcha" }
http-request lua.crowdsec_handle if { var(txn.crowdsec.remediation) -m str "ban" }
## Handle captcha cookie management via HAProxy (new approach)
## Set captcha cookie when SPOA provides captcha_status (pending or valid)
http-after-response set-header Set-Cookie %[var(txn.crowdsec.captcha_cookie)] if { var(txn.crowdsec.captcha_status) -m found } { var(txn.crowdsec.captcha_cookie) -m found }
## Clear captcha cookie when cookie exists but no captcha_status (Allow decision)
http-after-response set-header Set-Cookie %[var(txn.crowdsec.captcha_cookie)] if { var(txn.crowdsec.captcha_cookie) -m found } !{ var(txn.crowdsec.captcha_status) -m found }
use_backend <whatever>
backend crowdsec-spoa
mode tcp
server s1 127.0.0.1:9000
Common CDN Headersβ
| CDN Provider | Header Name | HAProxy Function |
|---|---|---|
| Generic / Most CDNs | X-Real-IP | req.hdr_ip(x-real-ip) |
| Cloudflare | CF-Connecting-IP | req.hdr_ip(cf-connecting-ip) |
| AWS CloudFront | CloudFront-Viewer-Address | req.hdr_ip(cloudfront-viewer-address) |
| Akamai | True-Client-IP | req.hdr_ip(true-client-ip) |
| Azure CDN | X-Forwarded-For | req.hdr_ip(x-forwarded-for) |
Prometheus Metricsβ
Enable and expose metrics:
prometheus:
enabled: true
listen_addr: 127.0.0.1
listen_port: 60601
Access them at http://127.0.0.1:60601/metrics.
Configuration Referenceβ
You can find a default configuration hosted on the Github Repository this is provided with the installation package.
log_modeβ
file|stdout
Where the log contents are written (With file it will be written to log_dir with the name crowdsec-spoa-bouncer.log)
log_dirβ
string
Log directory path that will contain the log file. By default, this should be set to /var/log/crowdsec-spoa/ as this directory is automatically created by the systemd service.
log_levelβ
trace|debug|info|warn|error
Log level (default: info)
log_compressionβ
true|false
Compress log files on rotation (default: true)
log_max_sizeβ
int (in MB)
Max size of log files before rotation (default: 500)
log_max_backupsβ
int
How many backup log files to keep before deletion (can happen before log_max_age is reached) (default: 3)
log_max_ageβ
int (in days)
Max age of backup files before deletion (can happen before log_max_backups is reached) (default: 30)
update_frequencyβ
string (That is parseable by time.ParseDuration)
Frequency to contact the API for new/deleted decisions (default: 10s)
api_urlβ
string
URL of the local API EG: http://127.0.0.1:8080
api_keyβ
string
API key to authenticate with the local API
insecure_skip_verifyβ
true|false
Skip verification of the API certificate, typical for self-signed certificates
listen_tcpβ
string
TCP address and port to listen on for SPOE connections. Format: ip:port or :port
listen_unixβ
string
Unix socket path to listen on for SPOE connections
hostsβ
[]object
List of host configurations for domain-specific settings
hostβ
string
Hostname pattern to match (supports wildcards).
Note: The list of host objects is automatically sorted from longest to shortest pattern, including wildcards. For example, *.example.com (matching all subdomains) will be evaluated before example.com, and the wildcard * (which matches any host) will always be at the bottom of the list. This ensures that more specific patterns take precedence over more general ones.
captchaβ
object
CAPTCHA configuration for this host
providerβ
hcaptcha|recaptcha|turnstile
CAPTCHA provider to use
site_keyβ
string
CAPTCHA site key
secret_keyβ
string
CAPTCHA secret key
fallback_remediationβ
string
ban|allow
If captcha is not configured which remediation to use as a fallback. Can be configured to allow to pass on captcha remediations (default: ban)
timeoutβ
int (in seconds)
HTTP client timeout in seconds, maximum 300 (default: 5)
cookieβ
object
Cookie generation configuration
sign_cookiesβ
true|false
Sign the cookie value (default: true)
secureβ
auto|always|never
Set the secure flag on the cookie. auto relies on the ssl_fc flag from HAProxy (default: auto)
http_onlyβ
true|false
Set the HttpOnly flag on the cookie (default: true)
secretβ
string
Secret used for signed/encrypted cookies (default: uses the secret key of the captcha provider)
session_idle_timeoutβ
string (That is parseable by time.ParseDuration)
Session idle timeout duration (default: 1h)
session_max_timeβ
string (That is parseable by time.ParseDuration)
Maximum session lifetime duration (default: 12h)
session_garbage_secondsβ
int (in seconds)
Interval in seconds for garbage collection of expired sessions (default: 60)
banβ
object
Ban remediation configuration for this host
contact_us_urlβ
string
URL to display in ban templates for users to contact support this value is passed to an anchor tag href value
log_levelβ
trace|debug|info|warn|error
Log level for this specific host (overrides the global log_level setting)
hosts_dirβ
string
A directory containing .yaml files, each representing a host YAML struct. Each file should define all fields required by the host configuration structure.
asn_database_pathβ
string
Path to the GeoIP2 ASN database file (optional)
city_database_pathβ
string
Path to the GeoIP2 City database file (optional)
prometheusβ
object
Prometheus metrics configuration
enabledβ
true|false
Enable Prometheus metrics endpoint
listen_addrβ
string
Address to listen on for Prometheus metrics endpoint
listen_portβ
int
Port to listen on for Prometheus metrics endpoint
Manual installation and advanced configurationβ
We strongly encourage the use of our packages.
Compile the Binaryβ
This requires a whole working golang installation.
git clone https://github.com/crowdsecurity/crowdsec-spoa-bouncer.git
cd crowdsec-spoa-bouncer
make build
Configure the Bouncerβ
sudo mkdir -p /etc/crowdsec/bouncers/
sudo cp config/crowdsec-spoa-bouncer.yaml /etc/crowdsec/bouncers/
The configuration file is located at /etc/crowdsec/bouncers/crowdsec-spoa-bouncer.yaml:
log_mode: file
log_dir: /var/log/crowdsec-spoa/
log_level: info
log_compression: true
log_max_size: 100
log_max_backups: 3
log_max_age: 30
update_frequency: 10s
api_url: http://127.0.0.1:8080/
api_key: ${API_KEY}
insecure_skip_verify: false
listen_tcp: 0.0.0.0:9000
listen_unix: /run/crowdsec-spoa/spoa.sock
prometheus:
enabled: false
listen_addr: 127.0.0.1
listen_port: 60601
Generate an API key:
sudo cscli bouncers add mybouncer
Then update the api_key field in the configuration file.
You can check that the bouncer is correctly installed with cscli:
β― sudo cscli bouncers list
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Name IP Address Valid Last API pull Type
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
cs-spoa-bouncer-1752052534 127.0.0.1 βοΈ crowdsec-spoa-bouncer
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β― sudo cscli bouncers inspect cs-spoa-bouncer-1752052534
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Bouncer: cs-spoa-bouncer-1752052534
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Created At 2025-07-09 09:15:34.685444393 +0000 UTC
Last Update 2025-07-09 12:42:18.92023029 +0000 UTC
Revoked? false
IP Address 127.0.0.1
Type crowdsec-spoa-bouncer
Version v0.0.3-beta29-rpm-pragmatic-arm64-db7065289a0f5ce1c92f34807c9a98b23c07dc90
Last Pull
Auth type api-key
OS ?
Auto Created false
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Configure HAProxyβ
Lua Integration & Environment Variablesβ
In the global section of your haproxy.cfg, configure Lua paths and template environment:
global
lua-prepend-path /usr/lib/crowdsec-haproxy-spoa-bouncer/lua/?.lua
lua-load /usr/lib/crowdsec-haproxy-spoa-bouncer/lua/crowdsec.lua
setenv CROWDSEC_BAN_TEMPLATE_PATH /var/lib/crowdsec-haproxy-spoa-bouncer/html/ban.html
setenv CROWDSEC_CAPTCHA_TEMPLATE_PATH /var/lib/crowdsec-haproxy-spoa-bouncer/html/captcha.html
These variables are used by the Lua module to render proper HTML responses for banned or captcha-validated users.
Add SPOE Filter in frontendβ
frontend test
mode http
bind *:9090
filter spoe engine crowdsec config /etc/haproxy/crowdsec.cfg
http-request set-header X-Crowdsec-Remediation %[var(txn.crowdsec.remediation)] if { var(txn.crowdsec.remediation) -m found }
http-request set-header X-Crowdsec-IsoCode %[var(txn.crowdsec.isocode)] if { var(txn.crowdsec.isocode) -m found }
## Handle 302 redirect for successful captcha validation (native HAProxy redirect)
http-request redirect code 302 location %[var(txn.crowdsec.redirect)] if { var(txn.crowdsec.remediation) -m str "allow" } { var(txn.crowdsec.redirect) -m found }
## Call lua script only for ban and captcha remediations (performance optimization)
http-request lua.crowdsec_handle if { var(txn.crowdsec.remediation) -m str "captcha" }
http-request lua.crowdsec_handle if { var(txn.crowdsec.remediation) -m str "ban" }
## Handle captcha cookie management via HAProxy (new approach)
## Set captcha cookie when SPOA provides captcha_status (pending or valid)
http-after-response set-header Set-Cookie %[var(txn.crowdsec.captcha_cookie)] if { var(txn.crowdsec.captcha_status) -m found } { var(txn.crowdsec.captcha_cookie) -m found }
## Clear captcha cookie when cookie exists but no captcha_status (Allow decision)
http-after-response set-header Set-Cookie %[var(txn.crowdsec.captcha_cookie)] if { var(txn.crowdsec.captcha_cookie) -m found } !{ var(txn.crowdsec.captcha_status) -m found }
use_backend test_backend
Create SPOE Configβ
Create /etc/haproxy/crowdsec.cfg:
/etc/haproxy/crowdsec.cfg
spoe-agent crowdsec-agent
messages crowdsec-ip crowdsec-http
option var-prefix crowdsec
option set-on-error error
timeout hello 100ms
timeout idle 30s
timeout processing 500ms
use-backend crowdsec-spoa
spoe-message crowdsec-ip
args id=unique-id src-ip=src src-port=src_port
event on-client-session
spoe-message crowdsec-http
args remediation=var(txn.crowdsec.remediation) crowdsec_captcha_cookie=req.cook(crowdsec_captcha_cookie) id=unique-id host=hdr(Host) method=method path=path query=query version=req.ver headers=req.hdrs body=req.body url=url ssl=ssl_fc src-ip=src src-port=src_port
event on-frontend-http-request
Add SPOE Backendβ
backend crowdsec-spoa
mode tcp
balance roundrobin
server s1 127.0.0.1:9000
Start the Bouncerβ
Run Directly
sudo ./crowdsec-spoa-bouncer -c /etc/crowdsec/bouncers/crowdsec-spoa-bouncer.yaml
Or Run as a Systemd Service
sudo cp config/crowdsec-spoa-bouncer.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now crowdsec-spoa-bouncer