mod_botshield is an Apache 2.4 module that filters bot and scraper traffic in-process before Apache hands the request to PHP / FastCGI / upstream. This page covers installing the module, applying a minimal working configuration, and verifying that requests are being gated.
You need:
- Apache 2.4 with the development headers (
apache2-devon Debian/Ubuntu,httpd-develon RHEL-family). - OpenSSL development headers (
libssl-dev). - libcurl + json-c development headers (
libcurl4-openssl-dev,libjson-c-dev) — required even if you don't use the captcha tier. - A 64-bit Linux platform. The module is exercised on Linux x86-64; other Unix-like platforms with APR + Apache 2.4 likely work but are not part of the regression matrix.
Optional but recommended:
mod_watchdogfor periodic state-file snapshots and the headroom monitor. The module degrades gracefully without it (state save still runs at clean shutdown, the headroom log lines disappear).mod_statusfor the/server-statuscontribution hook.mod_remoteipif Apache sits behind a reverse proxy or load balancer. See deployment for why this is critical.mod_reqtimeoutfor slow-client / slowloris defense. See deployment for the recommended settings.
make enablemake enable is the one-shot path: it compiles, installs the .so
into Apache's modules directory, runs a2enmod botshield,
apachectl configtest, and systemctl reload apache2. If any step
fails the script stops before the reload.
Step-by-step equivalents if you'd rather drive each phase yourself:
make # apxs -c
sudo make install # cp .so to modules dir
sudo a2enmod botshield # writes mods-enabled symlink
sudo apachectl configtest # syntax check before reload
sudo systemctl reload apache2 # graceful reloadmake disable removes the module from mods-enabled without
deleting the .so — useful for quickly rolling back during
deployment.
mod_botshield refuses to start without BotShieldSecretFile. The
file holds the master HMAC key from which per-purpose subkeys are
HKDF-derived at config-load time (cookie GCM, pending-cookie HMAC,
embedded-bootstrap binding). It must be:
- mode 0600 (the file watcher rejects looser modes at startup).
- between 16 bytes and 1 KiB.
- a single value; no leading whitespace, no embedded NULs.
A 32-byte hex string is a reasonable default:
sudo install -m 600 /dev/null /etc/botshield/secret
sudo openssl rand -hex 32 | sudo tee /etc/botshield/secret >/dev/nullKeep this file off your repo and out of any backup that gets shared.
Rotation is supported via BotShieldSecondarySecretFile — see the
deployment page.
Drop a single block into the vhost you want gated:
<VirtualHost *:443>
ServerName example.test
DocumentRoot /var/www/example
# SSLEngine, certs, logs, etc.
BotShieldEnabled on
BotShieldSecretFile /etc/botshield/secret
BotShieldAlgorithm sha256-zeros
</VirtualHost>That's the floor. Everything else (tier thresholds, forgiveness, allow lists, captcha providers, triggers) ships with reasonable defaults documented on the site model and directives pages.
BotShieldAlgorithm sha256-zeros is currently the only built-in PoW
algorithm. Other names (sha384-zeros, pbkdf2-sha256, argon2id)
are reserved registry slots that fail with a clear "not implemented"
diagnostic if you set them — there's no silent fallback.
After reload, confirm the module is loaded and a real request is being processed.
1. Module loaded.
apachectl -M 2>&1 | grep botshield
# expected: botshield_module (shared)2. Module is decorating requests. Every gated request gets an
X-Botshield-Env: <env> response header (the env name comes from
the BotShieldEnv directive if you set one, or prod by default).
This is the cheapest end-to-end smoke test:
curl -skI https://example.test/ | grep -i x-botshield
# expected: X-Botshield-Env: prod3. Decisions are being logged. mod_botshield emits a structured
key=value decision line per request at Apache's info level.
The default LogLevel warn hides them. Bump just this module:
LogLevel botshield_module:infoReload and tail the error log:
sudo tail -f /var/log/apache2/error.log | grep "mod_botshield: decision"You should see one line per request:
[Tue Apr 28 10:00:00 2026] [botshield:info] mod_botshield:
decision tier=pass outcome=allow ip=192.0.2.1 score=0
cookie=absent provider=- alg=- reason="-" path="/"
tier=pass outcome=allow is the happy path — the module
inspected the request, decided it didn't warrant friction, and
declined so Apache served the real content.
4. The challenge widget renders. Force a challenge by spoofing a
scraper UA so the heuristic score crosses BotShieldScoreSilent
(default 20):
curl -sk -A "python-requests/2.31" https://example.test/ | grep -c BOTSHIELD
# expected: 1 (the widget marker is present in the rendered page)If you don't see the widget, the heuristic configuration on your scope likely diverges from the defaults — see site model for the tuning workflow and observability for how to inspect what each request scored.
Open the gated URL in a real browser. The interstitial should
auto-submit (silent tier) or render the checkbox widget (form tier),
solve a few seconds of SHA-256 PoW, and bounce back to the real page.
Reload — you should see the real content immediately. The browser
now holds a _bs_session cookie carrying the signed envelope and
accumulated reputation.
If you need to bypass the module without removing it from Apache's config (e.g. emergency rollback during a misfire):
sudo make disable && sudo systemctl reload apache2make disable flips a2dismod botshield so Apache no longer loads
the .so. Any directives in your vhost configs become unrecognized
on the next reload, so this is a "stop everything" switch — for
gradual rollback prefer BotShieldEnabled LogOnly (see
staging).
- Real-world deployments behind a proxy: deployment.
- Tier model, scoring, and tuning: site model.
- Allow lists, rate limits, and triggers: policy.
- Third-party captcha integration: captcha.
- Metrics, decision log, mod_status: observability.