CrowdSec¶
Optional, and off until switched on. CrowdSec
decides who is banned from what its agents see across the fleet; HAProxy asks
about each visitor through a small local agent, the SPOA bouncer
(crowdsec-haproxy-spoa-bouncer, from CrowdSec's own repository), and answers
a banned address with a 403 page — or a captcha first, for hCaptcha, reCAPTCHA
or Cloudflare Turnstile. The bouncer runs on every node and polls your CrowdSec
LAPI for decisions; one API key (cscli bouncers add haproxy-manager on the
CrowdSec host) serves every node. This page is about the setting; the LAPI and
its agents are yours to run.
The settings¶
Access › CrowdSec. The header carries a pill, saved: on or saved: off, and a status line for this node; the fields, in order:
| Field | Notes |
|---|---|
| LAPI URL | your CrowdSec local API, http://192.168.1.5:8080/ — the machine running the agent |
| API key | from cscli bouncers add; left empty, the stored key is kept |
| Skip TLS verification | for a LAPI on https:// with a certificate this node does not trust |
| What a banned visitor gets | ban (a 403 page) or captcha (a challenge first; solving it lets the visitor through) |
| Captcha provider, site key, secret key | captcha mode only — hCaptcha, reCAPTCHA or Cloudflare Turnstile, with the keys from the provider's dashboard; an empty secret keeps the stored one |
| Contact URL on the ban page | optional — somewhere a wrongly banned visitor can reach you; a form rather than a mailto:, which crawlers harvest |
| Never ask about | address lists CrowdSec is not consulted for — the LAN, typically; everyone else is |
| Enable CrowdSec | last, so the rest is filled in before it is turned on |
Two buttons. Save stores the settings and answers Saved. Apply to make it so on this node; the other nodes follow. — the settings are shared, and they reach every node with the next Apply like the rest of the configuration. Test the LAPI connection asks the LAPI one question with the credentials in the form and says what came back: the LAPI answers and accepts the key, refused the key (403), or did not answer. One rule governs it: the stored key is only ever sent to the stored URL. Testing a different URL needs a key typed into the form, so a mistyped address cannot hand your key to whatever answers there.
Save checks the result before storing it: the configuration these settings
render to is run through haproxy -c against staged copies of the SPOE engine
and the error pages, in a temporary directory, so a setting HAProxy would
refuse is never stored and nothing under /etc/haproxy changes until Apply.
The 403 page is the one under Error Pages for 403 when there is one, else the bouncer's own, with a contact link when a contact URL is set. Fail-open, by design: a bouncer that is down or slow leaves the request alone rather than taking the service down with it. It is a filter on top of your services, not a gate they depend on.
What Apply does¶
Each Apply, in this order: haproxy -c over the rendered configuration against
staged copies of the files it names; then the error pages and the CrowdSec
files are written for real — the bouncer's YAML and the SPOE engine HAProxy
loads (/etc/haproxy/crowdsec.cfg, HAM_CROWDSEC_SPOE); the bouncer is
started, restarted when its configuration changed, or stopped when CrowdSec is
off; and only then haproxy.cfg is installed and HAProxy reloaded, so the
first request after the reload finds a bouncer already listening.
With CrowdSec on, every HTTP listener asks the bouncer before anything else is done for a request; a TCP listener is left alone, since the answer is a page.
The bouncer on each node¶
The bouncer is a package from CrowdSec's repository, not the distribution's, and each install path handles that differently:
| Install | What you get |
|---|---|
| Docker image | the bouncer is in the image |
install.sh, Proxmox script |
CrowdSec's apt repository and the package are added, and the service left stopped |
.deb / .rpm |
the package only recommends the bouncer; the app fetches it itself when needed |
The app installs the bouncer only when CrowdSec is switched on and the
binary is missing — at startup and at Apply. A node that never enabled
CrowdSec is never given a third-party repository. The install adds CrowdSec's
packagecloud repository (on the
Debian family the bookworm suite, whose build is a static binary; on the RPM
family a .repo file for el/<major> or fedora/<version>), fetches the
repository's signing key over HTTPS and checks it against the fingerprint
6A89E3C2303A901A889971D3376ED5326E93CD0C before anything is installed. The
same fingerprint is pinned on every path — the image build, install.sh, the
Proxmox script and the app — so a key that does not match stops the install
rather than being trusted. A failed install is not retried for six hours (the
marker /var/lib/haproxy-manager/crowdsec-install-tried holds the time), and
the node serves everything else meanwhile.
A node that must not carry the bouncer is installed with
install.sh --without-crowdsec, which leaves the marker
/var/lib/haproxy-manager/crowdsec-not-wanted; the app honours it, and it
survives install.sh --update — which is what the one-click updater runs. The
status line on the CrowdSec page shows the bouncer's version and service state,
or bouncer not installed — fetched at the next Apply once CrowdSec is on, or
now: with an Install on this node button, which clears both markers and
installs at once. The install call is node-local, so the button works on a
passive node too — it is the one thing on the page a read-only node may do.
In the container the bouncer runs under supervisord as the crowdsec-spoa user
through /usr/local/bin/crowdsec-run, a loop that starts it again every 10 s
while the LAPI cannot be reached (the bouncer halts when the LAPI is down at
startup, and supervisord would otherwise give up on it). The status the page
reports is the bouncer process itself, not the loop around it. It is not
started with the container: the app starts it once CrowdSec is on, and stops it
when CrowdSec is switched off.
The bouncer's own log is not on the Logs page: journalctl -u
crowdsec-spoa-bouncer on a server, docker logs (its standard output) in the
container.
What it looks like on disk¶
| Path | What | Written |
|---|---|---|
config.json section crowdsec |
the settings, the LAPI key, the captcha secret and the signing key — mode 0600 like the rest | on Save |
/etc/crowdsec/bouncers/crowdsec-spoa-bouncer.yaml (HAM_CROWDSEC_BOUNCER_CFG) |
the bouncer's configuration, with the key — mode 0640, root:crowdsec-spoa, via a temporary file and rename |
at Apply |
/etc/haproxy/crowdsec.cfg (HAM_CROWDSEC_SPOE) |
the SPOE engine HAProxy loads | at Apply |
/var/lib/haproxy-manager/crowdsec-not-wanted |
this node is not to get the bouncer | by --without-crowdsec |
/var/lib/haproxy-manager/crowdsec-install-tried |
when an install last failed | by a failed install |
The CrowdSec settings are shared, so they are in every history snapshot and a history restore puts them back with the rest. They are not in the backup file from Settings › Backup & Export, which carries only the HAProxy, ACME and access sections; a restore leaves CrowdSec as the node has it. The markers are each node's own.
Mixed versions¶
Upgrade every node before editing rules or services
The crowdsec section is part of the shared fingerprint, so during a
rolling upgrade from 1.x the header says configuration differs until
every node runs 2.0 — expected, and it clears by itself. More important: a
1.x node reads only the old rule shape, so a rule saved on a 2.0 node with
both any of these and all of these filled would apply there without
its conditions. The Cluster page names the nodes still on 1.x; upgrade
them before editing rules or services on a 2.0 node.