correctness

Set the Vary: Accept header

Without Vary: Accept, CDNs and browser caches will happily serve the wrong representation to the wrong audience.

Vary: Accept is the single most-forgotten piece of content negotiation. Without it, any cache between you and your reader — a CDN, a browser, a corporate proxy — can serve the HTML version of a page to an agent, or the Markdown version to a human, depending only on whoever asked first.

What it does

Vary tells downstream caches: the representation you just cached depends on the value of this request header. Cache entries are keyed by the URL plus the listed headers.

HTTP/1.1 200 OK
Content-Type: text/markdown; charset=utf-8
Vary: Accept
Cache-Control: public, max-age=300

Now the CDN stores two separate cache entries for /article: one for Accept: text/markdown, another for Accept: text/html. Each reader gets the right bytes.

What it looks like when you forget

An agent requests your article, gets Markdown, and the CDN caches it keyed on URL alone. Ten seconds later a browser requests the same URL — and the CDN happily returns a text/markdown blob that renders as raw text in the browser. Or the reverse: the browser primes the cache with HTML, the agent gets a wall of <div> soup.

Gotchas

  • Vary: * disables caching entirely. You almost never want this.
  • Vary: Accept is case-insensitive as a header name, but the values you pass in Accept are matched literally by most CDNs. That’s why normalizing Accept on your origin is important (see the Accept parsing guide).
  • If you also vary by Accept-Encoding, list them together: Vary: Accept, Accept-Encoding.

Verify it

curl -sI -H "Accept: text/markdown" https://yoursite.com/article | grep -i vary

You should see Vary: Accept (possibly alongside other values). If it’s missing, your CDN is a timebomb.