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: _XX_APPSEC_CONFIG_OVERLAY_XX_

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

The AppSec component serves two JavaScript artefacts to the client during a challenge: a static library bundle (the fingerprinting + proof-of-work runner) and a dynamic per-epoch key module (which embeds the current signing key). Both are obfuscated, and you can tune how many distinct obfuscated variants are kept in memory and how often new ones are produced.

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

KeyDefaultRecommendedWhat it controls
crypto_obfuscation_pool_size13Number of distinct obfuscations of the per-epoch sign-key module kept per live epoch. Each variant costs ~5 s of CPU per rotation. A pool size of 3 is recommended in production: different clients see different obfuscations of the same key, which materially raises the cost of an attacker reverse-engineering the module. The default of 1 exists to keep tests cheap.
library_runtime_obfuscation_enabledfalsefalseWhen false, the AppSec component serves only the library bundle baked at build time (no runtime cost). When true, a background goroutine produces additional obfuscations of the static library bundle on a cadence. Enable only on hosts with CPU budget to spare — the build-time bundle is already obfuscated and is sufficient for most deployments.
library_obfuscation_pool_size11Maximum number of obfuscated library-bundle variants kept. Has no effect unless library_runtime_obfuscation_enabled is true — values >1 are clamped to 1 with a startup warning otherwise.
library_obfuscation_refresh_interval1h1hHow often the background obfuscator produces one new library-bundle variant. Each pass costs roughly one minute of CPU. Ignored when runtime obfuscation is disabled.
tip

Don't enable library_runtime_obfuscation_enabled on a small or shared host — the obfuscator is CPU-heavy and runs every library_obfuscation_refresh_interval. The build-time obfuscation is enough for most deployments; only turn this on if you specifically need rotating byte-level library variants in addition to the build-time bundle.

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 the JS obfuscation fields takes effect on the next rotation tick / refresh 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.