CrowdSec
Canasta includes an optional CrowdSec security profile that watches the Caddy access log, detects malicious traffic (scanners, brute-force, known CVE probes), and blocks offending IP addresses at the edge before requests reach MediaWiki. It is off by default; enabling it starts protecting the wiki right away β the bouncer that does the blocking is enrolled automatically.
CrowdSec works on both orchestrators β Docker Compose and Kubernetes. The canasta crowdsec commands and the enable/disable flow are identical on each; only the under-the-hood placement differs (on Compose the engine is its own container; on Kubernetes its local API runs in its own pod and an agent in the Caddy pod reads the access log). Orchestrator-specific details are called out where they matter below.
How it works
Enabling CrowdSec adds two pieces:
- A CrowdSec engine container that tails Caddy's access log, runs it through the
crowdsecurity/caddyandcrowdsecurity/http-cvedetection scenarios, and publishes IP decisions (bans). - A bouncer built into Caddy (the
ghcr.io/canastawiki/canasta-caddyimage, swapped in automatically) that pulls those decisions and returns403 Forbiddento banned clients.
The engine also subscribes to CrowdSec's community blocklist β a crowd-sourced list of IPs known to be malicious across the wider CrowdSec network β so the wiki benefits from the community's detections, not just its own (see below).
Detection acts on the real client IP. On Docker Compose, Caddy sits behind the host's HAProxy edge and takes the client address the edge forwards; when a CDN or WAF is in front you must tell Canasta which proxy to trust (see below), or both detection and blocking would act on the proxy's IP.
On Docker Compose, an instance with CrowdSec also runs a second bouncer, crowdsec-spoa, for the edge. The edge asks it about every request before its deny lists and rate limits, and answers a banned client with a plain 403. If the bouncer does not answer in time, the edge lets the request through, and Caddy's own bouncer still refuses banned clients. canasta crowdsec status lists the two bouncers as canasta-edge and canasta-caddy. Decisions stay per instance; a ban on one instance does not apply to the others on the host. See CrowdSec at the edge.
Where these pieces run depends on the orchestrator. On Docker Compose the engine is a separate crowdsec container and its state (registered bouncers, decisions, the cached community blocklist) lives in Docker named volumes. On Kubernetes (as of Canasta CLI 4.25.1) the engine is split in two. Its local API, which holds the registered bouncers and decisions and talks to CrowdSec's Central API, runs in its own canasta-<id>-crowdsec Deployment, and its state lives in a per-instance PersistentVolumeClaim (canasta-<id>-crowdsec). A crowdsec-agent sidecar in the Caddy pod reads the Caddy access log, runs the detection scenarios, and sends what it detects to the local API. The agent logs in with CROWDSEC_AGENT_PASSWORD, which the CLI generates in .env when CrowdSec is enabled. Because the Caddy pod holds no CrowdSec volume, Caddy rolls without downtime β a replacement pod is ready before the old one stops β and neither a restart of the local API nor a failing agent takes Caddy out of service. The CLI commands below are the same either way.
Enabling CrowdSec
Enabling CrowdSec is a single step: turn on the profile. The CLI registers the bouncer for you on the next start, so enforcement begins automatically β there is no separate enrollment command to run in the normal case.
New instances
Create an .env file with CrowdSec enabled, then pass it to canasta create:
echo "CANASTA_ENABLE_CROWDSEC=true" > my.env
canasta create -i myinstance -w mywiki -e my.env
Existing instances
canasta config set CANASTA_ENABLE_CROWDSEC=true
This saves the setting and restarts the instance with the CrowdSec engine running. In both cases the CLI automatically:
- Switches the Caddy image to the bouncer-enabled
ghcr.io/canastawiki/canasta-caddybuild (feature-off instances stay on stock Caddy) β on Kubernetes this image switch is reconciled into the instance'svalues.yamlat deploy time - Brings the engine up β on Compose by adding
crowdsectoCOMPOSE_PROFILES; on Kubernetes by settingcrowdsec.enabledinvalues.yaml, which deploys the local API and rolls the Caddy pod so the agent sidecar joins it - Registers the Caddy bouncer with the engine, stores its key as
CROWDSEC_BOUNCER_API_KEY, re-renders the Caddyfile, and restarts so the bouncer is enforcing β all on the start that enabling triggers - Materializes
config/crowdsec/acquis.yamlandconfig/crowdsec/whitelists.yamlin the instance directory
- Note: the automatic bouncer enrollment happens on the start/restart that enabling triggers. If you enable with
--no-restart, the bouncer is not enrolled until the nextcanasta start(or you can runcanasta crowdsec bouncer-enrollby hand). Until then Caddy boots cleanly but enforces nothing.
- GitOps-managed Kubernetes instances: as of Canasta CLI 4.25.0,
canasta config setdoes not restart a gitops-managed Kubernetes instance; it stages the change for Argo CD. The sync starts the engine, but only a start enrolls the bouncer, so enroll it yourself after the sync and push again to record its key:
canasta config set -i myinstance CANASTA_ENABLE_CROWDSEC=true
canasta gitops push -i myinstance
canasta gitops sync -i myinstance
canasta crowdsec bouncer-enroll -i myinstance
canasta gitops push -i myinstance
canasta gitops sync -i myinstance
bouncer-enrollreports that the bouncer is registered and its key stored, and that Caddy uses it only aftercanasta gitops push. Until then,canasta crowdsec statusshows the bouncer as not pulling or stale.- On a stopped instance, the bouncer is enrolled when the instance starts, but its configuration must then be pushed before Caddy uses it.
config setand the start both say so:canasta config set CANASTA_ENABLE_CROWDSEC=true -i <id>canasta gitops push -i <id>canasta start -i <id>β enrolls the bouncercanasta gitops push -i <id>β records the bouncer configuration (and, with encrypted secrets, its key); Argo CD applies it within about a minute, or runcanasta gitops sync -i <id>canasta gitops status -i <id>should then list nothing under "Changes the next push will render".
Bouncer enrollment
The Caddy bouncer is enrolled automatically the first time the instance starts after CrowdSec is enabled, so you normally never run this by hand. The command remains for maintenance:
canasta crowdsec bouncer-enroll
This registers the Caddy bouncer with the engine, stores the generated API key as CROWDSEC_BOUNCER_API_KEY, and restarts so the bouncer begins enforcing. On a gitops-managed Kubernetes instance, Caddy uses the new key only after canasta gitops push; the command says so. It is idempotent β if a bouncer is already enrolled and a key is stored, it reports success and does nothing.
Use --force to revoke the existing bouncer and issue a fresh key β for example to rotate a key you believe is compromised, or to recover one that has drifted out of sync with the engine:
canasta crowdsec bouncer-enroll --force
This registers the local bouncer with the local engine; it is unrelated to the cloud CrowdSec Console used for the full community blocklist.
Community blocklist
Beyond detecting attacks against your own wiki, CrowdSec blocks IPs that have misbehaved across the wider CrowdSec network β a crowd-sourced blocklist enforced by the same bouncer as your local decisions.
Lite list (default, automatic)
When CrowdSec is enabled, Canasta registers the engine with CrowdSec's Central API for you, which subscribes it to the community blocklist β no setup required. Once enabled and the bouncer is enrolled, known-malicious IPs from the community are blocked alongside your own local detections. This is the smaller "Lite" tier of the list, refreshed roughly every two hours.
Confirm it is active with canasta crowdsec status, which reports the Central API (community blocklist) registration state, how many community-blocklist IPs are loaded, and when the blocklist last updated. Updates normally arrive about every two hours, so if there has been none for more than three hours β or none at all since registration β the status line shows a warning instead.
Full list (optional)
To receive the full community blocklist (and optionally other console-managed blocklists), enroll the engine in the free CrowdSec Console:
1. Sign in at app.crowdsec.net. Open Engines (or Getting started); the engine-onboarding view has a step Connect with the Console showing a cscli console enroll command. The value in the "or use your enroll key" box is your enrollment key β an opaque string such as cmq9xβ¦, not a placeholder. Copy it.
2. Run the console-enroll command with that key:
canasta crowdsec console-enroll <enrollment-key>
3. Back in the Console, open Engines. Your engine appears under Pending enroll request (hard-refresh the page if it still shows the onboarding view). Click Accept enroll.
4. Reload the engine so it picks up the accepted enrollment β this restarts only the CrowdSec engine, not the whole instance:
canasta crowdsec reload
Then, in the Console under Blocklists, subscribe the engine to the CrowdSec Community Blocklist (and any others you want).
Subscribed blocklists are pulled on CrowdSec's Central API schedule (about every two hours) and enforced by the bouncer within roughly 15 seconds of an update. Keeping full-list access requires the engine to keep sharing signals (the default), so a wiki that detects little of its own traffic may fall back toward the Lite cap.
- Note:
console-enrollis distinct frombouncer-enroll.bouncer-enrollwires the local bouncer to the local engine (required for any enforcement, automatic on enable);console-enrollconnects the engine to CrowdSec's cloud Console (optional, only to upgrade the community blocklist from Lite to full).
Scraper and AI-crawler protection
CrowdSec contributes two of the layers that keep content scrapers and AI/LLM crawlers off your wiki. Blocking crawlers is a deliberate trade-off β you may want search-engine and AI-search crawlers for discoverability and citations β so tune it rather than blocking everything. The full cross-tool walkthrough, including edge User-Agent blocking in Caddy and robots.txt opt-outs, is in Help:User journeys/Combatting bots and scrapers.
Behavioural detection (already active)
The crowdsecurity/caddy collection your engine runs includes base-http-scenarios, which already bans:
http-crawl-non_staticsβ aggressive crawling of dynamic pages (i.e. bulk content scraping); andhttp-bad-user-agentβ requests from known scraper/bot user-agents.
Legitimate crawlers (Googlebot, Bingbot) are protected from false bans by the bundled seo-bots-whitelist. List the loaded scenarios with:
canasta crowdsec scenarios
Blocklists
In the CrowdSec Console under Blocklists, subscribe this engine to free lists that catch scraper infrastructure β for example Free Proxies (scrapers commonly route through open proxies) and Tor exit nodes (if you don't expect legitimate Tor readers). You already receive the CrowdSec Community Blocklist via the Central API.
CrowdSec's dedicated AI Crawlers blocklist is a paid (Platinum) list, but CrowdSec offers it free to open-source / FOSS community projects β a public wiki usually qualifies. Email community@crowdsec.net describing the project to request access.
When you unsubscribe from a blocklist β or delete the engine β in the Console, the IPs it already pulled stay cached in the engine's local database and the bouncer keeps blocking them; they are not removed automatically. This is usually harmless (you keep the protection you'd built up), but to stop blocking those addresses, run:
canasta crowdsec reload --purge-blocklist <name> # clear one list (the name shown in status)
canasta crowdsec reload --purge-blocklists # clear all console blocklists
This deletes only the locally cached decisions β lists you're still subscribed to refill on the next pull. canasta crowdsec status shows the active lists under "Console blocklists in effect (cached locally)", which is also where you find the name to pass to --purge-blocklist.
Managing decisions
Viewing status
Several read-only commands inspect the engine without changing anything:
canasta crowdsec status # registered bouncers and the IP decisions in effect
canasta crowdsec scenarios # the detection (collections and scenarios) the engine has loaded
canasta crowdsec alerts # attacks CrowdSec has detected (history); add --ip to filter to one address
canasta crowdsec metrics # engine throughput β a zero parsed-lines count means the Caddy log isn't being read
In status, the Caddy bouncer reads active while it is pulling decisions. It reads stale, with how long ago it last pulled, once its last pull is more than two minutes old, and not pulling when it has never pulled; in either case Caddy is running without the bouncer, or is down.
On Kubernetes, status also has an Agent line for the agent in the Caddy pod: reporting, with its last heartbeat; starting, for three minutes after the Caddy or local API pod restarts; or a warning that nothing is parsing the Caddy access log, with what to do about it. metrics shows the agent's metrics (log lines read and parsed, scenarios triggered) and the local API's (bouncer and decisions) separately, and scenarios lists what the agent has loaded.
Banning and unbanning manually
Add a manual decision independent of automatic detection. A ban defaults to four hours; --duration and --reason are optional:
canasta crowdsec ban 203.0.113.50
canasta crowdsec ban 203.0.113.50 --duration 24h --reason scraper
Remove decisions for an IP (lifting a manual ban or one added by automatic detection):
canasta crowdsec unban 203.0.113.50
- Note: the bouncer pulls decisions on a short interval, so a newly added or removed ban takes effect within roughly 15 seconds rather than instantly.
Whitelisting trusted IPs
To stop CrowdSec from ever acting on traffic you trust (office IPs, monitoring, an upstream proxy), edit config/crowdsec/whitelists.yaml in the instance directory. It is a normal, version-controlled file β the same kind of durable, editable config as Caddyfile.global. Run canasta crowdsec reload to apply them (it restarts just the CrowdSec engine, not the whole instance). On Kubernetes, reload first deploys the instance's current config files β the edited whitelists.yaml and any other pending config changes. The whitelist is applied by the agent in the Caddy pod, so a changed file rolls the Caddy pod, which keeps serving throughout. reload then restarts only the local API; Caddy keeps enforcing its cached decisions meanwhile.
Recovering the real client IP behind a CDN or WAF
CrowdSec can only ban the right address if Caddy reports the real client IP. With no CDN or WAF in front, this is automatic: on Docker Compose, Caddy trusts the X-Forwarded-For header only from the private network the HAProxy edge connects from. When it sits behind a CDN or WAF, set TRUSTED_PROXIES so Caddy reads the forwarded client IP β but only from the proxy you actually use:
canasta config set TRUSTED_PROXIES=cloudflare
| Value | Behavior |
|---|---|
cloudflare |
Reads CF-Connecting-IP and trusts it only from Cloudflare's published edge ranges (refreshed in-process; no maintenance required).
|
imperva |
Reads Incap-Client-IP and trusts it only from Imperva's published ranges.
|
| comma-separated CIDRs | Trusts X-Forwarded-For from an explicit list of proxy networks (e.g. 10.0.0.0/8,192.168.0.0/16); runs on stock Caddy.
|
On Docker Compose, the HAProxy edge applies this setting: it recovers the client address from the CDN's header (or the proxy's X-Forwarded-For), on connections from the listed ranges only, and passes it on to Caddy in X-Forwarded-For. Its rate limits and deny lists then apply to the visitor too. See Behind a CDN.
Locking trust to the provider's ranges is the safety property: if Caddy trusted the forwarded header from any source, a client connecting directly to the origin could forge its IP and dodge bans. A typo in the value is rejected when you run canasta config set.
On Kubernetes
On Kubernetes, Caddy always sits behind the in-cluster ingress (k3s's Traefik) rather than at the network edge, so recovering the real client IP works a little differently β and Canasta handles it for you:
- Inside the cluster. The cluster's pod-network CIDRs are trusted automatically via
CADDY_TRUSTED_PROXY_CIDRS(default10.0.0.0/8,172.16.0.0/12,192.168.0.0/16), so Caddy reads the client IP that the ingress forwards rather than the ingress's own address. - At the ingress.
canasta install k8s-cpconfigures k3s's Traefik as a DaemonSet withexternalTrafficPolicy: Local. Without this, Kubernetes replaces the source address with an internal node IP before the request reaches Caddy, and CrowdSec β seeing only private addresses β would treat all traffic as trusted and protect nothing. It is applied automatically on single- and multi-node clusters alike, so the real client IP reaches Caddy no matter which node terminates the connection. - Behind a CDN or WAF. Set
TRUSTED_PROXIESexactly as above (e.g.cloudflare). On Kubernetes its trusted ranges are combined with the cluster pod CIDRs, so Caddy trusts both the CDN edge and the in-cluster ingress.
Disabling CrowdSec
canasta config set CANASTA_ENABLE_CROWDSEC=false
This stops the CrowdSec engine, removes the bouncer directive from the Caddyfile, drops crowdsec from the active profile (on Kubernetes, removes the local API Deployment and the agent sidecar), and (if no other plugin feature needs it) returns Caddy to the stock image.
What happens to CrowdSec's data depends on the orchestrator:
- On Docker Compose, disabling is a reversible toggle that preserves all CrowdSec data. The engine database (registered bouncers, local decisions, the cached community blocklist) and its configuration and credentials (Central API registration, Console enrollment) live in Docker named volumes that survive disable/enable and ordinary restarts. Re-enable with
canasta config set CANASTA_ENABLE_CROWDSEC=trueand the engine resumes with its previous state: the existing bouncer key is reused, and it stays registered and enrolled. Only the explicit cleanup actions below clear that data. - On Kubernetes, disabling deletes the
canasta-<id>-crowdsecPersistentVolumeClaim along with the rest of CrowdSec's resources (on a gitops-managed instance, Argo CD prunes it at the sync). The bouncer registration, local decisions, cached community blocklist, Central API registration and Console enrollment are lost. Re-enabling starts a fresh engine: the CLI registers it with the Central API and enrolls the bouncer again with a new key, and the community blocklist re-pulls from zero. Re-enroll the engine in the Console if you were using the full list.
Cleaning up and resetting
Because disabling keeps CrowdSec's data on Docker Compose, clearing anything there is a separate, deliberate action. Match it to the scope β most needs are narrow, and only the last wipes the engine:
| You want to⦠| Use |
|---|---|
Reset a bouncer showing stale duplicate registrations in status (auto-created when the Caddy container reconnected from new IPs across restarts) |
canasta crowdsec bouncer-enroll --force β resets to a single registration, no other data lost (see Bouncer enrollment)
|
| Stop blocking IPs from a console blocklist you unsubscribed from | canasta crowdsec reload --purge-blocklist <name> / --purge-blocklists (see Blocklists)
|
| Remove the whole instance, CrowdSec and all | canasta delete β permanently deletes the instance, including all its volumes
|
| Reset just the CrowdSec engine to a blank slate while keeping the wiki (rare β e.g. clearing test state before go-live) | the manual procedure below |
Resetting the CrowdSec engine to a blank slate
This wipes the CrowdSec engine's database and configuration β the cached community blocklist, all local decisions, the bouncer registration, the Central API registration, and any Console enrollment β without touching the wiki. Do this only to start CrowdSec over from scratch: the community blocklist then re-pulls from zero over the next pull cycles, and you must re-enroll the full list in the Console if you were using it.
On Docker Compose, run these from the instance directory, and remove only the two CrowdSec volumes β never the wiki's database or content volumes.
# 1. Disable CrowdSec (reverts Caddy to stock; the wiki keeps running)
canasta config set CANASTA_ENABLE_CROWDSEC=false
# 2. Remove the engine container if it is still present
docker compose rm -sf crowdsec
# 3. Identify, then remove ONLY the CrowdSec volumes (substitute your instance's project name)
docker volume ls | grep crowdsec # expect <instance>_crowdsec-db and <instance>_crowdsec-config
docker volume rm <instance>_crowdsec-db <instance>_crowdsec-config
# 4. Clear the stale bouncer key so re-enable enrolls a fresh one
canasta config unset CROWDSEC_BOUNCER_API_KEY --no-restart
# 5. Re-enable: fresh volumes, scenarios reinstalled, Central API re-registered, bouncer re-enrolled
canasta config set CANASTA_ENABLE_CROWDSEC=true
- Why step 4 matters: the bouncer API key is stored in
.env, not in the volumes. If you wipe the engine database but leave the old key in place, the fresh engine won't recognize it and the bouncer enforces nothing. Unsetting it lets the automatic enrollment issue a new key on re-enable.
- Safety:
docker volume rmrefuses to remove a volume that is still in use, so if a container still holds it the command fails without changing anything β re-run step 2 and retry. The wiki's own data lives in separate volumes (database, uploaded images, Caddy data) and is left untouched.
The config/crowdsec/whitelists.yaml and config/crowdsec/acquis.yaml files live in the instance directory (not in the wiped volumes), so any customizations survive the reset.
On Kubernetes, the engine's state lives in the canasta-<id>-crowdsec PersistentVolumeClaim instead of Docker volumes, and disabling CrowdSec deletes it (see Disabling CrowdSec). To reset the engine to a blank slate, disable and re-enable it β the local API comes back with a fresh database, reinstalls the scenarios, re-registers with the Central API, and re-enrolls the bouncer:
canasta config set CANASTA_ENABLE_CROWDSEC=false
canasta config set CANASTA_ENABLE_CROWDSEC=true
- On a gitops-managed instance, both changes are staged: push and sync after disabling, then re-enable as described under Existing instances.
The config/crowdsec/whitelists.yaml and config/crowdsec/acquis.yaml customizations survive on Kubernetes too β they are stored with the instance config and re-applied on the next deploy.
Resource requirements
CrowdSec adds one lightweight engine (a separate container on Compose; on Kubernetes, a local API pod and an agent sidecar in the Caddy pod), plus the bouncer that runs inside Caddy itself. It is far less resource-hungry than the Java-based observability or Elasticsearch stacks. See System requirements for overall sizing.
GitOps
On a GitOps-managed instance, the CrowdSec enable flag, the bouncer key, the agent password (Kubernetes), and TRUSTED_PROXIES are stored as part of the instance's version-controlled configuration, so enabling CrowdSec survives canasta reconcile or a canasta gitops pull. The config/crowdsec/whitelists.yaml file is version-controlled with the rest of the instance config. On a gitops-managed Kubernetes instance, enabling CrowdSec takes a push, a sync and a manual bouncer enrollment; see Existing instances.
See also
- Help:Networking and TLS β how Caddy fronts the instance
- Help:Observability β searchable logs, including the Caddy access log CrowdSec reads
- Help:Troubleshooting β common errors and fixes