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 a sidecar in the Caddy pod). 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. When Caddy is the edge it already sees the real client; when it sits behind a CDN or WAF you must tell it which proxy to trust (see below), or both detection and blocking would act on the proxy's IP.
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 the engine runs as a crowdsec sidecar container inside the Caddy pod, sharing the Caddy access log, and its state lives in a per-instance PersistentVolumeClaim (canasta-<id>-crowdsec). 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.yamland rolling the Caddy pod so the engine 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.
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. 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.
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
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).
Recovering the real client IP behind a CDN or WAF
CrowdSec can only ban the right address if Caddy reports the real client IP. When Caddy is the edge this is automatic. When it sits behind a CDN or WAF, set CADDY_TRUSTED_PROXIES so Caddy reads the forwarded client IP β but only from the proxy you actually use:
canasta config set CADDY_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.
|
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
CADDY_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, and (if no other plugin feature needs it) returns Caddy to the stock image.
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=true and 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.
Cleaning up and resetting
Because disabling keeps CrowdSec's data, clearing anything 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. To reset it to a blank slate: disable CrowdSec, delete that PVC, clear the stale bouncer key, then re-enable β the sidecar 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
kubectl delete pvc canasta-<id>-crowdsec -n canasta-<id>
canasta config unset CROWDSEC_BOUNCER_API_KEY --no-restart
canasta config set CANASTA_ENABLE_CROWDSEC=true
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, a sidecar in the Caddy pod on Kubernetes) 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, and CADDY_TRUSTED_PROXIES are stored as part of the instance's version-controlled configuration, so enabling CrowdSec survives a config regeneration or a git pull. The config/crowdsec/whitelists.yaml file is version-controlled with the rest of the instance config.
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