Cybersecurity / Networking

CORS Header Access-Control-Allow-Origin Contains Multiple Values, Invalid Error

· by DebuggedIt

Quick answer

When a browser reports Access-Control-Allow-Origin contains multiple values, more than one layer in your request's path is independently adding this header —...

When a browser reports Access-Control-Allow-Origin contains multiple values, more than one layer in your request's path is independently adding this header — and with several possible layers (application code, web server, reverse proxy, CDN) each potentially configured with their own CORS handling, finding all of them requires checking each layer systematically rather than assuming the first one you fix is the only one.

The Problem

A cross-origin request 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.

Browsers require this specific header to have exactly one value — even if every duplicate happens to be identical, as in the example above, the browser still rejects the response outright, since a response with a duplicated header is itself considered malformed for this specific purpose.

Every possible layer to check Application code Web server config Reverse proxy CDN/WAF Any TWO of these adding the header = duplicate error

Why It Happens

Modern web architectures commonly stack several layers between the actual application and the end user, and any one of these layers might have its own, independently configured CORS handling:

  • Application-level CORS middleware (in your backend framework) sets the header, unaware that a layer further along the request's path is also going to add it
  • The web server (Nginx, Apache) has its own add_header-style CORS configuration, layered on top of what the application itself already sets
  • A reverse proxy or API gateway sitting between the web server and the public internet adds its own CORS headers, independently of both the application and web server layers
  • A CDN or WAF (Web Application Firewall) service has its own separate CORS configuration section in its own dashboard, entirely unaware of and unrelated to whatever your origin infrastructure is already doing

The Fix

Systematically test each layer independently to identify exactly which ones are actually adding the header, starting from the origin (your application) and working outward toward the public-facing edge:

# Test directly against your application server, bypassing every proxy/CDN layer
curl -I -H "Origin: https://app.example.com" http://localhost:3000/data

# Test against your web server/reverse proxy, still bypassing any CDN
curl -I -H "Origin: https://app.example.com" https://origin-server-ip/data

# Test against the full public path, including any CDN/WAF
curl -I -H "Origin: https://app.example.com" https://api.example.com/data

Comparing the Access-Control-Allow-Origin header count at each of these three points reveals exactly where the duplication is being introduced — if the origin server test shows one value, but the full public path shows two, the CDN/WAF layer is confirmed as adding a second one.

Once you've identified every layer independently adding the header, decide on exactly one layer that should own CORS handling, and remove the corresponding configuration from every other layer — this is the actual fix, not adjusting the specific value at any one layer, since the core issue is duplication across layers, not an incorrect value at any single one.

For application-level and web-server-level duplication specifically, this typically means choosing between handling CORS in your backend framework's middleware, or in your web server's configuration, but not both simultaneously for the same requests.

For CDN/WAF-level duplication, check that specific platform's own dashboard or configuration for its independent CORS settings section — this is genuinely a separate configuration surface from your origin server, and needs to be explicitly disabled or aligned there specifically, since no amount of origin-server configuration change affects what the CDN itself independently adds.

After removing duplicate configuration from every extra layer, re-run the same three-point test to confirm exactly one layer now adds the header, with a single, correct value at the final, full public-path test:

curl -I -H "Origin: https://app.example.com" https://api.example.com/data
# Access-Control-Allow-Origin should now appear exactly once

Still Not Working?

If you've confirmed only one layer explicitly adds the header in your own configuration, but duplication still appears in the actual response, check whether that single layer's own configuration is itself somehow applying the same directive twice — a web server configuration with the same add_header directive present in both a server block and an included, separately-loaded configuration file (common when configuration is split across multiple files with an include directive) can produce this exact symptom from what looks, at first glance, like a single, correctly-configured layer.