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:

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:

Diagnostic Checklist:

  1. Open Chrome DevTools > Network tab.
  2. Filter by Preflight or OPTIONS.
  3. Inspect Response Headers for Vary: Origin.
  4. Check Cache-Control and Age headers to confirm stale delivery.
  5. Verify Access-Control-Allow-Origin matches 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:

  1. Disable cache in DevTools Network tab.
  2. Trigger preflight from Origin A. Verify Vary: Origin and correct ACAO.
  3. Re-enable cache. Trigger preflight from Origin B.
  4. Confirm X-Cache: MISS (or equivalent CDN header) on the second request.
  5. Validate that ACAO reflects Origin B, not Origin A.

Header Order Validation:

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:

Subdomain Credential Isolation:

Proxy-Level Audit Trail:

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.