Centinel AnalyticaCentinel Analytica
PlatformsCDN / Edge

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:

  1. A backend named centinel pointing at the validator (1s connect / 3s first-byte timeouts, so an outage adds at most a bounded delay before failing open)
  2. A Private Edge Dictionary named centinel_config (API key, debug flag, kill-switch)
  3. 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)
  • curl and jq (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_* components

Manual 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: 1000 ms
  • First byte timeout: 3000 ms
  • Between bytes timeout: 2000 ms

Step 3: Create the Edge Dictionary

In Edge Dictionaries → Create:

  • Name: centinel_config
  • Mark as Private
  • Add items:
    • secret_key<your-centinel-api-key>
    • debugfalse
    • enabledtrue
  • 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:

FileSnippet typeNamePriority
init.vclinitcentinel_init10
recv.vclrecvcentinel_recv10
pass.vclpasscentinel_pass10
miss.vclmisscentinel_miss10
fetch.vclfetchcentinel_fetch10
error.vclerrorcentinel_error10
deliver.vcldelivercentinel_deliver150

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

  1. Review the new backend, dictionary, and snippets.
  2. Click Activate on the new version.
  3. 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)

KeyRequiredDescription
secret_keyyesCentinel validator API key.
enablednoKill-switch. Set to "false" to bypass validation instantly, with no version activation or redeploy. Defaults to enabled.
debugno"true" or "false" (string). When "true", debug headers are echoed on client responses. Keep "false" in production.
cookie_attributesnoOverride 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-page

Expect: 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 debugtrue 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 enabled dictionary item to false. Takes effect within seconds; set back to true to resume.
  • Remove completely: ./attach.sh uninstall <SERVICE_ID>, or in the UI clone the active version, delete the seven centinel_* snippets, the centinel_config dictionary, and the centinel backend, then activate.

Changelog

  • v2.3.0 — Next-Gen WAF coexistence
  • v2.2.0 — Attach script and kill-switch
  • v2.1.0 — Dedicated X-Centinel-Client gate header
  • v2.0.0 — Header-mode validator contract
  • v1.0.0 — Initial release

On this page