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 scoped incorrectly
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 misconfigured

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 and hardcoded add_header directives cause unpredictable header inheritance. The map directive evaluates $http_origin at request time. 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 {
  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";
    add_header Access-Control-Allow-Headers "Authorization, Content-Type";
    add_header Access-Control-Max-Age 86400;
    return 204;
  }
}

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 24 hours for security compliance
  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; to isolate cache per origin
Hardcoding multiple add_header directives in nested blocks Nginx only returns the last add_header in a given scope, causing header overwrites Use always flag and consolidate headers in server or root location block

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 verify cache purge.

How do I handle preflight caching with multiple origins?

Set Access-Control-Max-Age carefully. Ensure Vary: Origin is present so CDNs isolate cache per origin instead of sharing responses. Keep max-age under 86400 to allow rapid header rotation during deployments.

Can I use if blocks for CORS in Nginx?

Only for OPTIONS method handling. Use map for header assignment to avoid Nginx if directive scope pitfalls and unpredictable header overwrites. if blocks in Nginx operate at rewrite phase, not header injection phase.