Reference
Cache configuration
Worked examples for the four things most zones need: correct
Cache-Control headers at the origin, TTL rules per path, a stable cache key,
and invalidation that is precise enough to be used on every deploy.
- 01Cache-Control directives
- 02Header recipes by content type
- 03Zone rules
- 04Cache keys and normalisation
- 05Surrogate keys
- 06Purge API
- 07Verifying behaviour
01Cache-Control directives
The edge reads the directives below from origin responses. Anything not listed is passed through to the client untouched.
| Directive | Effect at the edge |
|---|---|
max-age=N | Freshness for N seconds at the edge and in the browser. |
s-maxage=N | Freshness at the edge only; overrides max-age there. |
stale-while-revalidate=N | Serve the expired object for up to N seconds while refreshing in the background. |
stale-if-error=N | Serve the expired object for up to N seconds if the origin errors or times out. |
immutable | Suppresses revalidation requests for content-addressed assets. |
no-cache | Store the object, but revalidate with the origin before every reuse. |
private, no-store | Never stored at the edge. |
02Header recipes by content type
Fingerprinted assets — /assets/app.9f2c1d.js
Cache-Control: public, max-age=31536000, immutable
ETag: "9f2c1d"
The filename changes when the content changes, so the object can live at the edge for a year and never be revalidated.
HTML pages — short TTL, background refresh
Cache-Control: public, max-age=0, s-maxage=300,
stale-while-revalidate=600, stale-if-error=86400
Vary: Accept-Encoding
Browsers always revalidate; the edge serves for five minutes, refreshes quietly for ten more, and rides out a day-long origin outage rather than serving an error page.
Read-only JSON — small TTL, tagged for purge
Cache-Control: public, s-maxage=60, stale-while-revalidate=120
Surrogate-Key: catalog product-4417
Vary: Accept-Encoding, Accept-Language
Anything user-specific
Cache-Control: private, no-store
Responses that depend on a session must be marked private, no-store at the
origin. Do not rely on a path rule to protect them: a rule can be edited, a header travels
with the response.
03Zone rules
Rules are evaluated top to bottom; the first pattern that matches wins. They set a
default for responses whose headers are absent or wrong, and they can override the origin
when override: true is set.
static-edge.yaml
zone: docs-static
default_ttl: 60s
compress: [text/html, text/css, application/javascript, application/json]
rules:
- match: "/assets/*"
ttl: 365d
browser_ttl: 365d
override: true
ignore_query: true
- match: "/api/v1/catalog*"
ttl: 60s
stale_while_revalidate: 120s
cache_key:
query_allow: [page, per_page, sort]
headers: [accept-language]
- match: "/api/v1/account*"
cache: false
- match: "/*"
ttl: 300s
stale_if_error: 24h
Validate a file before applying it:
se-cli config validate static-edge.yaml
se-cli config apply static-edge.yaml --zone docs-static
04Cache keys and normalisation
By default the key is method + host + path + sorted query string, plus the
values of every header named in Vary. Two habits keep the hit ratio high:
- Allow-list query parameters. Marketing parameters such as
utm_sourcefragment one object into thousands. Listing the parameters that actually change the response collapses them back into one. - Keep
Varyshort. Every additional header multiplies the number of stored variants.Vary: Accept-Encodingis almost always enough;Vary: User-Agenteffectively disables caching.
Requests that share a single cache key
/catalog?page=2&utm_source=newsletter
/catalog?utm_source=ads&page=2
/catalog?page=2
-> key: GET|shop.example|/catalog|page=2
05Surrogate keys
A surrogate key is a label attached to a response so that a group of objects can be dropped together. Emit them at the origin as a space-separated list:
Surrogate-Key: product-4417 catalog homepage
One product page, the catalog listing and the homepage can then all be invalidated by a single call when product 4417 changes, without knowing which URLs contain it. The header is consumed by the edge and stripped before the response reaches the client.
Limits: up to 64 keys per response, 128 bytes per key, 500 keys per purge request.
06Purge API
All calls are authenticated with a zone token in the Authorization header
and return 202 Accepted with a job identifier. Propagation to every node
normally completes in under five seconds.
Purge a single URL
curl -X POST https://static-edge-cdn.xyz/api/v1/zones/docs-static/purge \
-H "Authorization: Bearer $SE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"urls": ["/index.html", "/catalog?page=2"]}'
Purge by prefix
curl -X POST https://static-edge-cdn.xyz/api/v1/zones/docs-static/purge \
-H "Authorization: Bearer $SE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"prefix": "/api/v1/catalog"}'
Purge by surrogate key
curl -X POST https://static-edge-cdn.xyz/api/v1/zones/docs-static/purge \
-H "Authorization: Bearer $SE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"keys": ["product-4417", "catalog"]}'
Response
HTTP/2 202
content-type: application/json
{
"job": "pj_01J8ZQ4M2X",
"zone": "docs-static",
"objects": 3,
"queued_at": "2026-08-12T09:41:07Z"
}
Check a job
curl https://static-edge-cdn.xyz/api/v1/jobs/pj_01J8ZQ4M2X \
-H "Authorization: Bearer $SE_TOKEN"
{"everything": true} for incidents. It is rate limited to two calls per hour
per zone.
07Verifying behaviour
Request the same URL twice and read the headers. The first response should be
MISS with age: 0, the second HIT with a growing
age:
curl -sSI https://static-edge-cdn.xyz/docs | grep -Ei 'x-se-|age|cache-control'
If a response you expect to be cached reports BYPASS, check in order:
- A
Set-Cookieheader on the response. privateorno-storeinCache-Control.- A zone rule with
cache: falsematching earlier than you expected. - A request method other than
GETorHEAD.
A low hit ratio with plenty of MISS responses usually means key
fragmentation instead — revisit section 04.
Last reviewed: 12 August 2026 · API version v1