A hand-rolled caches.default layer cached one error for 24 hours

Let the platform cache honor upstream headers on proxied GETs; a manual Cache API layer that stores whatever comes back will pin a transient error.

Workers

· Chapter

8

·

2

min read

The answer. If your Worker is a thin reverse proxy (an analytics ingest, a CDN shim), don't hand-roll caching with caches.default. A naive cache.put(request, response.clone()) stores whatever the upstream returned — including a transient error — for as long as the TTL says, and every visitor at that edge location gets the poisoned copy until it expires. Delete the layer. Cloudflare already caches GET subrequests according to the upstream's own Cache-Control/immutable headers, and it applies them correctly. Your job is the path split and a minimal header set; caching is the platform's job.

The pattern.

const API_HOST = "analytics-api.example";
const ASSET_HOST = "analytics-assets.example";
const MOUNT = "/ingest";

export default {
  async fetch(request) {
    const url = new URL(request.url);
    if (url.pathname !== MOUNT && !url.pathname.startsWith(MOUNT + "/"))
      return new Response("not found", { status: 404 });
    const path = url.pathname.slice(MOUNT.length) || "/";
    const host = path.startsWith("/static/") ? ASSET_HOST : API_HOST;

    // Allow-list headers; forwarding the injected cf-*/x-forwarded-* bag caused a spurious 301.
    const headers = new Headers();
    for (const h of ["content-type", "user-agent", "accept"]) {
      const v = request.headers.get(h); if (v) headers.set(h, v);
    }
    const ip = request.headers.get("cf-connecting-ip");
    if (ip) headers.set("x-forwarded-for", ip);

    // No caches.default here: upstream's max-age/immutable headers drive the edge cache.
    const body = request.method === "GET" || request.method === "HEAD" ? undefined : request.body;
    return fetch(`https://${host}${path}${url.search}`, { method: request.method, headers, body });
  },
};

The receipt. HarperFlow's marketing site proxies PostHog through /ingest/* on its own hostname so hostname-based ad-blockers stop dropping visitors. An earlier version of that Worker added a caches.default layer in front of the script assets. One transient upstream error got stored with a 24-hour TTL and the proxy started returning intermittent 404s — per edge location, so it looked random. Deleting the layer fixed it; the production route went live on 2026-08-07 without it, and "no hand-rolled caches.default" is now written into the spec every sibling Worker on that zone copies from. We didn't log how many visitors hit the poisoned copy.

Watch out.

  • Forwarding the whole incoming header bag (cf-*, x-forwarded-*) perturbed upstream routing and produced a spurious 301. Allow-list headers instead.
  • If you must use the Cache API, check response.ok before put, keep the TTL short, and key on the full URL including the query string.
  • Keep the proxy route on /ingest* only, and re-verify that a POST still returns 200 whenever a broader /* route lands on the same zone.

Related: worker-fetch-own-zone-error-1042 · wrangler-kv-r2-default-local-use-remote