- Home
- HTTP Headers
- Vary
Header
Vary
Learn how the Vary header specifies which request headers affect the response. Essential for proper cache differentiation and content negotiation.
TL;DR: Tells caches which request headers affect the response, creating separate cache entries for different header values. Essential for content negotiation and CDN caching.
What is Vary?
Vary tells caches (browsers, CDNs, proxies) which request headers were used to generate the response. It’s like a recipe ingredient list - it says “this response was made using these specific request headers, so cache different versions for different header values.”
This ensures users get the right version of cached content based on their specific request characteristics.
How It Works
1. Server generates response based on request headers:
GET /api/data HTTP/1.1
Accept-Language: es
User-Agent: Mobile Browser
HTTP/1.1 200 OK
Vary: Accept-Language, User-Agent
Content-Type: application/json
{"message": "Hola", "layout": "mobile"}
```text
**2. Cache stores response** with key including `Accept-Language` and `User-Agent`.
**3. Different request gets different cached version:**
```http
GET /api/data HTTP/1.1
Accept-Language: en
User-Agent: Desktop Browser
# Cache miss - different Accept-Language value
# Server generates new response for English desktop users
Common Use Cases
Content Negotiation
Different content based on client preferences:
GET /api/users HTTP/1.1
Accept: application/json
Accept-Language: fr
HTTP/1.1 200 OK
Vary: Accept, Accept-Language
Content-Type: application/json
{"message": "Bonjour", "users": [...]}
```text
### Mobile vs Desktop
Different layouts for different devices:
```http
GET /page.html HTTP/1.1
User-Agent: Mozilla/5.0 (iPhone...)
HTTP/1.1 200 OK
Vary: User-Agent
Content-Type: text/html
<html class="mobile">...</html>
Compression Support
Different encoding based on client support:
GET /style.css HTTP/1.1
Accept-Encoding: gzip, br
HTTP/1.1 200 OK
Vary: Accept-Encoding
Content-Encoding: br
[Brotli-compressed CSS]
```text
### Authentication State
Different content for logged-in users:
```http
GET /dashboard HTTP/1.1
Authorization: Bearer token123
HTTP/1.1 200 OK
Vary: Authorization
Content-Type: text/html
<html>Welcome back, John!</html>
Multiple Headers
List multiple headers separated by commas:
Vary: Accept-Language, Accept-Encoding, User-Agent
```text
This creates separate cache entries for each combination of these header values.
## Vary: \*
Special value meaning "response varies on all headers":
```http
Vary: *
This effectively disables caching because every request is considered unique. Use sparingly!
Real-World Examples
API with Multiple Formats
GET /api/users HTTP/1.1
Accept: application/xml
Accept-Language: de
HTTP/1.1 200 OK
Vary: Accept, Accept-Language
Content-Type: application/xml
<?xml version="1.0"?>
<users>
<message>Hallo</message>
<user>...</user>
</users>
```text
### Responsive Images
```http
GET /hero-image HTTP/1.1
User-Agent: Mozilla/5.0 (iPhone...)
Accept: image/webp
HTTP/1.1 200 OK
Vary: User-Agent, Accept
Content-Type: image/webp
[Mobile-optimized WebP image]
Personalized Content
GET /recommendations HTTP/1.1
Cookie: userId=123; preferences=sports
HTTP/1.1 200 OK
Vary: Cookie
Content-Type: application/json
{"recommendations": ["Football News", "Basketball Scores"]}
```text
### CORS Responses
```http
GET /api/data HTTP/1.1
Origin: https://app.example.com
HTTP/1.1 200 OK
Vary: Origin
Access-Control-Allow-Origin: https://app.example.com
Content-Type: application/json
{"data": "..."}
Cache Efficiency
Good Vary Usage
Limited, predictable header values:
Vary: Accept-Language # Usually 2-5 languages
Vary: Accept-Encoding # Usually 2-3 encodings
```text
### Problematic Vary Usage
Headers with many unique values:
```http
❌ Vary: User-Agent # Thousands of different values
❌ Vary: Cookie # Unique per user
❌ Vary: Authorization # Unique per user
These create too many cache variations, reducing cache hit rates.
Best Practices
1. Only vary on headers that actually affect the response:
✅ Vary: Accept-Language (if response changes by language)
❌ Vary: Accept-Language (if response is always English)
```text
**2. Use specific headers, not broad ones:**
```http
✅ Vary: Accept-Encoding
❌ Vary: User-Agent (unless truly necessary)
3. Consider cache efficiency:
# Good: 2-3 languages × 2-3 encodings = 6-9 cache entries
Vary: Accept-Language, Accept-Encoding
# Bad: Thousands of user agents × languages = too many entries
Vary: User-Agent, Accept-Language
```text
**4. Use Vary with CORS:**
```http
# When Access-Control-Allow-Origin varies by Origin
Vary: Origin
5. Document your Vary strategy:
// Cache different versions for:
// - Language (en, es, fr)
// - Encoding (gzip, br, identity)
// - Format (json, xml)
app.get('/api/data', (req, res) => {
res.set('Vary', 'Accept-Language, Accept-Encoding, Accept')
// ... generate response based on these headers
})
```javascript
## Server Implementation
### Express.js
```javascript
app.get('/api/users', (req, res) => {
const lang = req.headers['accept-language'] || 'en'
const format = req.headers['accept'] || 'application/json'
// Set Vary header
res.set('Vary', 'Accept-Language, Accept')
// Generate response based on headers
if (format.includes('xml')) {
res.type('xml')
res.send(generateXMLResponse(lang))
} else {
res.json(generateJSONResponse(lang))
}
})
Nginx
# Vary on encoding for compressed responses
location ~* \.(css|js)$ {
add_header Vary "Accept-Encoding";
gzip_vary on;
}
# Vary on user agent for mobile detection
location / {
set $mobile "";
if ($http_user_agent ~* "(Mobile|Android|iPhone)") {
set $mobile "mobile";
}
if ($mobile) {
add_header Vary "User-Agent";
try_files $uri $uri/mobile.html $uri/index.html;
}
}
```text
## Common Mistakes
### Forgetting Vary with Content Negotiation
```http
❌ Missing Vary header:
GET /api/data HTTP/1.1
Accept: application/xml
HTTP/1.1 200 OK
Content-Type: application/xml
# Missing: Vary: Accept
✅ With proper Vary:
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/xml
Over-Varying
❌ Too many variations:
Vary: User-Agent, Cookie, Authorization, X-Custom-Header
✅ Minimal necessary variations:
Vary: Accept-Language, Accept-Encoding
```text
### Inconsistent Vary Headers
```http
❌ Sometimes includes header, sometimes doesn't:
Response 1: Vary: Accept-Language
Response 2: Vary: Accept-Language, Accept-Encoding
✅ Consistent across all responses:
Always: Vary: Accept-Language, Accept-Encoding
Related Headers
- Cache-Control - Caching directives that work with Vary
- Accept - Client’s content type preferences
- Accept-Language - Client’s language preferences
- Accept-Encoding - Client’s compression preferences
- User-Agent - Client identification (use carefully with Vary)
Frequently Asked Questions
What is the Vary header?
The Vary header tells caches which request headers affect the response. Caches store different versions based on these headers, like separate cached copies for gzip and non-gzip clients.
Why use Vary: Accept-Encoding?
It tells caches to store separate versions for different encodings. Without it, a cache might serve a gzip response to a client that cannot decompress it.
What does Vary: * mean?
Vary: * means every request is unique and the response should not be cached. It effectively disables caching for that response.
How does Vary affect CDN caching?
CDNs use Vary headers to create cache keys. Too many Vary headers reduce cache hit rates. Only vary on headers that actually change the response.