Skip to content

[Cache] Document CDN-Cache-Control as a BYPASS cause - #32569

Open
ngayerie wants to merge 3 commits into
productionfrom
ngayerie/cache-cdn-cache-control-bypass
Open

[Cache] Document CDN-Cache-Control as a BYPASS cause#32569
ngayerie wants to merge 3 commits into
productionfrom
ngayerie/cache-cdn-cache-control-bypass

Conversation

@ngayerie

@ngayerie ngayerie commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

Two small follow-ups to #32423.

What

  • Add a bullet to the BYPASS lists in cache-responses.mdx and investigating-uncached-responses.mdx documenting Cloudflare-CDN-Cache-Control and CDN-Cache-Control, with the precedence chain Cloudflare-CDN-Cache-Control > CDN-Cache-Control > Cache-Control. Uses a concrete example so the reader sees that a cacheable Cache-Control alongside a non-cacheable CDN-Cache-Control still produces BYPASS.
  • Rewrite the Age callout in cache-responses.mdx and the corresponding HIT-verification bullet in the troubleshooting page. Both currently mention CacheTieredFill=true, which is an http_requests Logpush field, not an HTTP header — readers cannot observe it on a response.

Why

Neither BYPASS list mentioned Cloudflare-CDN-Cache-Control or CDN-Cache-Control. When both Cache-Control and CDN-Cache-Control are set, CDN-Cache-Control wins per CDN-Cache-Control, so a reader debugging a BYPASS while only looking at Cache-Control has no signal from these pages that another header is in play.

@zaidoon1 for review.

- Add a BYPASS bullet documenting Cloudflare-CDN-Cache-Control and
  CDN-Cache-Control, and their precedence over Cache-Control. An
  origin returning cacheable Cache-Control alongside a non-cacheable
  CDN-Cache-Control directive produces BYPASS, and readers otherwise
  have no signal that these two headers even exist as BYPASS causes.
- Rewrite the Age callout and HIT confirmation to describe the
  Tiered Cache lower-tier fill in plain language, without referring
  to the internal CacheTieredFill=true field. That field is only
  visible in the http_requests Logpush dataset, not in HTTP
  responses, so it is not something readers can inspect.
@ngayerie
ngayerie requested review from a team, ack-cf and zaidoon1 as code owners August 6, 2026 09:29
@cloudflare-docs-bot

cloudflare-docs-bot Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Review

⚠️ Part of the review could not complete in commit 7ebf475 — it will retry on the next push.

Code Review

This code review is in beta and may not always be helpful — use your judgment.

No code review issues found.

Conventions

❌ This review could not complete this run; results may be incomplete. It will retry on the next push.

Style Guide Review

No style-guide issues found.

Commands

Only codeowners can run commands. Post a comment with the command to trigger it.

Command Description
/review Runs a review now. Incremental if a prior review exists, full if not.
/full-review Re-reviews the entire PR diff from scratch, ignoring incremental history. Useful after a rebase, when you want a fresh review, or if the bot gets out of sync and reports issues that no longer exist.
/ignore-review-limit Permanently lifts the 2-review automatic limit for this PR. Future pushes will trigger reviews as normal.
/disable-auto-review Stops automatic reviews from triggering on future pushes to this PR. Codeowners can still run /review or /full-review manually.
/rebase Rebases the PR branch against production. On conflict, attempts to resolve automatically using AI. Stops with an explanation if confidence is not high enough.

@github-actions github-actions Bot added product:cache Issues or PRs related to Cache size/xs labels Aug 6, 2026
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

This pull request requires reviews from CODEOWNERS as it changes files that match the following patterns:

Pattern Owners
/src/content/docs/cache/ @cloudflare/product-owners, @ack-cf, @zaidoon1

Address SG-619a15c3d17a from the docs bot review.
@@ -15,7 +15,7 @@ The `CF-Cache-Status` header output indicates whether a resource is cached or no
:::note[`Age` response header]
The `Age` response header is a header returned from cache that specifies the time in seconds that an asset has been in Cloudflare's cache. This value resets if the asset is revalidated, purged, or evicted and then re-cached.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As we're making this note more clear, I suggest updating this paragraph too, e.g. Cloudflare's cache is vague.

