Configuration (14)
Last updated Oct 12, 2026
Workers Caching is configured per Worker, in your Wrangler configuration file. When enabled, caching applies to every fetch() invocation — eyeball requests, service binding fetch() calls, and loopback fetch() calls between entrypoints via ctx.exports — unless you disable it for a specific entrypoint. Custom RPC methods bypass the cache.
This is your Worker's cache — configured through your Worker's code and Wrangler file. Your Worker controls its cache entirely through:
- The
cache.enabledflag in your Wrangler configuration, which turns caching on or off. You can override it per entrypoint and control cross-version behavior. - The
Cache-Control(andcdn-cache-control,cloudflare-cdn-cache-control) headers your Worker sets on its responses, per RFC 9111 ↗︎. - The optional
Cache-Tagresponse header for bulk purging or invalidation, andctx.cache.purge()andctx.cache.invalidate()to purge or invalidate from code.
That is the entire configuration surface.
Enable caching
Note
Requires Wrangler 4.69.0 or above.
Add a cache block to your Wrangler configuration:
{
"name": "my-worker",
"main": "src/index.ts",
"upload_source_maps": true,
// Set this to today's date
"compatibility_date": "2026-10-27",
"cache": {
"enabled": true,
},
}
name = "my-worker"
main = "src/index.ts"
upload_source_maps = true
# Set this to today's date
compatibility_date = "2026-10-27"
[cache]
enabled = true
Setting cache.enabled to true causes Cloudflare to check the cache before invoking your Worker on every HTTP request. This is the default for every entrypoint; you can override it per entrypoint with exports.
The cache block accepts two fields: enabled (required) and cross_version_cache (optional). Any other fields are reserved for future use and may cause validation errors in future versions of Wrangler.
Disable caching
To turn caching off, set cache.enabled to false (or remove the cache block) and redeploy:
{
"name": "my-worker",
"main": "src/index.ts",
"upload_source_maps": true,
// Set this to today's date
"compatibility_date": "2026-10-27",
"cache": {
"enabled": false,
},
}
name = "my-worker"
main = "src/index.ts"
upload_source_maps = true
# Set this to today's date
compatibility_date = "2026-10-27"
[cache]
enabled = false
Disabling caching does not purge previously cached responses — it only stops Cloudflare from consulting or populating the cache on subsequent requests. If you re-enable caching later, any entries that are still within their TTL become usable again. If you need cached responses to stop being served immediately, purge the cache after disabling.
Per-entrypoint caching
Note
Requires Wrangler 4.107.0 or above.
cache.enabled sets the default for the whole Worker, but a Worker can expose several entrypoints — the default export and any number of named WorkerEntrypoint classes — and you can turn caching on or off for each one independently. Use the exports map, keyed by entrypoint name, with "default" referring to the default export:
{
"name": "my-worker",
"main": "src/index.ts",
"upload_source_maps": true,
// Set this to today's date
"compatibility_date": "2026-10-27",
"cache": {
"enabled": true,
},
"exports": {
// Opt the default entrypoint out of caching.
"default": { "type": "worker", "cache": { "enabled": false } },
// Keep caching on for the Admin entrypoint.
"Admin": { "type": "worker", "cache": { "enabled": true } },
},
}
name = "my-worker"
main = "src/index.ts"
upload_source_maps = true
# Set this to today's date
compatibility_date = "2026-10-27"
[cache]
enabled = true
[exports.default]
type = "worker"
[exports.default.cache]
enabled = false
[exports.Admin]
type = "worker"
[exports.Admin.cache]
enabled = true
Each entry is { "type": "worker", "cache": { "enabled": <boolean> } }. A per-entrypoint cache.enabled overrides the top-level cache.enabled for that entrypoint; entrypoints you do not list inherit the top-level value. You can also enable caching for a single entrypoint without a top-level cache block by listing only that entrypoint.
This lets you opt specific entrypoints in and out without changing your Worker code:
- Opt an entrypoint out to keep it running on every request — the natural fit for a gateway or router entrypoint that authenticates, normalizes, or dispatches, and should never itself be served from cache. This is the recommended way to build the gateway pattern: disable caching on the gateway entrypoint and enable it on the inner entrypoint the gateway calls through
ctx.exports. - Opt an entrypoint in to cache only the specific entrypoints that return reusable responses, leaving the rest of the Worker uncached.
For lowest latency, disable cache for an entrypoint instead of returning `Cache-Control: no-store` for all responses
Do not enable caching on a gateway entrypoint and then return Cache-Control: no-store on every response to keep it running. With caching enabled, each request to that entrypoint still consults the lower and upper cache tiers first — incurring the tiered-cache round trip — only to run the Worker and discard an uncacheable response. That adds latency for no benefit. Disable caching on the entrypoint in the exports map instead, so requests skip the cache lookup and go straight to your gateway logic.
Versioned deployments
The cache configuration is part of your Worker version:
- Each version uploaded with
wrangler deployorwrangler versions uploadcaptures whatevercache.enabledvalue is in its Wrangler configuration. - Rolling back to a previous version also rolls back the
cachesetting attached to that version. - You can use gradual deployments to turn caching on for a percentage of traffic before applying it to 100%. During a gradual rollout from a version with caching disabled to a version with caching enabled, traffic routed to the old version runs uncached as it did before, and traffic routed to the new version consults and populates the cache. By default, the Worker version is part of the cache key, so the two versions populate independent cache entries and do not serve each other's responses — see Cross-version caching.
Cross-version caching
Note
The cross_version_cache option requires Wrangler 4.107.0 or above.
By default, the Worker version is part of the cache key. Each deployed version has its own isolated cache, so a new deployment starts from an empty cache and never serves responses written by a previous version. This is the default because it is the simplest behavior to reason about: a new deployment applies immediately, and you never serve a response that a superseded version produced.
The trade-off is that cache hit rate resets on every deployment. Because a new version cannot reuse the previous version's cached responses, the first requests after a deploy are misses while the new version's cache fills. This is the most common reason a Worker's cache hit rate drops right after a deployment.
If you want to maximize cache hit rate and are willing to accept slower rollouts of cache-affecting changes, set cross_version_cache to true. Cached responses are then shared across versions — a response written by one version can be served by a later version as long as its TTL has not expired:
{
"name": "my-worker",
"main": "src/index.ts",
"upload_source_maps": true,
// Set this to today's date
"compatibility_date": "2026-10-27",
"cache": {
"enabled": true,
"cross_version_cache": true,
},
}
name = "my-worker"
main = "src/index.ts"
upload_source_maps = true
# Set this to today's date
compatibility_date = "2026-10-27"
[cache]
enabled = true
cross_version_cache = true
Advanced users who deploy frequently and whose responses do not change between most deployments should consider enabling cross_version_cache — it avoids throwing away a warm cache on every deploy. The cost is that a deployment no longer invalidates the cache: after a change that alters response content, older cached responses continue to be served until they expire or you purge them, and during a gradual deployment both versions share one cache. When you need a deployment to take effect immediately with cross_version_cache enabled, purge the cache after deploying, or tag responses by version — see Invalidating cache across deployments.
cross_version_cache only has an effect when caching is enabled. It applies to every entrypoint whose cache is on.
Environment-specific configuration
The cache block can be set at the top level and overridden per environment. The typical pattern is to turn caching on in production once you are confident it is safe, while keeping staging uncached for easier debugging:
{
"name": "my-worker",
"main": "src/index.ts",
"upload_source_maps": true,
// Set this to today's date
"compatibility_date": "2026-10-27",
"cache": {
"enabled": false,
},
"env": {
"production": {
"cache": {
"enabled": true,
},
},
},
}
name = "my-worker"
main = "src/index.ts"
upload_source_maps = true
# Set this to today's date
compatibility_date = "2026-10-27"
[cache]
enabled = false
[env.production.cache]
enabled = true
Cache-Control semantics
With caching enabled, your Worker is the origin for Cloudflare's cache. Standard HTTP Cache-Control directives on the response your Worker returns determine whether and for how long Cloudflare caches it. For the full list of directives and how they interact, refer to Cache-Control.
Responses with no `Cache-Control` header are still cached
If your Worker returns a response with no Cache-Control header (and no Expires header), Cloudflare applies RFC 9111 heuristic freshness ↗︎ with the per-status default TTLs in the following table. Responses whose status is not in the table are not cached by default.
| Status code | Description | Default TTL |
|---|---|---|
200 |
OK | 7200 seconds (2 hours) |
203 |
Non-Authoritative Information | 7200 seconds (2 hours) |
204 |
No Content | 7200 seconds (2 hours) |
300 |
Multiple Choices | 1200 seconds (20 minutes) |
301 |
Moved Permanently | 1200 seconds (20 minutes) |
404 |
Not Found | 180 seconds (3 minutes) |
405 |
Method Not Allowed | 60 seconds (1 minute) |
410 |
Gone | 180 seconds (3 minutes) |
414 |
URI Too Long | 60 seconds (1 minute) |
501 |
Not Implemented | 60 seconds (1 minute) |
For example, a 200 response that does not set Cache-Control is cached for 2 hours. A 404 is cached for 3 minutes.
If you want a response not to be cached, return Cache-Control: no-store (or private) explicitly. Omitting Cache-Control is not the same as opting out of the cache.
If you set Cache-Control (or Expires) on the response, these defaults do not apply — your directives are honored instead.
Cache Deception Armor
When a response falls back to heuristic freshness (no Cache-Control set), Workers Caching also runs Cache Deception Armor to defend against cache deception attacks ↗︎.
Cache Deception Armor only inspects responses whose Content-Type starts with text/ or application/ — the high-risk types for accidentally caching generated, user-specific content under a static-looking URL. For those responses, if the request URI has a file extension that maps to a known MIME type (for example, .css → text/css) and the actual Content-Type does not match, the response is not cached and Cf-Cache-Status is BYPASS. For example, a Worker that serves /style.css with Content-Type: text/html will hit this.
Responses with Content-Type outside text/* and application/* (for example, image/png, video/mp4) are not subject to the check — those types are rarely used to deliver private data, so the trade-off of a stricter check is not worth the false positives. A request URI with no file extension (for example, /api/users/42) also does not trigger the check.
Setting any explicit Cache-Control directive skips the check entirely — Cache Deception Armor only activates when freshness is being inferred heuristically.
Set the freshness window with max-age
Use max-age to control how long the response is treated as fresh:
src/index.jsjs
export default {
async fetch(request) {
const body = await renderPage(request);
return new Response(body, {
headers: {
"Content-Type": "text/html",
// Cached for 1 hour at Cloudflare's edge and in the browser.
"Cache-Control": "public, max-age=3600",
},
});
},
};
// Replace with your own rendering logic.
async function renderPage(request) {
return `<!doctype html><title>Home</title><h1>Hello</h1>`;
}
src/index.tsts
export default {
async fetch(request): Promise<Response> {
const body = await renderPage(request);
return new Response(body, {
headers: {
"Content-Type": "text/html",
// Cached for 1 hour at Cloudflare's edge and in the browser.
"Cache-Control": "public, max-age=3600",
},
});
},
} satisfies ExportedHandler;
// Replace with your own rendering logic.
async function renderPage(request: Request): Promise<string> {
return `<!doctype html><title>Home</title><h1>Hello</h1>`;
}
If you need browsers and the edge to cache for different durations, use cdn-cache-control (or cloudflare-cdn-cache-control) for the edge-only directive and keep Cache-Control for what browsers see. Refer to Header precedence below.
Use stale-while-revalidate for low-latency refreshes
When a cached response becomes stale, stale-while-revalidate lets Cloudflare return the stale response immediately and refresh it in the background:
src/index.jsjs
export default {
async fetch(request) {
const data = { timestamp: Date.now() };
return new Response(JSON.stringify(data), {
headers: {
"Content-Type": "application/json",
// Fresh for 10 minutes; may be served stale for up to 1 minute
// while a background revalidation runs.
"Cache-Control": "public, max-age=600, stale-while-revalidate=60",
},
});
},
};
src/index.tsts
export default {
async fetch(request): Promise<Response> {
const data = { timestamp: Date.now() };
return new Response(JSON.stringify(data), {
headers: {
"Content-Type": "application/json",
// Fresh for 10 minutes; may be served stale for up to 1 minute
// while a background revalidation runs.
"Cache-Control": "public, max-age=600, stale-while-revalidate=60",
},
});
},
} satisfies ExportedHandler;
`s-maxage`, `must-revalidate`, and `proxy-revalidate` disable `stale-while-revalidate`
If your response includes any of s-maxage, must-revalidate, or proxy-revalidate, the stale-serving behavior is disabled and Cloudflare will block on a fresh revalidation when the response expires. The same is true for stale-if-error. This follows RFC 9111 §4.2.4 ↗︎: those three directives forbid serving stale content.
When you want stale-while-revalidate to take effect at the edge, use max-age for the freshness window — not s-maxage. If you need a longer edge TTL than browsers should honor while still using stale-while-revalidate, use cdn-cache-control for the edge directive.
Choose TTL and stale-while-revalidate values
High cache hit rate and high freshness are in tension. Background revalidation hides the latency of refreshing the cache, but your Worker still runs once per revalidation — it is not free.
