Handling Vary: Origin Header Correctly
Resolves browser and CDN caching conflicts triggered by missing or misconfigured Vary: Origin headers during CORS preflight requests.
Key Troubleshooting Focus:
- Root cause of cached preflight failures across multiple requesting origins
- Impact on reverse proxy and CDN cache-key segmentation
- Framework-specific header injection and validation patterns
- Step-by-step cache isolation verification workflow
Understanding Vary: Origin in Preflight Caching
The Vary: Origin response header instructs HTTP caches to segment stored responses based on the requesting Origin header. Without it, caches serve a single preflight response globally.
Browser preflight caches follow RFC 7234 and the WHATWG Fetch Standard. They isolate OPTIONS responses per origin only when Vary: Origin is explicitly present.
CDNs generate cache keys using request headers. Dynamic origin reflection without Vary causes key collisions. The first cached Access-Control-Allow-Origin value serves all subsequent origins.
| Cache State | Vary Header |
Access-Control-Allow-Origin |
Result |
|---|---|---|---|
| Initial Request | Missing | https://app.example.com |
Cached globally |
| Subsequent Request | Missing | https://app.example.com (stale) |
CORS blocked |
| Initial Request | Origin |
https://app.example.com |
Cached per origin |
| Subsequent Request | Origin |
https://admin.example.com |
Cache hit (correct) |
Implementing a robust baseline requires aligning server-side logic with Server-Side CORS Configuration & Header Management before introducing edge caching layers.
Exact Console Errors & Root Cause Analysis
Misconfigured Vary headers manifest as intermittent CORS failures. The browser reports policy violations despite valid server-side logic.
Primary Console Error:
Access to XMLHttpRequest at 'https://api.example.com/data' from origin 'https://admin.example.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Root Cause Mapping:
- The CDN or browser preflight cache returns a stale
OPTIONSresponse. - The cached response contains
Access-Control-Allow-Origin: https://app.example.com. - The requesting origin (
https://admin.example.com) does not match. - The browser rejects the response as a cross-origin violation, not a network error.
Diagnostic Checklist:
- Open Chrome DevTools > Network tab.
- Filter by
PreflightorOPTIONS. - Inspect
Response HeadersforVary: Origin. - Check
Cache-ControlandAgeheaders to confirm stale delivery. - Verify
Access-Control-Allow-Originmatches the exact requesting origin.
Framework & Reverse Proxy Configuration
Correct implementation requires strict header ordering and conditional reflection. Middleware execution order dictates cache segmentation behavior.
Nginx Configuration
Place Vary directives before conditional Access-Control-* headers. Use always to ensure propagation on error responses.
server {
location /api/ {
if ($http_origin ~* ^https://([a-z0-9-]+\\.)?example\\.com$) {
set $cors_origin $http_origin;
}
add_header Vary Origin always;
add_header Access-Control-Allow-Origin $cors_origin always;
add_header Access-Control-Allow-Credentials true always;
if ($request_method = OPTIONS) { return 204; }
}
}
Express.js Middleware
Set Vary unconditionally before evaluating origin allowlists. Execution order prevents race conditions in async middleware chains.
app.use((req, res, next) => {
res.set('Vary', 'Origin');
if (allowedOrigins.includes(req.headers.origin)) {
res.set('Access-Control-Allow-Origin', req.headers.origin);
res.set('Access-Control-Allow-Credentials', 'true');
}
next();
});
Directive precedence and validation rules align with Access-Control-* Header Directives. Cloudflare Workers or Edge Rules must mirror this exact header injection sequence to prevent upstream stripping.
Step-by-Step Validation & Cache Bypass Testing
Verify cache segmentation by simulating concurrent preflight requests from distinct origins. Bypass intermediate caches during initial validation.
cURL Multi-Origin Preflight Simulation:
curl -I -X OPTIONS -H 'Origin: https://app.example.com' -H 'Access-Control-Request-Method: POST' https://api.example.com/data
curl -I -X OPTIONS -H 'Origin: https://admin.example.com' -H 'Access-Control-Request-Method: POST' https://api.example.com/data
DevTools Cache-Hit/Miss Verification:
- Disable cache in DevTools Network tab.
- Trigger preflight from Origin A. Verify
Vary: Originand correctACAO. - Re-enable cache. Trigger preflight from Origin B.
- Confirm
X-Cache: MISS(or equivalent CDN header) on the second request. - Validate that
ACAOreflects Origin B, not Origin A.
Header Order Validation:
Varymust appear beforeAccess-Control-Allow-Originin the response chain.- Reverse proxies often reorder headers during response normalization.
- Use
curl -voropenssl s_clientto inspect raw header transmission order.
Edge-Case Security Boundary Mapping
Improper Vary scoping introduces cache poisoning vectors in multi-tenant architectures. Strict validation prevents unauthorized origin reflection.
Origin Reflection Attack Mitigation:
- Never reflect
$http_originorreq.headers.originwithout allowlist validation. - Pair strict allowlists with
Vary: Originto segment cached responses. - Reject requests with malformed or null origins at the WAF layer.
Subdomain Credential Isolation:
Access-Control-Allow-Credentials: truerequires exact origin matching.- Dynamic origin allowlists must enforce TLD+1 boundaries.
- Isolate session cookies per subdomain to prevent cross-origin credential leakage.
Proxy-Level Audit Trail:
- Monitor load balancer logs for stripped
Varyheaders. - Track cache-key collisions using CDN analytics dashboards.
- Implement response header validation in CI/CD pipelines before deployment.
Common Mistakes
| Issue | Technical Impact | Resolution |
|---|---|---|
Omitting Vary: Origin with dynamic ACAO |
Global cache poisoning. First origin’s ACAO serves all subsequent requests. |
Add Vary: Origin to all dynamic CORS responses. |
Setting Vary: * to bypass caching |
Disables HTTP caching entirely. Increases origin latency and server load. | Use Vary: Origin for precise, standards-compliant segmentation. |
Reverse proxy stripping Vary |
CDN treats all preflights as identical keys. Cross-origin collisions occur. | Configure proxy pass-through rules to preserve Vary headers. |
FAQ
Does Vary: Origin work with Access-Control-Allow-Origin: *?
No. Wildcard origins bypass preflight caching entirely. Vary: Origin is only required when dynamically reflecting specific origins or using an allowlist.
How to prevent CDN cache poisoning via Origin reflection?
Validate the Origin header against a strict allowlist before reflection. Never reflect arbitrary origins, and always pair reflection with Vary: Origin.
Why does Vary: Origin cause 403 errors on cached preflight responses?
A cached response with an invalid or null Access-Control-Allow-Origin is served to a new origin. The browser blocks the request, interpreting it as a policy violation rather than a cache miss.