Varnish
Bot protection for Varnish Cache 6.0 LTS using a VCL config and a small helper VMOD.
Overview
Centinel integrates with Varnish through VCL. Protected requests are routed to the Centinel validator as a normal Varnish backend fetch, so Varnish's backend connection pool keeps the connection warm. The decision comes back in x-centinel-* response headers, so VCL never parses JSON. A small helper module (libvmod_centinel) base64-decodes the block or challenge page for synth delivery and appends the validator's Set-Cookie without overwriting cookies from your origin. Requests are blocked, challenged, or restarted to your origin based on the decision.
If the validator is unreachable, times out, or returns an error, requests fail open to your origin.
Backend TLS required
Open-source Varnish has no native backend TLS, and the validator is HTTPS. You must run a small local TLS proxy in front of validator.centinelanalytica.com and point the validator backend at it over plain HTTP on localhost. Reuse an nginx/HAProxy you already run, or add a ghostunnel sidecar — the install steps show both. Varnish Enterprise users can use a native TLS backend and skip the proxy. (hitch can't be used here — it only terminates inbound TLS, not the outbound TLS this leg needs.)
Prerequisites
- Secret key from your dashboard
- Varnish Cache 6.0 LTS (open source) or Varnish Enterprise 6, plus the matching
varnish-devheaders - Build tools:
autoconf,automake,libtool,pkg-config,python3-docutils - A local TLS proxy for the HTTPS validator (an existing nginx/HAProxy, or a ghostunnel sidecar)
- A configured origin backend
Install
Download centinel-varnish.zip and extract it:
curl -O https://docs.centinelanalytica.com/downloads/centinel-varnish.zip
unzip centinel-varnish.zip -d centinel-varnish/
cd centinel-varnish/The zip contains:
centinel.vcl— the VCL config templatesrc/— thelibvmod_centinelVMOD source (base64 decoding for block pages)autogen.sh,configure.ac,Makefile.am— autotools build files
Install the build dependencies (Debian/Ubuntu example), then build:
sudo apt-get install -y varnish varnish-dev \
build-essential automake autoconf libtool pkg-config python3-docutils
sh autogen.sh
./configure --disable-dependency-tracking
make
sudo make installmake install puts libvmod_centinel.so into Varnish's vmod directory.
Why --disable-dependency-tracking
The module includes the vmodtool-generated header, which doesn't exist when configure's dependency bootstrap runs. The flag defers that to the build step.
Open-source Varnish can't speak TLS to a backend, so a local proxy accepts plain HTTP on 127.0.0.1:8443 and originates TLS (with SNI) to the validator. All three options keep the validator connection warm under load; pick by what's already on the host.
If the host already runs nginx (or HAProxy), add a TLS-originating proxy — no extra daemon, and the most resource-efficient option in our benchmark. The keepalive + proxy_ssl_session_reuse + Connection "" lines keep the upstream TLS connection warm. The proxy_ssl_verify + proxy_ssl_name lines are not optional: without them nginx sends the upstream group name as SNI and accepts any certificate, so an on-path attacker could impersonate the validator and capture your API key:
# nginx.conf
upstream centinel_validator { server validator.centinelanalytica.com:443; keepalive 16; }
server {
listen 127.0.0.1:8443;
location / {
proxy_pass https://centinel_validator;
proxy_ssl_server_name on; # SNI
proxy_ssl_name validator.centinelanalytica.com; # SNI defaults to the upstream name, not the host
proxy_ssl_verify on; # verify the validator's cert
proxy_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
proxy_ssl_verify_depth 3;
proxy_ssl_session_reuse on;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host validator.centinelanalytica.com;
}
}HAProxy equivalent: a backend with server centinel validator.centinelanalytica.com:443 ssl sni str(validator.centinelanalytica.com) verify required ca-file /etc/ssl/certs/ca-certificates.crt plus http-reuse always.
ghostunnel is a maintained single-binary TLS proxy (Prometheus metrics, cert hot-reload, graceful restart):
ghostunnel client \
--listen 127.0.0.1:8443 \
--target validator.centinelanalytica.com:443 \
--disable-authentication # present no client cert; the server cert is still verifiedThe connection stays warm as long as Varnish keeps its (pooled) front-side connection open.
stunnel still works (it just uses the most CPU of the options we measured):
# /etc/stunnel/centinel.conf
foreground = no
[centinel]
client = yes
accept = 127.0.0.1:8443
connect = validator.centinelanalytica.com:443
sni = validator.centinelanalytica.com
verifyChain = yes # sni alone does not verify the cert
checkHost = validator.centinelanalytica.com
CAfile = /etc/ssl/certs/ca-certificates.crtstunnel /etc/stunnel/centinel.confEdit the bundled centinel.vcl:
- Point the
originbackend at your app. - Point the
validatorbackend at the TLS proxy (127.0.0.1:8443). - Set your secret key in
vcl_backend_fetch(set bereq.http.X-API-Key = "...";).
backend origin { .host = "127.0.0.1"; .port = "8080"; }
backend validator { .host = "127.0.0.1"; .port = "8443"; .max_connections = 200; }varnishd -f /etc/varnish/centinel.vcl -a :80 -s malloc,256m -p workspace_client=256kOr on a running instance:
varnishadm vcl.load centinel /etc/varnish/centinel.vcl && varnishadm vcl.use centinelConfigure
How it works
On every request, the VCL:
- Bypasses static assets (configurable regex in
vcl_recv) and lets Varnish cache them normally. - Reads the decision from
x-centinel-*response headers.centinel.b64decode()decodes the block or challenge page. - Applies the decision:
allow/not_matched— restart to your originblock— 403 plus the block pageredirect— 200 plus the verification page
- Forwards the validator's
Set-Cookieand a whitelisted set of response headers (Content-Type,Cache-Control,X-Content-Type-Options,X-Frame-Options,Content-Security-Policy,Referrer-Policy).
Configuration reference
| Setting | Where | Default |
|---|---|---|
| Secret key | vcl_backend_fetch → X-API-Key | — (required) |
| Origin backend | backend origin | your app |
| Validator backend | backend validator | TLS proxy → validator |
| Protected paths | vcl_recv static-asset regex | static assets bypass |
| Block/challenge page | from the validator (x-centinel-response-html-b64) | served via synth |
Keep-alive & tuning
- Keep the validator connection warm. Varnish pools the localhost hop to the proxy automatically (
.max_connectionson thevalidatorbackend). The proxy must keep its upstream TLS connection warm so the handshake is paid once, not per request — nginx does this withkeepalive+proxy_ssl_session_reuse, HAProxy withhttp-reuse always, and the stunnel/ghostunnel tunnels stay warm as long as Varnish keeps its front-side connection pooled. - Low-traffic sites. Varnish closes idle pooled backends after
backend_idle_timeout(default 60s). Raise it (e.g.-p backend_idle_timeout=300) so a quiet period doesn't force a fresh TLS handshake on the next request. - Large block/challenge pages. The page is delivered in a response header (
x-centinel-response-html-b64), so a very large page can exceed Varnish's header limit. If your block page is big, raise-p http_resp_hdr_len(and-p workspace_clientfor the decoded page).
Changelog
- v1.0.1 — TLS-proxy and keep-alive guidance
- v1.0.0 — Initial release