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:
- Identify exact browser console errors (e.g.,
No Access-Control-Allow-Origin header is present on the requested resource) - Replace wildcard
*with dynamicmapdirective validation to prevent reverse proxy cache poisoning - Ensure
Access-Control-Allow-Credentialsaligns strictly with exact origin echo - Validate preflight
OPTIONSresponse headers step-by-step usingcurland browser DevTools
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:
- Use regex anchors (
^and$) to prevent substring spoofing - Set
default ""to return an empty string for untrusted origins - Normalize protocol and trailing slashes before matching
- Scale allowlists using environment-scoped variables
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:
- Mirror
Access-Control-Request-HeadersinAccess-Control-Allow-Headers - Validate
Access-Control-Request-MethodagainstAccess-Control-Allow-Methods - Debug
Access-Control-Max-Agecaching side effects on header updates - Ensure
OPTIONSreturns204 No ContentbeforeGET/POSTexecution
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:
- Execute
curl -I -X OPTIONSsimulation to verify exact header echo - Test cross-subdomain credential sync with identical top-level domains
- Audit
Varyheader isolation to prevent CDN cache poisoning across origins - Confirm
Access-Control-Max-Agedoes not exceed 24 hours for security compliance - Validate
OPTIONShandler bypasses authentication middleware to avoid401preflight 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.