Documentation
Directive reference
Canonical reference for all 78 directives, grouped by family.
Directive reference
On this page
- Scope and validity
- Core
- Tier thresholds and forgiveness
- Silent-tier dispatch
- Widget customization
- Captcha tier
- Captcha-verify endpoint hardening
- SHM sizing and persistence
- Allow list
- Rate limit + block-path
- Robots.txt enforcement
- Triggers
- Per-scope triggers
- Flag triggers
- Safeguard
- Load-aware throttling
- Multi-vhost reputation
- Log-only / staging mode
- App bridge
- Where to next
mod_botshield registers 78 directives at config time. This page is
the canonical reference, grouped by family. The
underlying source-of-truth is bs_cmds[] in src/botshield.c:139 —
when tuning behavior, treat the source as authoritative
when it disagrees with a doc page.
Scope and validity
Most directives accept RSRC_CONF | ACCESS_CONF — they're valid in
server config, <VirtualHost>, <Directory>, <Location>,
<Files>, and their *Match variants.
A few are server-scope only (RSRC_CONF); placing them inside
<VirtualHost> emits a NOTICE and the directive is ignored. The
SHM segment is module-global, so any directive that sizes the
segment or backs it with a state file lives at the main server
level. These directives carry an explicit "(server scope only)"
note in the table.
.htaccess is never valid for any BotShield directive — OR_ALL
is never used. Bot-protection config in writable filesystem
locations is a deliberate non-goal.
File-backed *File directives read their targets once at config-
parse time and cache the bytes on the per-directory config — no
per-request file I/O. Missing, unreadable, or oversized files fail
apachectl configtest, so a broken template can't be reloaded
into a running server.
Core
| Directive | Syntax | Default |
|---|---|---|
BotShieldEnabled |
on|off|logonly |
off |
BotShieldDebug |
on|off |
off |
BotShieldSecretFile |
/path |
unset (required) |
BotShieldSecondarySecretFile |
/path |
unset |
BotShieldAlgorithm |
<name> |
unset (required) |
BotShieldCookieTTL |
N (sec) |
3600 (range 1..86400) |
BotShieldCookieDomain |
".example.com" |
unset (host-only) |
BotShieldDifficulty |
N |
4 (range 1..16) |
BotShieldEndpointPrefix |
/path |
/botshield |
BotShieldEnabled is the master gate. It is tri-state:
on— enforce. Tier decisions serve interstitials, triggers / rate-limit / block-path rules act on matches.off— module declines every request in scope; same shape as unloading the module on this path.logonly— observe-only. The handler runs and emits decision logs, but every enforcement-suppression site short-circuits: tier dispatch logsoutcome=~challengeand declines instead of serving the interstitial; trigger / rate-limit / block-path matches log:observeand skip side effects. Use this to stage a whole policy revision before flipping enforcement on. See staging.
Because BotShieldEnabled is per-<Directory> / <Location>,
operators can carve out exceptions:
BotShieldEnabled LogOnly # vhost: observe
<Location "/about">
BotShieldEnabled On # /about: enforce
</Location>
BotShieldDebug returns 403 "Hello World" for every request in
scope — useful as a smoke test that the hook is firing.
BotShieldSecretFile and BotShieldAlgorithm are required. The
module emits 503 X-Botshield: misconfigured for any request
where both are not resolved on the scope. BotShieldSecondarySecretFile
is the verify-only secondary key for graceful rotation — see
deployment.
BotShieldAlgorithm only sha256-zeros is built-in today;
sha384-zeros, sha512-zeros, pbkdf2-sha256, argon2id are
reserved registry slots that fail with a clear "not implemented"
diagnostic.
BotShieldCookieDomain adds a Domain= attribute to Set-Cookie so
reputation follows across subdomains. Default is host-only. When
HTTPS is in use AND no domain is set, the module emits the
__Host-bs_session cookie name; otherwise the legacy
_bs_session. Verify path checks both.
BotShieldDifficulty is the leading-hex-zeros count for the PoW.
Higher = more client work. 4 is ~100ms on a modern phone; 6 is
~3s; 8 is ~50s — don't go above 6 without a reason.
BotShieldEndpointPrefix is the URL prefix for module-owned
handlers (/botshield/captcha-verify, /botshield/metrics,
/botshield/embedded.js, /botshield/safeguard-info etc.).
Change it if it collides with real app routes.
Tier thresholds and forgiveness
| Directive | Syntax | Default |
|---|---|---|
BotShieldScoreSilent |
N |
20 |
BotShieldScoreHard |
N |
50 |
BotShieldScoreCaptcha |
N |
80 |
BotShieldForgivenessSilent |
N |
10 |
BotShieldForgivenessForm |
N |
25 |
BotShieldForgivenessCaptcha |
N |
50 |
BotShieldForgivenessCapPerHour |
N |
200 (0 disables) |
Tier dispatch ladder:
score < BotShieldScoreSilent→ passBotShieldScoreSilent ≤ score < BotShieldScoreHard→ silentBotShieldScoreHard ≤ score < BotShieldScoreCaptcha→ formBotShieldScoreCaptcha ≤ score→ captcha (or form if no provider)
See site model for the full scoring discussion.
BotShieldForgivenessCapPerHour caps total cookie-side
forgiveness in any rolling 60-minute window. Default 200 ≈ 4–8
challenge-passes worth of credit. Lower for stricter farming
resistance; 0 disables (legacy behavior).
Silent-tier dispatch
| Directive | Syntax | Default |
|---|---|---|
BotShieldSilentMode |
interstitial|embedded |
interstitial |
interstitial (the default) serves a no-click splash page that
auto-submits a SHA-256 PoW on load — the legacy silent-tier
behavior. embedded instead hands off to the site-included
/botshield/embedded.js wrapper: the page serves DECLINED (real
content) and the wrapper does the PoW in a Web Worker, then POSTs
the result back to /botshield/embedded-verify to mint
_bs_session on the next request. Embedded mode trades a brief
window where the cookie isn't yet on the client (the very first
request goes through unverified) for a zero-interstitial UX.
Embedded mode requires you to include the wrapper script in your page templates; without it, the request still serves the real content but no cookie ever lands.
Widget customization
| Directive | Syntax | Default |
|---|---|---|
BotShieldPromptText |
"text" |
I'm not a robot |
BotShieldLogoFile |
/path.svg |
embedded Guardian |
BotShieldLogoLabel |
"text" |
botshield |
BotShieldShowLogo |
on|off |
on |
BotShieldShowLabel |
on|off |
on |
BotShieldShowBox |
on|off |
on |
BotShieldHelp |
off|on|button |
button |
BotShieldHelpFile |
/path.html |
built-in text |
BotShieldChallengeFile |
/path.html |
built-in shell |
BotShieldChallengeFile replaces the full HTML page that wraps the
widget; the file must contain <!-- BOTSHIELD --> where the widget
is spliced in. Other widget directives still apply to the widget
block itself. Max 256 KiB.
Logo and help files are 64 KiB max each. Logo content is served
inline as <img>-equivalent SVG; help content is rendered as
trusted HTML (no escaping — you own sanitization).
BotShieldShowLogo/Label/Box strip widget chrome down to a lone
checkbox if the surrounding page styles its own chrome. When label
is hidden it moves to the button's aria-label — accessibility is
preserved.
Captcha tier
| Directive | Syntax | Default |
|---|---|---|
BotShieldCaptchaProvider |
<name> |
unset |
BotShieldCaptchaSiteKey |
"key" |
unset |
BotShieldCaptchaSecretFile |
/path |
unset |
BotShieldCaptchaTimeout |
N (ms) |
1000 (100..5000) |
BotShieldCaptchaConnectTimeout |
N (ms) |
250 (50..5000) |
BotShieldRecaptchaV3MinScore |
0..1 |
0.5 |
BotShieldCaptchaExpectedHostname |
"name" or "" |
server hostname |
BotShieldCaptchaExpectedAction |
"action" or "" |
botshield |
BotShieldCaptchaCABundle |
/path |
libcurl default |
BotShieldFormCaptcha |
on|off |
off |
Provider names: turnstile, hcaptcha, recaptcha-v2,
recaptcha-v3, friendly, geetest. See
captcha for the wire-protocol details.
BotShieldCaptchaTimeout is the total siteverify HTTP budget; on
timeout the verify path fails open. BotShieldCaptchaConnectTimeout
is the connect phase only — tighter, raised on links with
transient packet loss.
BotShieldRecaptchaV3MinScore only matters for recaptcha-v3.
Reject verifications below this score even on success: true.
BotShieldCaptchaExpectedHostname / Action: empty string
disables the check; unset uses defaults (server_hostname /
"botshield"). GeeTest binds host/action via HMAC and doesn't
return them in the response, so these are no-ops for that
provider.
BotShieldFormCaptcha intercepts POSTs and validates the
captcha token inline rather than via interstitial. Requires a
captcha provider configured on the same scope.
Captcha-verify endpoint hardening
| Directive | Syntax | Default | Scope |
|---|---|---|---|
BotShieldCaptchaRateLimit |
N (per IP per minute) |
30 (0..1000) |
server / vhost |
BotShieldCaptchaMaxInFlight |
N (global concurrent) |
64 (1..1024) |
server only |
Both reject before any libcurl call. RateLimit returns 429 with
Retry-After; MaxInFlight returns 503. 0 on RateLimit disables.
SHM sizing and persistence
All directives in this section are server scope only. Inside
<VirtualHost> they emit a NOTICE and are ignored.
| Directive | Syntax | Default |
|---|---|---|
BotShieldShmSize |
<size> (128K..256M) |
16M |
BotShieldFlaggedIPCapacity |
N |
50000 (1024..1000000) |
BotShieldIPv6PrefixLen |
N (0..128) |
64 |
BotShieldBloomIPs |
N |
1000000 (1000..10000000) |
BotShieldBloomWindow |
N (sec) |
604800 (3600..2592000) |
BotShieldStateFile |
/path |
unset (persistence off) |
BotShieldStateSaveInterval |
N (sec) |
300 (0=shutdown-only) |
BotShieldRateLimitEscalateCapacity |
N |
50000 |
BotShieldSafeguardCapacity |
N |
50000 |
BotShieldEmbeddedNonceCapacity |
N |
32768 (1024..1048576) |
BotShieldShmSize is the total budget for the flagged-IP / strike
/ safeguard tables and the Bloom buffers.
BotShieldFlaggedIPCapacity etc. size individual tables within
that budget — the headroom watchdog will flag if the segment is
underprovisioned.
BotShieldIPv6PrefixLen masks IPv6 client IPs before keying the
SHM tables. Default /64 is per-subscriber for typical ISP
allocations; tighter values (/56, /48) flag larger blocks of
addresses sharing reputation.
BotShieldStateFile enables crash-durable persistence; the
periodic save requires mod_watchdog, the graceful-shutdown save
runs regardless. State format mismatches on load reject the file
with a NOTICE and start fresh — never a startup failure.
Allow list
| Directive | Syntax | Default | Scope |
|---|---|---|---|
BotShieldAllow |
on|off |
off |
any |
BotShieldAllowBot |
<name> <ua-pattern> [<target>] |
builtin only | server / vhost |
BotShieldAllow is the master gate for the allow-list family.
BotShieldAllowBot registers a UA pattern + IP-range pair. The
third arg is a path to a CIDR file, comma-separated inline CIDRs
(prefixed with *,), or * alone for UA-only matching. See
policy for the
verified / fake / unverified outcomes.
Rate limit + block-path
| Directive | Syntax | Default |
|---|---|---|
BotShieldRateLimit |
<name> <budget> <per> <ua-pattern> <ipspec> [mode=observe] |
none |
BotShieldRateLimitEscalate |
<rate-rule> <strikes> <per> [status=N] [ttl=N] |
none |
BotShieldBlockPath |
<name> <path-glob> <ua-pattern> <ipspec> [mode=observe] |
none |
Cohort: <ua-pattern> is a substring or ""/* for any UA;
<ipspec> is a path to a CIDR file, comma-separated inline CIDRs,
or * for any IP. Both axes can't be wildcard — that's rejected
at config time. <per> accepts sec/min/hour (or s/m/
h); plain integers are rejected.
BotShieldRateLimitEscalate upgrades repeated 429s on the same
client to a stickier status code — see
policy.
Robots.txt enforcement
| Directive | Syntax | Default |
|---|---|---|
BotShieldRobotsTxt |
/path |
unset |
BotShieldRobotsRefreshInterval |
N (sec) |
60 (0=disabled) |
BotShieldRobotsWildcardScope |
heuristic|strict|off |
heuristic |
See policy for the matcher semantics and refresh model.
Triggers
| Directive | Predicate args | Action keys |
|---|---|---|
BotShieldPathTrigger |
<name> <path-glob> |
status=, redirect=, log=, flag=, ttl=, penalty=, mode= (no credit=) |
BotShieldCookieTrigger |
<name> <pred> (see policy page) |
status=, redirect=, log=, flag=, ttl=, penalty=, credit=, mode= |
BotShieldEnvTrigger |
<name> <env-pred> (see policy page) |
status=, log=, flag=, ttl=, penalty=, credit=, mode= (no redirect=) |
BotShieldFeedbackTrigger |
<event> |
flag=, ttl=, log=, mode= |
BotShieldLoadTrigger |
<name> state=<n>|state>=<n> |
status=, log=, penalty=, mode= (no redirect=, flag=, ttl=) |
BotShieldSessionCookieName |
<name> (single arg, repeatable) |
n/a (feeds cookies=session predicate) |
See policy for full predicate vocabulary and family-by-family semantics.
BotShieldSessionCookieName registers a name that the
cookies=session cookie-trigger predicate considers a session
cookie. Repeatable; each call appends.
Per-scope triggers
| Directive | Syntax | Scope |
|---|---|---|
BotShieldTrigger |
[reset] [status=N|pass] [redirect=URL] [log=tag] [flag=NAME] [ttl=N] [penalty=N] [credit=N] [mode=enforce|observe] |
server / vhost / Directory / Location / LocationMatch / Files / If |
The Apache scope the directive lives in IS the predicate; no path
glob argument. Multiple BotShieldTrigger lines in one scope
each append a separate action; they all fire on a pass, the first
non-pass status short-circuits. reset (no other args) drops
inherited triggers from outer scopes and clears earlier same-scope
entries.
This is the directive that replaces the legacy BotShieldFlagIP
— the equivalent today is BotShieldTrigger flag=<name> ttl=<sec>.
Flag triggers
| Directive | Syntax |
|---|---|
BotShieldFlagTrigger |
<flag> [reset] [action=<verb> args...] [mode=observe] |
Action verbs: action=score add=N (signed N -1000..1000),
action=tier_floor min=<tier> (raise effective tier; tier is one
of pass/silent/form/captcha). The reset keyword clears
all earlier triggers (compiled-in defaults + prior
declarations) for the named flag at post-config time.
Flag bits: honeypot_hit, scanner_probe, fake_bot,
pow_fail_streak, app_verified_human, app_verified_session,
app_trust_signal. See
policy for compiled-in
defaults and override semantics.
Safeguard
| Directive | Syntax | Default | Scope |
|---|---|---|---|
BotShieldSafeguard |
on|off |
on |
server / vhost |
BotShieldSafeguardThreshold |
N |
5 |
server / vhost |
BotShieldSafeguardWindow |
N (sec) |
600 |
server / vhost |
BotShieldSafeguardTTL |
N (sec) |
900 |
server / vhost |
BotShieldSafeguardRedirectURL |
<url> |
unset (uses built-in explainer) | server / vhost |
Challenge-loop suppression. When a client has been issued the threshold number of challenges within the window without ever returning a verified cookie, the next request gets a 302 redirect to break the loop.
BotShieldSafeguardRedirectURL lets the operator point the
redirect at their own page (a status page, a help article, a
login flow). When unset, the module redirects to its built-in
explainer at <BotShieldEndpointPrefix>/safeguard-info. The
original URI is appended as ?return=<urlencoded path> regardless
of which target is chosen, so the user can resume their journey
once the underlying problem is fixed. The return parameter is
validated for same-origin shape (must start with a single /,
no scheme, no double-slash) to prevent open-redirect abuse.
The built-in explainer page describes common reasons the
auto-check failed (JavaScript disabled, privacy extension,
browser version) and offers a Continue link back to the original
URL. It is auto-routed by the module — no <Location> carve-out
needed.
See policy and site model for the behavior arc.
Load-aware throttling
Sampling and hysteresis:
| Directive | Syntax | Default | Scope |
|---|---|---|---|
BotShieldLoadStateFile |
/path |
unset | server only |
BotShieldLoadRefreshInterval |
N (sec) |
1 |
server only |
BotShieldLoadWarmThreshold |
N (% workers busy) |
65 |
server only |
BotShieldLoadHotThreshold |
N (% workers busy) |
85 |
server only |
BotShieldLoadStateFile points at an external single-word state
file (managed by an out-of-band collector) that overrides the
scoreboard sample. Useful when load decisions should key on a
metric Apache itself doesn't see (queue depth, downstream
saturation, etc.).
The trigger family that consumes the state lives under
BotShieldLoadTrigger (above). See
policy.
Multi-vhost reputation
| Directive | Syntax | Default |
|---|---|---|
BotShieldShareScope |
<token> |
derived from ServerName |
Vhosts with the same token share one reputation namespace. See deployment.
Log-only / staging mode
Folded into BotShieldEnabled (tri-state on / off /
logonly). See the Core section above and the
staging guide.
App bridge
| Directive | Syntax | Default |
|---|---|---|
BotShieldAppFeedback |
on|off |
off |
BotShieldAppFeedbackHeader |
<header-name> |
X-BotShield-Feedback |
BotShieldAppClaims |
on|off |
off |
BotShieldAppIntegrationSecretFile |
/path |
unset (required for either above) |
See captcha for the wire format and security model.
Where to next
- Conceptual model and scoring: site model.
- Deployment topology and capacity: deployment.
- Per-family policy semantics: policy.
- Captcha and app-bridge integration: captcha.
- Decision log and metrics: observability.