Fastly VCL
Attach Centinel Analytica to your existing Fastly VCL service via the attach script, Fastly Web UI, or Terraform.
Overview
Centinel attaches to your existing Fastly VCL service. The integration adds exactly three things to your service and modifies nothing else. Your domains, origins, custom VCL, and other snippets stay untouched:
- A backend named
centinelpointing at the validator (1s connect / 3s first-byte timeouts, so an outage adds at most a bounded delay before failing open) - A Private Edge Dictionary named
centinel_config(API key, debug flag, kill-switch) - Seven VCL snippets named
centinel_*
On every request the snippets check the path filter, validate the request against the Centinel /validate API, and block, challenge, or forward to your origin based on the decision. Every validator failure mode fails open. Changes take 2-3 minutes to roll out across Fastly's edge.
Installation aborts up front if any of those names already exist on your service, and uninstalling removes only those names.
Prerequisites
- An existing Fastly VCL service with at least one domain and origin
- Centinel API key (for validator authentication)
- Fastly account with API token (Account → Personal API Tokens, full-service access)
curlandjq(attach script path only)
Install
Download centinel-fastly.zip and extract.
Attach script
The script clones your service's active version, adds the backend + dictionary + snippets, validates the VCL, and activates.
cd install
export FASTLY_API_KEY="your-fastly-api-token"
export CENTINEL_API_KEY="your-centinel-api-key"
./attach.sh install <SERVICE_ID>Your service ID is on the service overview page in the Fastly dashboard (or fastly service list).
Useful variations:
./attach.sh install <SERVICE_ID> --no-activate # review the new version in the UI before activating
./attach.sh install <SERVICE_ID> --debug # echo x-centinel-* headers (turn off in production)
./attach.sh status <SERVICE_ID> # list installed Centinel components
./attach.sh uninstall <SERVICE_ID> # remove only the centinel_* componentsManual installation via Fastly Web UI
Step 1: Clone the active version
Log into your Fastly dashboard, select your service, and click Clone version to create an editable draft (you can't edit active versions).
Step 2: Create the centinel backend
In Origins → Hosts, add a backend alongside your existing origin:
- Name:
centinel - Address:
validator.centinelanalytica.com - Port:
443 - Use SSL: yes
- SSL hostname:
validator.centinelanalytica.com - SSL SNI hostname:
validator.centinelanalytica.com - Override host:
validator.centinelanalytica.com - Connect timeout:
1000ms - First byte timeout:
3000ms - Between bytes timeout:
2000ms
Step 3: Create the Edge Dictionary
In Edge Dictionaries → Create:
- Name:
centinel_config - Mark as Private
- Add items:
secret_key→<your-centinel-api-key>debug→falseenabled→true
- Save.
Step 4: Upload the seven VCL snippets
In VCL Snippets → Create snippet, repeat for each file in the snippets/ directory from the downloaded zip. The priorities matter. Fastly runs lower numbers first, so these make Centinel run before your own recv/fetch snippets and after your own deliver snippets:
| File | Snippet type | Name | Priority |
|---|---|---|---|
init.vcl | init | centinel_init | 10 |
recv.vcl | recv | centinel_recv | 10 |
pass.vcl | pass | centinel_pass | 10 |
miss.vcl | miss | centinel_miss | 10 |
fetch.vcl | fetch | centinel_fetch | 10 |
error.vcl | error | centinel_error | 10 |
deliver.vcl | deliver | centinel_deliver | 150 |
Paste each file's contents verbatim. You don't need to substitute an API key in the VCL; the snippets read it from the dictionary at runtime.
Step 5: Activate
- Review the new backend, dictionary, and snippets.
- Click Activate on the new version.
- Wait 2-3 minutes for the new VCL to propagate globally.
Terraform (service managed by Terraform)
If your service is already managed by an existing fastly_service_vcl resource, paste these blocks into it (the Fastly provider is monolithic, so snippets must live inside the service resource):
resource "fastly_service_vcl" "your_service" {
# ... your existing domains, backends, settings ...
# --- Centinel bot protection ---
backend {
name = "centinel"
address = "validator.centinelanalytica.com"
port = 443
use_ssl = true
ssl_cert_hostname = "validator.centinelanalytica.com"
ssl_sni_hostname = "validator.centinelanalytica.com"
override_host = "validator.centinelanalytica.com"
connect_timeout = 1000
first_byte_timeout = 3000
between_bytes_timeout = 2000
max_conn = 200
}
dictionary {
name = "centinel_config"
write_only = true
}
dynamic "snippet" {
for_each = {
init = 10, recv = 10, pass = 10, miss = 10, fetch = 10, error = 10, deliver = 150
}
content {
type = snippet.key
name = "centinel_${snippet.key}"
content = file("${path.module}/centinel-snippets/${snippet.key}.vcl")
priority = snippet.value
}
}
# --- end Centinel ---
}Copy the seven .vcl files from the zip's snippets/ directory into centinel-snippets/ next to your Terraform config, and populate the dictionary items (secret_key, debug, enabled) via a fastly_service_dictionary_items resource or the Fastly API.
write_only dictionaries and Terraform
The Fastly provider cannot read back write_only = true dictionary items. Either set write_only = false while managing items in Terraform, or keep it true and populate items via the Fastly API after terraform apply.
Configure
Edge Dictionary items (centinel_config)
| Key | Required | Description |
|---|---|---|
secret_key | yes | Centinel validator API key. |
enabled | no | Kill-switch. Set to "false" to bypass validation instantly, with no version activation or redeploy. Defaults to enabled. |
debug | no | "true" or "false" (string). When "true", debug headers are echoed on client responses. Keep "false" in production. |
cookie_attributes | no | Override the _centinel Set-Cookie attributes. Default: Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=86400. |
Path filtering
Static assets (images, fonts, CSS, JS, media) are excluded by default. The exclusion regex lives in recv.vcl as a literal (a Fastly VCL constraint: regex patterns can't come from variables). To customize, edit the regex in recv.vcl and re-upload that one snippet.
Coexisting with your existing VCL
The integration is additive, but two interactions are inherent to pure-VCL bot protection. Audit your existing VCL for these before activating:
Snippet ordering. Fastly runs lower priority numbers first, and customer snippets default to 100. Centinel's recv/fetch snippets run at priority 10 (before yours); its deliver snippet runs at priority 150 (after your default-priority deliver snippets, so its cleanup and validator-header application win). Don't return() out of vcl_recv or vcl_fetch from a snippet whose priority is below 10 (it would run before centinel_recv and silently bypass protection). If you need a deliver snippet to run after Centinel's cleanup, give it priority above 150.
Restarts. Validation consumes exactly one restart per protected request, and origin-bound requests carry the marker header req.http.X-CN-Done == "1". If your VCL branches on req.restarts, adapt it:
# run-once logic — was: if (req.restarts == 0)
if (req.restarts == 0 || (req.http.X-CN-Done && req.restarts == 1)) { ... }
# restart-based origin failover — was: if (req.restarts > 0)
if (req.restarts > 1 || (req.restarts == 1 && !req.http.X-CN-Done)) { ... }Next-Gen WAF (NGWAF / Signal Sciences). If your service runs Fastly's edge WAF, Centinel's snippets (priority 10) run before NGWAF's (priority 9000) and mark the internal validator leg with x-sigsci-no-inspection: true, so the WAF skips the bodyless POST /validate hop. Without it, the WAF blocks the validator subrequest with HTTP 406 and logs its response as a spurious security event. The header is a no-op on services without NGWAF, so you don't need a manual allow-rule. Keep your own NGWAF snippets at their default priority (9000); a snippet below 10 runs before Centinel and breaks the bypass.
Reserved names. Backend centinel, dictionary centinel_config, snippets centinel_*, request headers X-CN-* / X-Centinel-* / x-sigsci-no-inspection (validator leg only), and synthetic codes 760/761 in vcl_error.
Requests that are automatically skipped (fail-open): static assets, FASTLYPURGE, WebSocket upgrades, ESI subrequests, background fetches, shield-node passes, and everything while the kill-switch is off.
Verify
curl -i https://www.example.com/some-pageExpect: your origin's normal response, plus Set-Cookie: _centinel=...; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=86400 and Server-Timing: centinel;dur=<ms>. That timing is the validator round-trip only (typically well under 100 ms), appended to any existing Server-Timing.
For temporary debugging, set debug → true in the dictionary; responses then include x-centinel-decision, x-centinel-request-id, and crawler info. Turn it back off in production.
Uninstall
- Pause instantly: set the
enableddictionary item tofalse. Takes effect within seconds; set back totrueto resume. - Remove completely:
./attach.sh uninstall <SERVICE_ID>, or in the UI clone the active version, delete the sevencentinel_*snippets, thecentinel_configdictionary, and thecentinelbackend, then activate.
Changelog
- v2.3.0 — Next-Gen WAF coexistence
- v2.2.0 — Attach script and kill-switch
- v2.1.0 — Dedicated
X-Centinel-Clientgate header - v2.0.0 — Header-mode validator contract
- v1.0.0 — Initial release