# The cache you thought you had

> 2026-08-08 · tuned.page

We shipped `s-maxage=600` months ago and told ourselves pages were cached at the edge. They were not. Not one of them, not once.

## The measurement

Two URLs, same worker:

    /ozer   cache-control: public, s-maxage=600 · vary: cookie · no cf-cache-status
    /       cache-control: public, s-maxage=600 · no vary      · no cf-cache-status

The first diagnosis was `Vary: Cookie` — a known way to defeat shared caching. The landing page killed that theory: no `Vary` there either, and still no `cf-cache-status`.

## What was actually happening

**Cloudflare does not cache a Worker's generated response on its own.** The Worker *is* the origin. There is no cache layer in front of it reading your headers and deciding to store the body. `s-maxage` was a string we sent to nobody.

The fix is to use the Cache API explicitly. Three rules:

- A request carrying a session cookie never touches the cache — not read, not written.
- The key is the URL alone. Cookie-bearing requests are already excluded, so `Vary: Cookie` is stripped before storing.
- Only responses that carry `s-maxage` and set no cookie are stored. The page decides whether it is cacheable; there is no path list to go stale.

After:

    request 1  x-cache: MISS
    request 2  x-cache: HIT
    request 3  x-cache: HIT

## The second trap

What comes back from cache is not what you sent. We send `max-age=0` so browsers always revalidate. The cached response came back with `max-age=14400` — Cloudflare applies the zone's four-hour browser TTL on the way out.

Unnoticed, every visitor's browser would have frozen the page for four hours. In a product whose entire promise is that pages keep themselves current.

The page's own `Cache-Control` is now copied on write and restored on read.

## The part worth stealing

There is a third lesson hiding here. We later needed a CSP source expression for our inline JSON-LD, and a nonce looked like the obvious answer.

It is the wrong answer for a cached page. Pages sit at the edge for ten minutes and **every copy carries the same nonce**. An attacker who downloads the page once learns it and can put it on their injected script. A hash is bound to content: it matches what we generate and nothing anyone injects.

Cache changes the security properties of things that look unrelated to caching.

---

tuned.page — https://tuned.page/tuned
