Cache-Control directives determine cacheability
The `Cache-Control` header (and `cdn-cache-control`, `cloudflare-cdn-cache-control`) set on responses determine whether and how long Cloudflare caches them. Standard HTTP `Cache-Control` directives follow RFC 9111 semantics.
RFC 9111 heuristic freshness defaults for cacheable status codes
If a response has no `Cache-Control` header and no `Expires` header, Cloudflare applies RFC 9111 heuristic freshness with these per-status default TTLs: 200/203/204 (7200 seconds = 2 hours), 300/301 (1200 seconds = 20 minutes), 404/410 (180 seconds = 3 minutes), 405/414/501 (60 seconds = 1 minute). Other status codes are not cached by default. To prevent caching, return `Cache-Control: no-store` or `private` explicitly.
Cache Deception Armor protects against cache poisoning
When responses fall back to heuristic freshness (no `Cache-Control` set), Workers Caching runs Cache Deception Armor to defend against cache deception attacks. It only inspects responses with `Content-Type` starting with `text/` or `application/`. If the request URI has a file extension mapping to a known MIME type and the actual `Content-Type` does not match, the response is not cached and `Cf-Cache-Status` is `BYPASS`. For example, `/style.css` with `Content-Type: text/html` will bypass. Responses with other content types and requests without file extensions are not subject to the check. Any explicit `Cache-Control` directive skips Cache Deception Armor entirely.
Use max-age to control freshness window
The `max-age` directive controls how long a response is treated as fresh. For example, `Cache-Control: public, max-age=3600` caches the response for 1 hour at both Cloudflare's edge and in browsers.
Use stale-while-revalidate for low-latency refreshes
The `stale-while-revalidate` directive lets Cloudflare return a stale cached response immediately while refreshing it in the background. For example, `Cache-Control: public, max-age=600, stale-while-revalidate=60` keeps the response fresh for 10 minutes and may serve it stale for up to 1 minute while a background revalidation runs.
Directives that disable stale-while-revalidate
If the response includes `s-maxage`, `must-revalidate`, or `proxy-revalidate`, the stale-serving behavior is disabled and Cloudflare will block on fresh revalidation when the response expires. The same applies to `stale-if-error`. This follows RFC 9111 §4.2.4. To use `stale-while-revalidate` at the edge, use `max-age` for the freshness window, not `s-maxage`. If a longer edge TTL than browser TTL is needed, use `cdn-cache-control` for the edge directive.
TTL and stale-while-revalidate strategy for mostly static content
For mostly static content with small tolerance for staleness, use a short `max-age` (for example, 60 seconds) and a longer `stale-while-revalidate` window (for example, 3600 seconds). Most requests are HITs; occasional requests trigger a background refresh.
TTL and stale-while-revalidate strategy for high-traffic endpoints
For "always serve from cache" on high-traffic endpoints, use `max-age=0, stale-while-revalidate=<large>`. Every request returns the previously cached response immediately and triggers a background refresh. The Worker runs once per request to revalidate, so CPU costs are close to running every time. Freshness drops as request volume drops.
Serve stale on error with stale-if-error
The `stale-if-error` directive lets Cloudflare return a previously cached response when the Worker fails while refreshing an expired cache entry. This occurs when the Worker throws, times out, or returns a 5xx response. Cloudflare serves the last successful cached response (with `Cf-Cache-Status: STALE`) for up to the `stale-if-error` window. A true cache miss cannot benefit from `stale-if-error` because there is nothing stale to serve.
Default stale-if-error behavior when not set
If `stale-if-error` is not set explicitly and the response does not carry `s-maxage`, `must-revalidate`, or `proxy-revalidate`, Cloudflare's default is to serve stale responses on Worker error indefinitely (as long as the cached entry has not been purged). This aids resilience but can mask real failures. To surface Worker errors to clients quickly, set `stale-if-error=0`. If the response carries `s-maxage`, `must-revalidate`, or `proxy-revalidate`, `stale-if-error` is disabled and Worker errors flow through to clients immediately.
Cache header precedence order
When multiple cache headers are present, precedence is: 1. `cloudflare-cdn-cache-control` (Cloudflare-specific, highest precedence, consumed by Cloudflare and stripped from response to clients), 2. `cdn-cache-control` (standard header for CDN-only directives, respected by Cloudflare and passed through to downstream CDNs), 3. `Cache-Control` (standard HTTP header, respected by Cloudflare and passed through to clients). Use `cloudflare-cdn-cache-control` for longer edge TTL than exposed to browsers without leaking the directive downstream.
Override Cache-Control via cf.cacheControl in ctx.exports calls
When one entrypoint invokes another cached entrypoint through `ctx.exports`, the calling entrypoint can supply the `Cache-Control` directive by setting `cf.cacheControl` on the request. The callee does not need to set its own `Cache-Control`. Cloudflare treats `cf.cacheControl` as a trusted directive following standard `Cache-Control` semantics. This lets the caller decide caching policy without modifying the callee's code. `cf.cacheControl` is honored only for calls within the same account and has no effect on eyeball requests.
Automatic cache bypass conditions
Workers Caching inherits Cloudflare's standard cache bypass rules. Most common triggers: response includes a `Set-Cookie` header (unless `Cache-Control` includes `private="set-cookie"` or `no-cache="set-cookie"`), request includes an `Authorization` header (response only stored if `Cache-Control` includes `public`, `must-revalidate`, or `s-maxage`), response `Cache-Control` includes `private` or `no-store`. When any apply, `Cf-Cache-Status` is `BYPASS` and the Worker runs on every request.
no-cache does not bypass the cache
`Cache-Control: no-cache` does not bypass the cache. The response is stored but treated as stale immediately. Behavior depends on accompanying directives: `no-cache` alone triggers inline revalidation (Worker invoked before serving), `no-cache, stale-while-revalidate=N` serves cached body immediately and refreshes in background. In both cases, the Worker is invoked on every request and CPU time is billed. The cached body is preserved across revalidations.
Status codes never cached
Status codes 520–526 (Cloudflare failsafe responses) are treated as transient errors and never cached, even with explicit `Cache-Control` directives.