Skip to content

_headers: warn that comma-joining a singleton header like Cache-Control produces conflicting directives #32979

Description

@oddharsh

Which page

https://developers.cloudflare.com/workers/static-assets/headers/

What the page gets right

The combining behaviour is documented, clearly:

An incoming request which matches multiple rules' URL patterns will inherit all rules' headers.

If a header is applied twice in the _headers file, the values are joined with a comma separator.

What's missing is a warning about one consequence of that behaviour.

The gap

The worked example on that page joins X-Robots-Tag, where a comma-separated list is exactly what the header means. Readers reasonably generalise from it.

For a singleton header the same join produces something no browser can act on sensibly. Adding a longer cache lifetime to one file under an existing glob:

/lwe/quadgrams.txt
  Cache-Control: public, max-age=604800, stale-while-revalidate=604800

/lwe/*
  Cache-Control: public, max-age=0, s-maxage=86400

puts this on the wire (reproduced on wrangler 4.125.0, wrangler dev --local):

Cache-Control: public, max-age=604800, stale-while-revalidate=604800, public, max-age=0, s-maxage=86400

Two max-age values in one field. RFC 9111 leaves that ambiguous, so implementations resolve it however they like, and the result is worse than the single revalidation the narrower rule was meant to save.

Why it's easy to miss

Nothing errors, the asset still serves, and a browser still caches it somehow. curl -sI shows it only if you read the whole line instead of grepping for the directive you just added.

The other thing readers assume, because every other layered-config system in Workers behaves this way, is that the more specific rule wins. Wrangler's routes, CSP maps, and run_worker_first all let the specific entry take precedence. _headers is the exception.

Suggested addition

A short warning after the "joined with a comma separator" line, saying that joining works for list-valued headers like X-Robots-Tag and produces conflicting directives for singleton headers like Cache-Control.

It's worth stating the remedy in the same breath, because there isn't an obvious one: a file under a glob can only get a different Cache-Control by moving out of the glob's path or by setting the header in the Worker. Adding a narrower rule is not one of the options.

Metadata

Metadata

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions