Skip to content

Vary Header

DodaTech 2 min read

title: "Vary Header — Content Negotiation and Cache Variants" description: "The Vary response header tells caches to store multiple versions of a response based on request headers like Accept, Accept-Encoding, and Accept-Language." date: 2026-06-28 lastmod: 2026-06-28 weight: 16 tags: [apis, caching] }

The Vary header instructs caches to key stored responses by specified request headers, ensuring different clients receive the correct content variant.

What You'll Learn

  • How Vary works with content negotiation
  • Common Vary header values
  • Cache key generation with Vary

Why It Matters

Without Vary, a cache might serve a gzipped response to a client that doesn't support gzip, or an English response to a French client.

Code Examples

# Vary header for content negotiation
@app.route('/api/users')
def get_users():
    response = jsonify(get_users_data())

    # Vary by Accept-Encoding (compression)
    response.headers['Vary'] = 'Accept-Encoding'
    response.headers['Cache-Control'] = 'public, max-age=3600'
    return response

# Vary by multiple headers
@app.route('/api/user/profile')
def user_profile():
    response = jsonify(profile_data())

    # Vary by language and encoding
    response.headers['Vary'] = 'Accept-Language, Accept-Encoding'
    response.headers['Cache-Control'] = 'public, max-age=3600'
    return response

# Vary by Authorization (different cached per user)
@app.route('/api/dashboard')
def dashboard():
    response = jsonify(generate_dashboard())

    # Each user gets their own cache entry
    response.headers['Vary'] = 'Authorization'
    response.headers['Cache-Control'] = 'private, max-age=60'
    return response
// Express with Vary header
app.get('/api/articles', (req, res) => {
  const lang = req.headers['accept-language']?.split(',')[0] || 'en';
  const articles = getArticles(lang);

  res.set({
    'Vary': 'Accept-Language, Accept-Encoding',
    'Cache-Control': 'public, max-age=3600'
  });
  res.json(articles);
});
# Example Vary response
HTTP/1.1 200 OK
Content-Type: application/json
Content-Encoding: gzip
Vary: Accept-Encoding, Accept-Language
Cache-Control: public, max-age=3600

# Cache stores separate entries for:
# /api/users + Accept-Encoding: gzip + Accept-Language: en
# /api/users + Accept-Encoding: gzip + Accept-Language: fr
# /api/users + Accept-Encoding: identity + Accept-Language: en

Common Mistakes

1. Missing Vary: Accept-Encoding

CDNs and proxies may serve wrong compression variant.

2. Overly Broad Vary Headers

Varying by * or many headers fragments the cache.

3. Not Including Authorization for Private Data

Without Vary: Authorization, one user's data may leak to another.

4. Vary on User-Agent

User-Agent has too many values, fragmenting cache. Use device detection on the server.

5. Vary Without Corresponding Logic

If you Vary by a header, your server must actually respect it.

Practice Questions

  1. What problem does Vary solve?
  2. What header values should you Vary by for compression?
  3. Why is Vary: User-Agent problematic?
  4. How does Vary affect cache hit ratio?
  5. Can you Vary by custom headers?

Answers:

  1. Serving correct content variants to different clients.
  2. Accept-Encoding.
  3. Too many unique User-Agent values drastically reduce cache hits.
  4. More Vary headers = fewer cache hits (cache fragmentation).
  5. Yes: Vary: X-API-Version for versioned APIs.

Challenge: Implement a multi-language API with proper Vary headers. Show how the cache stores separate entries for each language.

FAQ

Does Vary work with CDNs?

: Most CDNs respect Vary. AWS CloudFront and Fastly handle it well.

What is Vary: *?

: Wildcard means the response varies on all request headers. Avoid it.

How many Vary headers are too many?

: Keep it to 1-3 headers. More than 3 usually fragments the cache too much.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro