Nginx CORS Header "Multiple Values" Received Error
The browser error about receiving multiple values for a CORS header means both Nginx and your backend application are independently setting the same Access-Control-Allow-Origin header, and the two values get combined into a single, invalid multi-value header that browsers correctly reject as ambiguous.
The Problem
A request that should succeed with CORS fails, and the browser console shows:
Access to fetch at 'https://api.example.com/data' from origin 'https://app.example.com'
has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header contains
multiple values 'https://app.example.com, https://app.example.com', but only one is allowed.
Notice the same origin value appears twice, which is the specific tell that two different layers are each independently adding the same header rather than one of them having an outright wrong value.
Why It Happens
Both Nginx (via add_header directives) and many application frameworks have their own built-in or manually configured CORS handling, and when both are active simultaneously for the same request, both add their own copy of the same response header. HTTP allows a header to appear multiple times in some cases, but browsers specifically require Access-Control-Allow-Origin to have exactly one value, and reject the response outright if it sees more than one. This happens when:
- Nginx is configured with CORS headers via
add_headerfor a proxy location, while the backend application also has its own CORS middleware enabled and doing the same thing independently - Nginx's
add_headeris applied at multiple nested levels (both aserverblock and alocationblock, for example), and due to Nginx's inheritance rules foradd_header, both end up contributing rather than the more specific one cleanly overriding the other - A CDN or another proxy layer in front of Nginx also adds its own CORS headers, stacking with whatever Nginx and the backend already add
The Fix
Decide on exactly one layer that should be responsible for CORS headers, and remove the corresponding logic from every other layer — having CORS handled in more than one place is the core issue to resolve, not a specific value to correct.
If you want Nginx to be the single source of truth, remove CORS-related middleware or configuration from your backend application entirely, and configure it only in Nginx:
location /api/ {
add_header 'Access-Control-Allow-Origin' 'https://app.example.com' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type' always;
proxy_pass http://backend;
}
Alternatively, if you'd rather keep CORS logic in your application code (often preferable, since it's usually easier to make dynamic — like reflecting different allowed origins per environment — in application code than in Nginx config), remove any add_header CORS directives from Nginx entirely for that location, letting the backend's response pass through unmodified.
If you're using nested server and location blocks with add_header at both levels, remember Nginx's specific inheritance rule: if a location block defines its own add_header directives at all, it does NOT inherit any from the parent server block — but this only applies if the location block has at least one add_header of its own; if it has none, it does inherit the parent's. This inconsistent inheritance behavior is a common source of unexpected duplicate or missing headers when directives are spread across multiple nested blocks without full awareness of this rule:
# If both of these exist, understand exactly which one actually applies
# based on the inheritance rule above, rather than assuming both simply stack
server {
add_header 'Access-Control-Allow-Origin' 'https://app.example.com' always;
location /api/ {
add_header 'X-Custom-Header' 'value' always;
# This location now does NOT inherit the server-level CORS header above,
# since it defines its own add_header directive
}
}
Still Not Working?
If you've consolidated CORS handling to a single layer within your own infrastructure but the duplicate header issue persists, check whether a CDN sitting in front of everything is also independently injecting its own CORS headers — many CDN platforms have their own separate CORS configuration section, distinct from and unaware of whatever your origin server (Nginx or your application) is already sending, requiring you to either disable the CDN's own CORS handling or make it the single source of truth instead, removing the logic from your origin server layer.