Suggested change
The `Age` response header is a header returned from cache that specifies the time in seconds that an asset has been in Cloudflare's cache. This value resets if the asset is revalidated, purged, or evicted and then re-cached.
The `Age` response header is returned when Cloudflare serves a response from a local cache object on the data center handling the request. It is the number of seconds since that object was admitted or last revalidated at that cache. It resets if the asset is revalidated, purged, or evicted and then re-cached.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[AI] Thanks @foreseaz — applied in 7ebf4751. Per-data-center scope is more accurate, and it also lines up with the second paragraph of the callout (which describes Tiered Cache behavior between lower and upper tiers).

Clarify that Age is per-data-center scope: the seconds since the
serving data center's local cache admitted or last revalidated the
object, rather than the vaguer 'time in Cloudflare's cache'.

Applies @foreseaz's suggestion.
The `Age` response header is a header returned from cache that specifies the time in seconds that an asset has been in Cloudflare's cache. This value resets if the asset is revalidated, purged, or evicted and then re-cached.

The `Age` header is only present for responses served from the cache. It will not appear on a cache MISS, dynamic traffic, the first request that populates the lower tier HIT from tiered cache `CacheTieredFill=true` or any responses that did not originate from the cache (for example, responses generated by a Worker that bypassed the cache).
The `Age` header is only present for responses served from the cache. It will not appear on a cache MISS, dynamic traffic, the first request that populates a lower-tier data center's cache from an upper tier via [Tiered Cache](/cache/how-to/tiered-cache/), or any responses that did not originate from the cache (for example, responses generated by a Worker that bypassed the cache).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM on clarifying the tiered-cache case.
Optional follow-up: this note still underspecifies when Age appears. We only set it for pure local cache serves (HIT / STALE / UPDATING) — not REVALIDATED, EXPIRED, or BYPASS.

Suggest update:

Suggested change
The `Age` header is only present for responses served from the cache. It will not appear on a cache MISS, dynamic traffic, the first request that populates a lower-tier data center's cache from an upper tier via [Tiered Cache](/cache/how-to/tiered-cache/), or any responses that did not originate from the cache (for example, responses generated by a Worker that bypassed the cache).
`Age` is typically present for `HIT`, `STALE`, and `UPDATING`.
It is not present for:
- `MISS`, `EXPIRED`, `REVALIDATED`, `BYPASS`, `DYNAMIC`, and `NONE`/`UNKNOWN`
- the first request that populates a lower-tier data center's cache from an upper tier via [Tiered Cache](/cache/how-to/tiered-cache/) (even if `CF-Cache-Status` is `HIT`)
- responses that never hit cache (for example, a Worker that bypassed the cache)
After that lower-tier data center is populated, later local hits include `Age`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this suggestion needs another look from @zaidoon1 or other folks.

Common reasons the origin response is treated as not cacheable include:

* The response exceeds the [maximum cacheable file size](/cache/concepts/default-cache-behavior/#cacheable-size-limits) for your plan.
* The origin returned a `Cloudflare-CDN-Cache-Control` or `CDN-Cache-Control` header with a non-cacheable directive. Cloudflare evaluates these headers ahead of `Cache-Control`, in the precedence `Cloudflare-CDN-Cache-Control` > `CDN-Cache-Control` > `Cache-Control`. For example, an origin returning both `Cache-Control: public, max-age=3600` and `CDN-Cache-Control: no-store` produces `BYPASS`. Inspect every response header, not just `Cache-Control`. Refer to [CDN-Cache-Control](/cache/concepts/cdn-cache-control/) for the precedence rules.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good call, maybe we want to mention Surrogate-Control too.

Cloudflare-CDN-CC > CDN-CC > Surrogate-Control > Cache-Control

cc @zaidoon1

@foreseaz

foreseaz commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Both changes LGTM. Left some nits to make the existing change more clear, thanks for the effort @ngayerie

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

product:cache Issues or PRs related to Cache size/xs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants