Skip to main content
Security Engine version:
Version: Next

Bot detection configuration

This page covers the configuration of the bot detection feature: the signing keys and their rotation, cookie lifetime, and JavaScript bundle obfuscation.

Where to set these values

Bot-detection settings live under a top-level challenge: block inside an appsec-config YAML file — the same kind of file documented in AppSec configuration syntax. Multiple appsec-configs loaded by your AppSec acquisition combine field by field, so you can keep the upstream collection's appsec-config unchanged and ship a small overlay of your own that only sets what you care about. The mechanics of loading and merging appsec-configs are covered in AppSec configuration syntax.

A minimal overlay looks like this — every field below is optional, see the rest of this page for what each one does:

YAML
# /etc/crowdsec/appsec-configs/mycorp-overlay.yaml
name: mycorp/appsec-bot-challenge-overlay

challenge:
master_secret: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
crypto_obfuscation_pool_size: 3

Reload CrowdSec for changes to take effect:

SH
sudo systemctl reload crowdsec

Key management

The challenge mechanism is built on a long-lived master secret, from which the AppSec component derives two independent key families: one rotated on a schedule that signs challenge tickets and validates proof-of-work, and one (static for the lifetime of the master secret) that seals the success cookie.

KeyDefaultOperational meaning
master_secretrandom, ephemeral (single-instance only)Long-lived secret used to derive every other key. Hex (≥64 chars, preferred) or passphrase (≥32 bytes of UTF-8). Required when running more than one AppSec instance — all instances must share the same value or cookies issued by one are rejected by another.
key_rotation_interval5mHow often the per-epoch signing key advances. All instances in a distributed setup must agree on this value to derive identical per-epoch keys. Minimum 30s.
max_live_epochs3How many past epochs (in addition to the current one) the AppSec component still accepts. Bump this if a meaningful share of your clients need more than (max_live_epochs + 1) × key_rotation_interval to solve and submit the challenge (slow mobile networks, long round-trips).
cookie_ttl12hHow long a successful-challenge cookie stays valid. Decoupled from key rotation — the cookie carries its own not_after timestamp sealed under the master cookie key, so rotating the per-epoch sign key does not invalidate already-issued cookies.

Single-instance deployments

Leaving master_secret unset is fine: the AppSec component generates a 32-byte random secret at startup and logs a warning. Every restart invalidates all outstanding challenge cookies, which is acceptable for a single host.

Multi-instance / HA deployments

Set master_secret and key_rotation_interval to the same value on every AppSec instance. If the values differ, a cookie issued by instance A will be rejected by instance B and clients will be re-challenged on every request that lands on a different node — a noticeable user-experience regression and a load amplifier.

To rotate the master secret safely:

  1. Generate a new secret.
  2. Roll it out to every AppSec instance within one cookie_ttl window.
  3. Restart each instance after it has the new value.

During the rollout, clients holding cookies sealed under the old secret will be re-challenged once on instances that already have the new secret — there is no way to keep both valid simultaneously.

Generating a secret

The recommended form is a 32-byte hex string:

SH
openssl rand -hex 32
# e.g. 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef

Passphrases are accepted too, but must be at least 32 bytes of UTF-8. An invalid value (too short, non-hex characters in a hex-looking string) causes CrowdSec to fail loading the config.

JS obfuscation

During a challenge the AppSec component serves the client three pieces of JavaScript, and only one of them is worth tuning:

  • The fingerprinting library — the open-source fpscanner by Antoine Vastel, which collects the device signals. It is served unobfuscated, as public and cacheable code on its own <script> tag. This is deliberate: fpscanner is not secret, there is nothing to hide in it, and there is no knob to obfuscate it.
  • The crypto/glue bundle — the small block that runs the proof-of-work, XORs the fingerprint, and posts the submission.
  • The per-epoch signing-key module — the only sensitive artefact, because it carries the live signing key. It is re-obfuscated at runtime, and you can control how many distinct variants are kept per epoch.

Code running inside an attacker-controlled browser can always be reverse-engineered eventually; obfuscation buys time and cost, not invisibility.

KeyDefaultWhat it controls
crypto_obfuscation_pool_size1Number of distinct obfuscations of the per-epoch sign-key module kept per live epoch. Each variant costs ~5 s of CPU per rotation.

Advanced tuning

These challenge: fields are safety backstops sized for correctness and DoS resistance. Leave them at their defaults unless you have a specific, measured reason to change them.

KeyDefaultWhat it controls
max_cookie_size4096Upper bound, in bytes, on the sealed challenge cookie — enforced on both seal and open. It caps the memory allocated from the attacker-supplied fingerprint envelope, closing an over-allocation DoS. 4096 is the per-cookie size browsers guarantee; raise it only if a non-browser client you control tolerates larger cookies.
spent_set_max_entries1000000Maximum number of entries in the replay-protection set that stops a solved challenge from being submitted twice. A deep DoS backstop — steady-state usage stays far below it.
log_levelinherits globalLog verbosity for the challenge runtime alone, independent of the global CrowdSec log level (debug, info, warning, error). Lets you turn up challenge diagnostics without making the rest of CrowdSec noisy.

DNS cache

Identity-verified bots (see Known bots it lets through) are confirmed with a forward-confirmed reverse-DNS lookup, and results are kept in cache. DNS cache configuration is global engine configuration under crowdsec_service. See dns_cache in the CrowdSec configuration reference to tune its TTL or size.

Applying changes

Most fields take effect on the next CrowdSec reload:

SH
sudo systemctl reload crowdsec

Specifically:

  • Changing master_secret invalidates all in-flight challenges (clients mid-challenge will be re-challenged) and invalidates every already-issued cookie. Plan a rotation as described in Multi-instance / HA deployments.
  • Changing key_rotation_interval or max_live_epochs invalidates in-flight challenges but does not invalidate already-issued cookies — they remain valid until their own not_after timestamp.
  • Changing cookie_ttl affects only cookies issued after the reload; cookies already in the wild keep their original lifetime.
  • Changing crypto_obfuscation_pool_size takes effect on the next key-rotation tick.

Verification

Check that the AppSec component picked up the config:

SH
sudo cscli metrics show appsec

Hit a protected endpoint with a clean client and confirm the challenge HTML is served. Tail the CrowdSec log:

SH
sudo tail -F /var/log/crowdsec.log | grep -E "challenge submission|on_challenge_submit"

You should see lines like:

TEXT
level=info msg="challenge submission accepted" source=198.51.100.42 fsid=FS1_xyz is_bot=false allowlisted=false
level=info msg="on_challenge_submit rejected" source=203.0.113.7 reason=fast-bot-detection signals="[cdp]"

If you don't see any challenge submission lines at all after a reload, double-check that:

  • The new appsec-config is listed in your AppSec datasource (appsec_configs:) — see Where to set these values.
  • The bouncer is forwarding /crowdsec-internal/challenge/* paths unchanged — see Prerequisites on the intro page.
CrowdSec Docs
We use cookies

This site uses cookies to help us improve your experience. You can accept or decline below.