Fastly Setup
This guide covers setting up your Fastly account and Compute service for Trusted Server.
Create a Fastly Account
- Go to manage.fastly.com and create an account if you don't have one
Create an API Token
- Log in to the Fastly control panel
- Go to Account > API tokens > Personal tokens
- Click Create token
- Configure the token:
- Name the token (e.g., "Trusted Server Deploy")
- Choose User Token
- Choose Global API Access
- Choose what makes sense for your organization in terms of Service Access
- Click Create Token
- Copy the key to a secure location - you will not be able to see it again
Create a Compute Service
- Click Compute in the navigation
- Click Create Service
- Click Create Empty Service (below the main options)
- Configure the service:
- Add your domain of the website you'll be testing or using
- Click Update
Configure Origins
Origins are the backend servers that Trusted Server will communicate with (ad servers, SSPs, etc.).
- In your Compute service, click on the Origins section
- For each backend you need to add:
- Enter the FQDN or IP address
- Click Add
- Enter a Name in the first field - this name will be referenced in your code (e.g.,
my_ad_integration_1) - Configure port numbers and TLS settings as needed
TIP
After saving origin information, you can select port numbers and toggle TLS on/off.
Configure Fastly CLI Profile
After installing the Fastly CLI, create a profile with your API token:
fastly profile createFollow the interactive prompts to paste your API token.
Domain Configuration
TIP
With a dev account, Fastly gives you a test domain by default (e.g., xxx.edgecompute.app). You can use this for testing before configuring your own domain.
Using Your Own Domain
When you're ready to use your own domain:
- In the Fastly control panel, add your domain to the service
- Create a CNAME record at your DNS provider pointing to your Fastly domain
- Fastly provides 2 free TLS certificates (non-wildcard) per account
TLS Requirements
- Fastly Compute only accepts client traffic via TLS (HTTPS)
- Origins and backends can be non-TLS if needed
CDN-fronted Client IP
When another CDN or Fastly service fronts Trusted Server, the Fastly Compute client address identifies the immediate edge node rather than the original reader. Geolocation, EC identity derivation, and consent jurisdiction all read that single value, so all three describe the fronting POP instead of the reader. Nothing errors: pages render and ads serve while the derived values are wrong.
Trusted Server can instead consume a reader-IP header, but only when the same request carries a shared secret that only the front door knows. Trusting the header on its own would be worse than the problem it solves, because any caller could then choose the address used for geolocation, EC identity derivation, and bot protection.
This is opt-in. With no [trusted_client_ip] section, Trusted Server keeps using the immediate peer address.
Choose dedicated header names
Prefer header names that nothing else in the fronting service uses:
[trusted_client_ip]
ip_header = "x-ts-client-ip"
auth_header = "x-ts-client-ip-auth"
shared_secret = "trusted_client_ip_shared_secret"ip_header may also be fastly-client-ip, which suits a VCL service dedicated to Trusted Server. On a service that also carries other traffic, a dedicated x- name is safer: the front-door VCL then never modifies Fastly-Client-IP, so security rules, rate limiters, logging formats, and vendor snippets that read it keep working unchanged.
Both names are validated at startup. ip_header must be fastly-client-ip or begin with x-, auth_header must begin with x-, the two must differ, and neither may reuse a Trusted Server internal header name such as x-forwarded-for or x-ts-ec. Use lowercase in the TOML.
Configure the front door
Set both headers in the fronting VCL service, reading the secret from a private (write-only) edge dictionary rather than from VCL source:
sub vcl_recv {
# Trusted Server reader-IP handoff.
if (fastly.ff.visits_this_service == 0) {
# Client-supplied copies never survive, on any route.
unset req.http.X-TS-Client-IP;
unset req.http.X-TS-Client-IP-Auth;
# Stamp only on the Trusted Server route, so the secret never reaches
# another backend.
if (req.http.host == "www.example.com") {
set req.http.X-TS-Client-IP = client.ip;
set req.http.X-TS-Client-IP-Auth =
table.lookup(ts_private_config, "trusted_client_ip_secret");
}
}
}Attach an edge dictionary named ts_private_config to the fronting service and store the secret under the key trusted_client_ip_secret. Create the dictionary as write-only so the value cannot be read back through the API or the web interface. Store the identical value in the physical store mapped from trusted_server_secrets, under the key named by trusted_client_ip.shared_secret (trusted_client_ip_shared_secret above). If either key is absent, authentication cannot succeed and Trusted Server falls back to the peer address.
Four details in that example carry weight:
fastly.ff.visits_this_service == 0restricts the whole block to the first entry into this service. A shielded request enters the same service twice, and on the second entryclient.ipis the first POP rather than the reader. Keeping theunsetlines inside this guard also lets the values stamped on the first entry survive the second.unsetbeforesetremoves every copy of each header, including one a client sent. Trusted Server ignores a forwarded address whenever either header carries more than one value, so without theunseta reader could send its own copy, force the fallback, and keep its real address out of geolocation and bot protection.- The route condition wraps only the
setlines. Client-supplied values are removed on every route, while the secret is added only on the route that reaches Trusted Server. Match onreq.http.hostorreq.urlrather than on the selected backend: those are available from the start ofvcl_recvand do not change when other VCL restarts the request. - There is no
req.restartsguard. A restart can change which backend a request reaches. Re-running the block on each pass re-evaluates the route condition, so a stamp made before a restart cannot follow the request to a different backend. On the ordinary path the host is unchanged and re-running writes the same values.
Configure the identical header names and the secret-store key name in Trusted Server. Use a cryptographically random value of at least 32 ASCII graphic bytes, encoded as hex or base64url with no whitespace, for the two store entries. The app-config blob contains only trusted_client_ip_shared_secret, not that value.
What Trusted Server does with the result
Trusted Server accepts the forwarded address only when the request carries exactly one authentication value matching the resolved shared secret byte for byte and exactly one IP value that parses directly as IPv4 or IPv6. Values are not trimmed. Both headers are removed before routing.
| Request state | Address used |
|---|---|
| One matching auth value and one bare IP value | Forwarded reader |
| Auth value missing, empty, wrong, duplicated, or not UTF-8 | Immediate peer |
| IP value missing, duplicated, not UTF-8, or not a bare IP address | Immediate peer |
| Request bypassed the front door | Immediate peer |
No [trusted_client_ip] section configured | Immediate peer |
No combination rejects the request. A misconfigured front door, a rotated secret, or a renamed header degrades to the current behavior rather than causing an outage.
Confirm the topology first
The VCL example assumes the reader connects directly to the fronting Fastly service.
| Topology | client.ip at the front door | Result |
|---|---|---|
| Reader to fronting Fastly service to Trusted Server | The reader | Correct reader address |
| Reader to another CDN to Fastly to Trusted Server | That CDN's node | Wrong address, and authenticated as valid |
| Reader directly to Trusted Server | Not applicable | Falls back to the immediate peer |
| Fastly no-code request routing | No injection point | Mechanism unavailable |
The second row is the one topology that fails with a wrong value instead of falling back. If another CDN precedes Fastly, restrict direct access to the Fastly front door and derive ip_header from that CDN's protected reader-IP value instead of from client.ip.
No-code request routing limitation
Fastly no-code request routing does not provide a point to inject these headers. If that routing path does not preserve the original reader IP, Trusted Server cannot recover it with this mechanism. Use a fronting service that can set both headers before forwarding the request.
Verify before trusting it
Geolocation response headers are the observable signal. From a client whose real location differs from the fronting POP, compare a request through the front door against one sent directly to the Compute service, and confirm x-geo-city and x-geo-coordinates agree.
Then confirm the anti-evasion path holds by sending duplicate and junk trust headers through the front door:
curl -sD - -o /dev/null 'https://www.example.com/' \
-H 'X-TS-Client-IP: 198.51.100.7' \
-H 'X-TS-Client-IP: 203.0.113.10' \
-H 'X-TS-Client-IP-Auth: not-the-secret'The geolocation headers should still describe the reader. If they describe the fronting POP, something upstream is leaving a client-supplied copy in place. IPv6 readers are worth testing separately, because Trusted Server requires a bare address and rejects bracketed, ported, or zone-suffixed forms.
Effect on origin requests
Trusted Server treats fastly-client-ip as client-spoofable and strips it at request entry whether or not [trusted_client_ip] is configured, so it no longer forwards an inbound Fastly-Client-IP to the publisher origin. Check whether the origin reads that header for geolocation, fraud checks, or logging before deploying. Reconstructing a trustworthy client-address header for origin requests is tracked separately from this mechanism.
Create Config and Secret Stores
For features like request signing, you'll need to create Fastly stores:
Config Store
Used for storing public configuration (e.g., public keys, key metadata):
fastly config-store create --name jwks_storeSecret Stores
Trusted Server keeps static app-config credentials under logical store ID trusted_server_secrets. The physical Fastly store can use another name, such as ts_secrets. Request-signing private keys remain in their separate, runtime-managed store.
Set the physical mapping before provisioning:
export EDGEZERO__STORES__SECRETS__TRUSTED_SERVER_SECRETS__NAME=ts_secrets
ts provision --adapter fastlyProvisioning creates or reuses the physical store and persists this runtime mapping in Fastly Config Store edgezero_runtime_env, scoped to the current Fastly service:
EDGEZERO__SERVICES__<SERVICE_ID>__STORES__SECRETS__TRUSTED_SERVER_SECRETS__NAME=ts_secretsThe runtime ignores legacy unscoped entries. The Fastly service must link both ts_secrets and edgezero_runtime_env to the active service version. The custom streaming entry point reads the service-scoped mapping before loading app config, so every startup and reload resolves static credentials from ts_secrets while the portable manifest continues to declare trusted_server_secrets.
Create the separate request-signing store when that feature is enabled:
fastly secret-store create --name signing_keysDo not copy the same app credential store under a second hardcoded trusted_server_secrets Fastly link. Configure the mapping instead.
Create EC KV Store
Edge Cookie flows require one KV store:
- Identity graph store (
ec_store) - EC identity graph, source-domain keyed partner UIDs, minimal consent metadata, and withdrawal tombstones
Partners are configured statically in [[ec.partners]] and loaded into an in-memory registry at startup. There is no separate consent KV store. Consent is interpreted from live request cookies, headers, geolocation, and policy defaults.
Create it:
fastly kv-store create --name ec_identity_storeConfigure the secret-store key name in trusted-server.toml:
[ec]
passphrase = "ec_passphrase"
ec_store = "ec_identity_store"Store the high-entropy passphrase under that key in ts_secrets. The resolved value, rather than the key name, must contain at least 32 characters. Pipe the value over standard input so it never appears in the process argument list:
openssl rand -hex 32 | tr -d '\n' | fastly secret-store-entry create \
--store-id=<ts-secrets-store-id> \
--name=ec_passphrase \
--stdinVerify stores exist:
fastly kv-store listVerify stores are linked to your active service version:
fastly resource-link list --service-id <service-id> --version <active-version>If EC sync returns kv_unavailable or identify responses are degraded, first check that the identity store is present and linked to the active version. Legacy partner/consent KV bindings can be removed once no deployment-specific tooling depends on them.
Next Steps
- Return to Getting Started to continue setup
- See Configuration for detailed configuration options
- See EC Setup Guide for end-to-end EC verification
- See Request Signing for setting up cryptographic signing