π 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 LAPI decisions periodically)
- mTLS to LAPI (via
cert_path/key_path/ca_cert_path) - IP / range / country decisions
- Ban remediation (custom HTML / redirects)
- CAPTCHA remediation (hCaptcha / reCAPTCHA / Turnstile)
- GeoIP headers (ASN / Country)
- AppSec (WAF evaluation via CrowdSec AppSec)
- Prometheus metrics
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
Containerβ
The container image runs the SPOA bouncer (it does not bundle HAProxy): crowdsecurity/spoa-bouncer.
Quick start:
docker run -d \
--name crowdsec-spoa-bouncer \
-e CROWDSEC_KEY="<your-lapi-api-key>" \
-e CROWDSEC_URL="http://crowdsec:8080/" \
-p 9000:9000 \
-p 6060:6060 \
crowdsecurity/spoa-bouncer
If HAProxy runs in another container (for example in Docker Compose), point the SPOA backend to crowdsec-spoa-bouncer:9000.
Docker Compose exampleβ
services:
crowdsec:
image: crowdsecurity/crowdsec:latest
restart: unless-stopped
ports:
- 127.0.0.1:8080:8080
environment:
COLLECTIONS: "crowdsecurity/haproxy"
BOUNCER_KEY_SPOA: "${BOUNCER_KEY_SPOA}"
GID: "${GID-1000}"
volumes:
- crowdsec-db:/var/lib/crowdsec/data/
- crowdsec-config:/etc/crowdsec/
# Optional: configure log acquisition for your setup
# - ./crowdsec/acquis.yaml:/etc/crowdsec/acquis.yaml:ro
networks:
- crowdsec
crowdsec-spoa-bouncer:
image: crowdsecurity/spoa-bouncer:latest
restart: unless-stopped
depends_on:
- crowdsec
environment:
CROWDSEC_KEY: "${BOUNCER_KEY_SPOA}"
CROWDSEC_URL: "http://crowdsec:8080/"
volumes:
- templates:/var/lib/crowdsec-haproxy-spoa-bouncer/html/
- lua:/usr/lib/crowdsec-haproxy-spoa-bouncer/lua/
networks:
- crowdsec
haproxy:
image: haproxy:latest
restart: unless-stopped
volumes:
- ./config/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
- ./config/crowdsec.cfg:/etc/haproxy/crowdsec.cfg:ro
- templates:/var/lib/crowdsec-haproxy-spoa-bouncer/html/:ro
- lua:/usr/lib/crowdsec-haproxy-spoa-bouncer/lua/:ro
ports:
- "80:80"
- "443:443"
depends_on:
- crowdsec-spoa-bouncer
networks:
- crowdsec
volumes:
crowdsec-db:
crowdsec-config:
lua:
templates:
networks:
crowdsec:
Create ./config/haproxy.cfg and ./config/crowdsec.cfg from the βHAProxy Configurationβ section below (in Compose, the SPOA backend server should target crowdsec-spoa-bouncer:9000). Set BOUNCER_KEY_SPOA in a .env file or your shell environment, and persist CrowdSec directories (at least /var/lib/crowdsec/data/) as described in the Docker getting started guide.
To use a custom configuration file:
docker run -d \
--name crowdsec-spoa-bouncer \
-v $PWD/crowdsec-spoa-bouncer.yaml:/etc/crowdsec/bouncers/crowdsec-spoa-bouncer.yaml:ro \
-p 9000:9000 \
crowdsecurity/spoa-bouncer
For all container options and environment variables, see: https://github.com/crowdsecurity/cs-haproxy-spoa-bouncer/blob/main/docker/README.md
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-tcp
groups crowdsec-http-body crowdsec-http-no-body
option var-prefix crowdsec
option set-on-error error
timeout hello 200ms
timeout idle 55s
timeout processing 500ms
use-backend crowdsec-spoa
log global
## TCP/IP level check - runs early to check IP remediation
## Uses event directive to trigger on each new client session (not sent as a group)
spoe-message crowdsec-tcp
args id=unique-id src-ip=src src-port=src_port
event on-client-session
## HTTP message with body - used when body size is within limit for AppSec
## Note: Host and captcha cookie are extracted from headers=req.hdrs, no need to send separately
spoe-message crowdsec-http-body
args remediation=var(txn.crowdsec.remediation) id=unique-id 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
## HTTP message without body - used when body is too large or not needed
## Note: Host and captcha cookie are extracted from headers=req.hdrs, no need to send separately
spoe-message crowdsec-http-no-body
args remediation=var(txn.crowdsec.remediation) id=unique-id method=method path=path query=query version=req.ver headers=req.hdrs url=url ssl=ssl_fc src-ip=src src-port=src_port
## Group for HTTP message with body - used when body size is within limit for AppSec
spoe-group crowdsec-http-body
messages crowdsec-http-body
## Group for HTTP message without body - used when body is too large or not needed
spoe-group crowdsec-http-no-body
messages crowdsec-http-no-body
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
# Select which SPOE group to send (with/without body)
acl body_within_limit req.body_size -m int le 51200 # 50KB - stay safely under SPOE frame limit
http-request send-spoe-group crowdsec crowdsec-http-body if body_within_limit || !{ req.body_size -m found }
http-request send-spoe-group crowdsec crowdsec-http-no-body if !body_within_limit { req.body_size -m found }
http-request set-header X-Crowdsec-Remediation %[var(txn.crowdsec.remediation)]
## Handle 302 redirect for successful captcha validation (redirect to current request URL)
http-request redirect code 302 location %[url] 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.
Real client IP behind a CDN (or upstream proxy)β
When HAProxy is deployed behind an upstream CDN/proxy, the source IP seen by HAProxy may be the CDN edge IP, not the real client IP. Set the source IP in HAProxy before calling send-spoe-group:
frontend http-in
# Extract real client IP from proxy headers (runs before SPOE groups)
# Priority: X-Real-IP > CF-Connecting-IP > X-Forwarded-For > direct src
http-request set-src hdr_ip(X-Real-IP) if { req.hdr(X-Real-IP) -m found }
http-request set-src hdr_ip(CF-Connecting-IP) if { req.hdr(CF-Connecting-IP) -m found } !{ req.hdr(X-Real-IP) -m found }
http-request set-src hdr_ip(X-Forwarded-For) if { req.hdr(X-Forwarded-For) -m found } !{ req.hdr(X-Real-IP) -m found } !{ req.hdr(CF-Connecting-IP) -m found }
filter spoe engine crowdsec config /etc/haproxy/crowdsec.cfg
acl body_within_limit req.body_size -m int le 51200
http-request send-spoe-group crowdsec crowdsec-http-body if body_within_limit || !{ req.body_size -m found }
http-request send-spoe-group crowdsec crowdsec-http-no-body if !body_within_limit { req.body_size -m found }
In upstream-proxy/CDN setups, the TCP check (crowdsec-tcp) still runs at on-client-session and may see the proxy IP; calling an HTTP group after set-src ensures the request is evaluated with the real client IP.
Common CDN headersβ
| CDN Provider | Header Name | HAProxy Function |
|---|---|---|
| Generic / Most CDNs | X-Real-IP | hdr_ip(X-Real-IP) |
| Cloudflare | CF-Connecting-IP | hdr_ip(CF-Connecting-IP) |
| AWS CloudFront | CloudFront-Viewer-Address | hdr_ip(CloudFront-Viewer-Address) |
| Akamai | True-Client-IP | hdr_ip(True-Client-IP) |
| Azure CDN | X-Forwarded-For | hdr_ip(X-Forwarded-For) |
How-to guidesβ
- CAPTCHA: enable per domain
- AppSec: forward requests for WAF evaluation
- Prometheus: expose metrics endpoint
Enable CAPTCHA for a domainβ
hosts:
- host: "example.com"
captcha:
site_key: "<your-site-key>"
secret_key: "<your-secret-key>"
provider: "hcaptcha"
signing_key: "<your-32-byte-minimum-secret-key>"
The following captcha providers are supported:
hcaptcha
recaptcha
turnstile
Enable AppSec (WAF) forwardingβ
The SPOA bouncer can forward requests to CrowdSec AppSec for WAF evaluation.
Prerequisites:
- CrowdSec AppSec is installed and configured (see intro and quickstart).
Enable it in the bouncer configuration:
# Global AppSec URL (optional)
appsec_url: http://127.0.0.1:7422
appsec_timeout: 200ms
hosts:
- host: "*"
appsec:
always_send: false
# url: http://custom-appsec:7422 # optional per-host override
# api_key: custom-key # optional per-host override
HAProxy requirements when using AppSec (and/or captcha):
- Enable request buffering:
option http-buffer-request - Increase HAProxy buffer size (max 64KB):
tune.bufsize 65536 - Use the
crowdsec-http-bodygroup when the body is available (see thebody_within_limit+send-spoe-groupexample above)
Because request-body forwarding is constrained by HAProxy/SPOE/SPOP limits, keep an explicit body size limit (for example 51200) and consider a layered approach (IP remediation at HAProxy, deeper inspection downstream).
Expose 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β
The upstream example configurations live in the cs-haproxy-spoa-bouncer repository:
crowdsec.cfg: https://github.com/crowdsecurity/cs-haproxy-spoa-bouncer/blob/main/config/crowdsec.cfg- HAProxy examples: https://github.com/crowdsecurity/cs-haproxy-spoa-bouncer/tree/main/config
YAML snippets below show each key in context.
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_mode: "file" # or "stdout"
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.
When installed from packages, the systemd unit runs the bouncer as the crowdsec-spoa user and creates /var/log/crowdsec-spoa/ automatically (via LogsDirectory=). If you set a custom log_dir, make sure the directory exists and that the crowdsec-spoa user has permission to read/write there.
log_dir: "/var/log/crowdsec-spoa/"
log_levelβ
trace|debug|info|warn|error
Log level (default: info)
log_level: "info"
compress_logsβ
true|false
Compress log files on rotation (default: true)
compress_logs: true
log_max_sizeβ
int (in MB)
Max size of log files before rotation (default: 500)
log_max_size: 500
log_max_filesβ
int
How many backup log files to keep before deletion (can happen before log_max_age is reached) (default: 3)
log_max_files: 3
log_max_ageβ
int (in days)
Max age of backup files before deletion (can happen before log_max_files is reached) (default: 30)
log_max_age: 30
update_frequencyβ
string (parseable by time.ParseDuration)
Frequency to contact the API for new/deleted decisions (default: 10s)
update_frequency: "10s"
api_urlβ
string
URL of the local API EG: http://127.0.0.1:8080
api_url: "https://lapi.example.com:8080/"
api_keyβ
string
API key to authenticate with the local API
api_key: "<your-lapi-api-key>"
insecure_skip_verifyβ
true|false
Skip verification of the API certificate, typical for self-signed certificates
insecure_skip_verify: false
cert_pathβ
string
Client certificate path for mTLS to LAPI.
cert_path: "/etc/ssl/certs/client.crt"
key_pathβ
string
Client private key path for mTLS to LAPI.
key_path: "/etc/ssl/private/client.key"
ca_cert_pathβ
string
CA certificate path for validating the LAPI certificate (mTLS / custom CAs).
ca_cert_path: "/etc/ssl/certs/ca.crt"
retry_initial_connectβ
true|false
Retry connecting to LAPI on startup instead of failing fast.
retry_initial_connect: true
scopesβ
[]string
Only pull decisions matching these scopes (for example ip, range, country).
scopes: ["ip", "range", "country"]
scenarios_containingβ
[]string
Only pull decisions whose scenario contains one of these strings.
scenarios_containing: ["crowdsecurity/"]
scenarios_not_containingβ
[]string
Do not pull decisions whose scenario contains one of these strings.
scenarios_not_containing: ["whitelist"]
originsβ
[]string
Only pull decisions from these origins.
origins: ["crowdsecurity", "lists"]
listen_tcpβ
string
TCP address and port to listen on for SPOE connections. Format: ip:port or :port
listen_tcp: "0.0.0.0:9000"
listen_unixβ
string
Unix socket path to listen on for SPOE connections
listen_unix: "/run/crowdsec-spoa/spoa.sock"
hostsβ
[]object
List of host configurations for domain-specific settings
hosts:
- host: "example.com"
captcha:
provider: "turnstile"
site_key: "<your-site-key>"
secret_key: "<your-secret-key>"
signing_key: "<your-32-byte-minimum-secret-key>"
ban:
contact_us_url: "https://example.com/support"
appsec:
always_send: false
log_level: "info"
- host: "*"
captcha:
fallback_remediation: "allow"
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.
hosts:
- host: "*.example.com" # <-- host pattern
captchaβ
object
CAPTCHA configuration for this host
hosts:
- host: "example.com"
captcha:
provider: "turnstile"
site_key: "<your-site-key>"
secret_key: "<your-secret-key>"
signing_key: "<your-32-byte-minimum-secret-key>"
providerβ
hcaptcha|recaptcha|turnstile
CAPTCHA provider to use
hosts:
- host: "example.com"
captcha:
provider: "turnstile" # <-- provider
site_keyβ
string
CAPTCHA site key
hosts:
- host: "example.com"
captcha:
site_key: "<your-site-key>" # <-- site_key
secret_keyβ
string
CAPTCHA secret key
hosts:
- host: "example.com"
captcha:
secret_key: "<your-secret-key>" # <-- 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)
hosts:
- host: "*"
captcha:
fallback_remediation: "allow" # <-- fallback_remediation
timeoutβ
int (in seconds)
HTTP client timeout in seconds, maximum 300 (default: 5)
hosts:
- host: "example.com"
captcha:
timeout: 5 # <-- timeout (seconds)
cookieβ
object
Cookie generation configuration
hosts:
- host: "example.com"
captcha:
cookie:
secure: "auto"
http_only: true
secureβ
auto|always|never
Set the secure flag on the cookie. auto relies on the ssl_fc flag from HAProxy (default: auto)
hosts:
- host: "example.com"
captcha:
cookie:
secure: "auto" # <-- secure
http_onlyβ
true|false
Set the HttpOnly flag on the cookie (default: true)
hosts:
- host: "example.com"
captcha:
cookie:
http_only: true # <-- http_only
pending_ttlβ
string (parseable by time.ParseDuration)
TTL for pending captcha tokens (default: 30m)
hosts:
- host: "example.com"
captcha:
pending_ttl: "30m" # <-- pending_ttl
passed_ttlβ
string (parseable by time.ParseDuration)
TTL for passed captcha tokens (default: 24h)
hosts:
- host: "example.com"
captcha:
passed_ttl: "24h" # <-- passed_ttl
signing_keyβ
string (minimum 32 bytes)
Key used to sign captcha tokens (required when using captcha). Generate one with openssl rand -hex 32. If you run multiple SPOA instances serving the same domains, use the same signing_key everywhere so tokens validate consistently.
hosts:
- host: "example.com"
captcha:
signing_key: "<your-32-byte-minimum-secret-key>" # <-- signing_key
banβ
object
Ban remediation configuration for this host
hosts:
- host: "example.com"
ban:
contact_us_url: "https://example.com/support"
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
hosts:
- host: "example.com"
ban:
contact_us_url: "https://example.com/support" # <-- contact_us_url
log_levelβ
trace|debug|info|warn|error
Log level for this specific host (overrides the global log_level setting), useful when debugging a single host.
hosts:
- host: "example.com"
log_level: "info" # <-- host log_level
appsecβ
object
Host-level AppSec configuration (optional).
hosts:
- host: "example.com"
appsec:
always_send: false
always_sendβ
true|false
When false, AppSec evaluation is skipped if a higher-priority remediation already applies (for example ban or captcha).
hosts:
- host: "example.com"
appsec:
always_send: false # <-- always_send
urlβ
string
AppSec URL override for this host (defaults to global appsec_url).
hosts:
- host: "example.com"
appsec:
url: "http://127.0.0.1:7422" # <-- url
api_keyβ
string
AppSec API key override for this host (defaults to top-level api_key).
hosts:
- host: "example.com"
appsec:
api_key: "<appsec-api-key>" # <-- api_key
timeoutβ
string (parseable by time.ParseDuration)
AppSec request timeout for this host (default: 200ms).
hosts:
- host: "example.com"
appsec:
timeout: "200ms" # <-- timeout
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.
hosts_dir: "/etc/crowdsec/bouncers/hosts.d"
asn_database_pathβ
string
Path to the GeoIP2 ASN database file (optional)
asn_database_path: "/var/lib/crowdsec/data/GeoLite2-ASN.mmdb"
city_database_pathβ
string
Path to the GeoIP2 City database file (optional)
city_database_path: "/var/lib/crowdsec/data/GeoLite2-City.mmdb"
prometheusβ
object
Prometheus metrics configuration
prometheus:
enabled: true
listen_addr: "127.0.0.1"
listen_port: "60601"
enabledβ
true|false
Enable Prometheus metrics endpoint
prometheus:
enabled: true # <-- enabled
listen_addrβ
string
Address to listen on for Prometheus metrics endpoint
prometheus:
listen_addr: "127.0.0.1" # <-- listen_addr
listen_portβ
string
Port to listen on for Prometheus metrics endpoint
prometheus:
listen_port: "60601" # <-- listen_port
pprofβ
object
Enable and expose Go pprof endpoints (debugging only).
pprof:
enabled: false
listen_addr: "127.0.0.1"
listen_port: "6060"
enabledβ
true|false
Enable the pprof endpoint (debugging only).
pprof:
enabled: true # <-- enabled
listen_addrβ
string
Address to listen on for pprof endpoint.
pprof:
listen_addr: "127.0.0.1" # <-- listen_addr
listen_portβ
string
Port to listen on for pprof endpoint.
pprof:
listen_port: "6060" # <-- listen_port
appsec_urlβ
string
Global CrowdSec AppSec URL (optional).
appsec_url: "http://127.0.0.1:7422"
appsec_timeoutβ
string (parseable by time.ParseDuration)
Global AppSec request timeout (default: 200ms).
appsec_timeout: "200ms"
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/cs-haproxy-spoa-bouncer.git
cd cs-haproxy-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
compress_logs: true
log_max_size: 100
log_max_files: 3
log_max_age: 30
update_frequency: 10s
api_url: http://127.0.0.1:8080/
api_key: ${API_KEY}
insecure_skip_verify: false
# Optional (mTLS to LAPI)
#cert_path: /etc/ssl/certs/client.crt
#key_path: /etc/ssl/private/client.key
#ca_cert_path: /etc/ssl/certs/ca.crt
#retry_initial_connect: true
# Host configuration examples
hosts:
- host: "example.com"
captcha:
provider: "turnstile"
site_key: "<your-site-key>"
secret_key: "<your-secret-key>"
signing_key: "<your-32-byte-minimum-secret-key>"
pending_ttl: "30m"
passed_ttl: "24h"
cookie:
secure: "auto"
http_only: true
ban:
contact_us_url: "https://example.com/support"
appsec:
always_send: false
# url: "http://127.0.0.1:7422" # optional per-host override
# api_key: "<appsec-api-key>" # optional per-host override
# timeout: "200ms" # optional per-host override
log_level: "info"
- host: "*"
captcha:
fallback_remediation: "allow"
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"
# Optional (AppSec)
#appsec_url: http://127.0.0.1:7422
#appsec_timeout: 200ms
# Optional (debug only)
#pprof:
# enabled: false
# listen_addr: 127.0.0.1
# listen_port: "6060"
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β
Follow the βHAProxy Configurationβ section above. Use send-spoe-group and the upstream /etc/haproxy/crowdsec.cfg (with spoe-groups). The upstream repository also ships full examples under config/.
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