Configuring CORS in Nginx for Multiple Origins: Preflight Debugging & Exact Error Resolution

Direct resolution framework for Nginx CORS preflight failures when routing requests across multiple allowed origins. This guide maps exact browser console errors to configuration gaps, enforces dynamic origin validation via the map directive, and establishes strict credential synchronization boundaries.

Key Resolution Targets:

Diagnosing Preflight Failures & Console Errors

Browser preflight failures stem from mismatched header injection or missing Vary directives. Nginx must explicitly echo the requesting origin. Wildcard responses trigger immediate rejection when credentials are involved.

Console Error Mapping Table:

Browser Console Error Root Cause Nginx Configuration Gap
No 'Access-Control-Allow-Origin' header is present Header missing entirely add_header omitted or map block has empty default
The 'Access-Control-Allow-Origin' header contains multiple values Duplicate header injection Conflicting add_header in server and location blocks
The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include' Spec violation * used alongside Access-Control-Allow-Credentials: true
Response to preflight request doesn't pass access control check Missing OPTIONS handler if ($request_method = OPTIONS) block absent or missing Vary

Inspect the Origin request header in DevTools Network tab. Cross-reference it against your Nginx allowlist. Verify Vary: Origin presence to prevent reverse proxy cache collisions. For comprehensive directive syntax and precedence rules, reference Server-Side CORS Configuration & Header Management.

Dynamic Origin Validation with Nginx map Directive

Static if blocks for header assignment cause unpredictable behavior in Nginx because if runs in the rewrite phase, not the header injection phase. The map directive evaluates $http_origin at request time before location processing. It safely routes trusted domains while blocking unauthorized requests.

Implementation Rules:

map $http_origin $cors_origin {
  default "";
  ~^https://(app|admin|portal)\.example\.com$ $http_origin;
}

server {
  location /api/ {
    add_header Access-Control-Allow-Origin $cors_origin always;
    add_header Access-Control-Allow-Credentials true always;
    add_header Vary Origin always;

    if ($request_method = OPTIONS) {
      add_header Access-Control-Allow-Methods "GET, POST, OPTIONS" always;
      add_header Access-Control-Allow-Headers "Authorization, Content-Type" always;
      add_header Access-Control-Max-Age 600 always;
      return 204;
    }
    proxy_pass http://backend;
  }
}

This configuration demonstrates regex-based allowlist validation, conditional OPTIONS 204 response, mandatory Vary header isolation, and exact origin echo for credential compatibility. For advanced regex scaling and environment variable injection, review Dynamic Origin Validation Patterns.

Credential Sync & Preflight Header Alignment

Credentials require exact origin matching. Browsers strictly reject * when Access-Control-Allow-Credentials: true is present. Preflight responses must mirror client headers exactly.

Header Synchronization Checklist:

Preflight Debugging Command:

curl -I -X OPTIONS \
  -H "Origin: https://app.example.com" \
  -H "Access-Control-Request-Method: POST" \
  -H "Access-Control-Request-Headers: Authorization, Content-Type" \
  https://api.example.com

This command simulates browser preflight behavior. It verifies exact header echo, 204 status code, and absence of wildcard conflicts before frontend execution. Check the Access-Control-Allow-Headers output. Missing headers cause immediate 403 or TypeError in fetch clients.

Step-by-Step Validation & Edge-Case Security Boundaries

Verify configuration against the WHATWG Fetch Standard and W3C CORS specification. CDN edge caches aggressively store the first Origin response. Missing Vary: Origin breaks multi-origin routing.

Validation Workflow:

  1. Execute curl -I -X OPTIONS simulation to verify exact header echo
  2. Test cross-subdomain credential sync with identical top-level domains
  3. Audit Vary header isolation to prevent CDN cache poisoning across origins
  4. Confirm Access-Control-Max-Age does not exceed 600 for Chrome/Safari (Firefox supports up to 86400)
  5. Validate OPTIONS handler bypasses authentication middleware to avoid 401 preflight failures

Monitor DevTools Network tab for preflight status. Ensure 204 responses contain zero body payload. Validate that Access-Control-Expose-Headers only surfaces required response metadata.

Common Configuration Pitfalls

Issue Root Cause Resolution
Using wildcard * with Access-Control-Allow-Credentials: true Browsers strictly block the request due to security spec violation Replace * with dynamic $cors_origin echo from map directive
Omitting Vary: Origin header in multi-origin setups Reverse proxies and CDNs cache the first origin response, serving it to all subsequent requests Add add_header Vary Origin always; in the location block
Using add_header in nested blocks without always Nginx only inherits add_header directives from outer blocks when no add_header is in the inner block; always ensures delivery on all status codes Consolidate headers in the location block and always use always

Frequently Asked Questions

Why does my Nginx CORS config work for one domain but fail for another?

Missing Vary: Origin causes proxy cache poisoning. The CDN serves the first origin’s response to all subsequent requests. Alternatively, the regex in the map directive lacks the second domain pattern. Update the regex alternation group and purge the CDN cache.

How do I handle preflight caching with multiple origins?

Set Access-Control-Max-Age to 600 (the Chrome/Safari maximum). Ensure Vary: Origin is present so CDNs isolate cache per origin instead of sharing responses.

Can I use if blocks for CORS header assignment in Nginx?

Only for method routing (e.g., if ($request_method = OPTIONS)). Use map for header value assignment to avoid Nginx if directive scope pitfalls and unpredictable header overwrites. if blocks in Nginx operate at the rewrite phase, not the header injection phase, which leads to headers being ignored in some scenarios.