Response Headers
PRISM adds several headers to HTTP responses to indicate rendering status, cache state, and variant information. These headers are useful for debugging, CDN cache keying, and monitoring.
PRISM strips any x-prism-* headers from origin responses before adding its own, preventing origin-side forgery.
Rendered Responses
These headers appear on responses that were rendered through the Chrome pipeline:
x-prism-rendered
x-prism-rendered: true
Present on every response that was rendered by PRISM via headless Chrome. Absent on proxied (bypass) responses.
x-prism-cache
x-prism-cache: HIT|MISS|STALE|BYPASS
Indicates the cache status of the response:
| Value | Meaning |
|---|---|
HIT | Served from the render cache |
MISS | Freshly rendered and now cached |
STALE | Served stale from cache while re-rendering in the background |
BYPASS | Not rendered; proxied directly to origin |
This header is present on all responses, including bypass responses.
STALE requires that serving stale is permitted. An origin that sent
must-revalidate, proxy-revalidate or s-maxage has forbidden it — s-maxage
carries proxy-revalidate semantics for shared caches (RFC 9111 §5.2.2.10) — so
entries carrying those expire outright rather than entering the grace window
configured by cache.grace_period_secs.
Cache-Status
Cache-Status: prism; hit
The same fact as x-prism-cache, in the interoperable field RFC 9211 defines,
so a CDN, a browser devtools pane or an engineer reading someone else's stack
can see it without knowing PRISM exists. Both are sent; x-prism-cache
predates the standard and deployments read it, so it is not going anywhere.
| Situation | Cache-Status |
|---|---|
| served from cache | prism; hit |
| served stale while re-rendering | prism; hit; detail=stale |
| rendered because nothing was stored | prism; fwd=miss |
| not rendered at all | prism; fwd=bypass |
Stale is reported as a qualified hit rather than fwd=stale, because the
reader was answered from the cache — forwarding is precisely what serving
stale avoids.
There is no ttl parameter. RFC 9211 makes every parameter optional, and the
remaining freshness is derivable from the Age and
Cache-Control sent alongside it. A third statement of the
same number would be one more thing that can disagree with the other two.
x-prism-render-time
x-prism-render-time: 1250
The time in milliseconds that the Chrome render took. Only present on cache MISS responses (fresh renders). Not included on HIT or STALE responses since no render occurred.
x-prism-variant
x-prism-variant: bot-mobile|bot-desktop|human-mobile|human-desktop
The rendering variant used for this request. Determined by combining bot detection with device detection (when viewport-aware rendering is enabled):
| Variant | Description |
|---|---|
bot-mobile | Bot user-agent on a mobile viewport |
bot-desktop | Bot user-agent on a desktop viewport |
human-mobile | Human user on a mobile viewport |
human-desktop | Human user on a desktop viewport |
x-prism-fallback
x-prism-fallback: true
Present when PRISM served the origin's own response instead of a render. The value says why:
| Value | Meaning |
|---|---|
true | The render was attempted and failed — Chrome crash, timeout, content validation — so the origin's response was served instead. A reader always gets an answer even when the render pipeline is degraded. |
not-html | The origin returned something that is not HTML on a route that renders. Nothing to render; it is passed through unchanged. |
no-transform | The origin sent Cache-Control: no-transform and cache.honor_no_transform is on. The render succeeded and was discarded: the directive forbids serving a transformed representation, not producing one. |
The first is a degradation. The other two are PRISM doing what it was told.
Bypass / Unlicensed Responses
x-prism-license
x-prism-license: unlicensed
Present on bypass responses when PRISM is running without a valid license. Not present when properly licensed.
CDN Integration Headers
Vary
Vary: User-Agent, Accept-Encoding, Accept
Names the request headers that select between representations of a URL, merged with whatever the origin already declared and anything listed in [cache] vary.
User-Agent appears only when it can actually matter — in bot-only mode, where it decides whether a client is rendered or proxied, and when viewports are enabled. It is close to unique per client, so declaring it costs a downstream cache nearly every hit. Operators who want low-cardinality edge caching normalise the User-Agent into one header at the edge and list that header in [cache] vary; PRISM then keys on it and advertises it instead.
X-Prism-Variant is deliberately not listed here. Vary names request headers and nothing sends that one, so a cache looked it up, found it absent on every request, and collapsed every variant into a single object. Use it as a cache-key component instead — see CDN Integration.
Since 1.4.0 this is set on bypassed responses too, when the route is one that renders for somebody. A URL that renders for a crawler and is proxied to a person has two representations, and a downstream cache that stores the second without knowing what separates them will answer the first with it — on a request PRISM never sees and so cannot correct. Routes that never render declare nothing extra, so edge caching for assets and excluded paths is unaffected.
Age
Age: 47
How many seconds old the response body is, counting time it spent in a cache upstream of PRISM before it arrived. Required of any cache reusing a stored response (RFC 9111 §4).
Without it a downstream CDN starts its own freshness clock from zero and the same seconds are counted on every hop. PRISM also reads the origin's Age: a response arriving with Age: 50 and max-age=60 has ten seconds of freshness left here, not sixty.
Time spent rendering is included, since 1.6.0. The entry's clock starts when it is stored, which is after the render, so the age it starts from accounts for the origin's Date, its Age, and however long the fetch and render took — RFC 9111 §4.2.3. Before that the render was granted for free: on the shipped 30-second error TTL, a five-second render was a sixth of the lifetime nobody had counted.
Cache-Control
Cache-Control: public, max-age=3600, s-maxage=3600
Synthesized by PRISM only when the origin response includes no Cache-Control of its own and cache.synthesize_cache_control is enabled.
The lifetime announced is the one PRISM actually stores under, which since 1.5.3 depends on what the response is:
| response | announced |
|---|---|
| an ordinary render | cache.default_ttl or the path-specific rule |
| a 5xx from the origin | cache.error_ttl_secs (default 30s), or no-store when that is 0 |
| PRISM's own 429, 502, 503, 504 | no-store |
Before 1.5.3 the route's TTL was announced in every case, so an error stored for thirty seconds was advertised for an hour and anything caching in front of PRISM kept serving it after the origin recovered. See status codes for what is stored and for how long.
When the origin does send one, it is preserved and — since 1.4.0 — obeyed. PRISM's cache is shared and cannot revalidate, so a directive requiring validation before reuse can only be honoured by not keeping the entry at all: no-cache, max-age=0, s-maxage=0 and Vary: * are all refused, alongside no-store and private. Where the origin does grant freshness it caps PRISM's own TTL rather than being overruled by it, s-maxage first.
Before 1.4.0 only no-store and private were read and everything else was forwarded to the client while PRISM itself ignored it.
Header Precedence
PRISM preserves security and cache headers from the origin response (e.g., Strict-Transport-Security, X-Frame-Options, Content-Security-Policy) but does not override any header it has already set. Origin ETag and Last-Modified headers are not forwarded on rendered responses because the response body has been transformed.
Debugging with Headers
Use curl -I to inspect PRISM headers without downloading the full response:
# Check if a page is being rendered
curl -sI https://example.com/products/widget | grep -i x-prism
# Expected output for a rendered bot request:
# x-prism-rendered: true
# x-prism-cache: HIT
# x-prism-variant: bot-desktop
To test as a bot:
curl -sI -A "Googlebot/2.1" https://example.com/products/widget | grep -i x-prism