A second Content-Security-Policy header is not a stronger one
27 July 2026 · 4 min read
Two CSP headers do not override each other. The browser enforces both, your new allowed origin never takes effect, and nothing in the response looks wrong.
Someone adds an allowed origin to the application's Content-Security-Policy, deploys, and the resource is still blocked. The header is right there in the response. The origin is in it. The browser blocks it anyway.
The reason is that there are two headers. nginx add_header appends - it does not replace - so a policy set in the server config and a policy set in the application both arrive. A browser given two CSP headers enforces the INTERSECTION of them. The new origin is allowed by one policy and absent from the other, so it is not allowed.
One owner, and a check that says so
Pick the layer that owns the header and remove it everywhere else. Application middleware is usually the right owner: it can vary the policy per route, and it lives with the code that decides what a page loads.
Then make the duplicate detectable without a browser:
curl -sSI https://example.com | grep -ci content-security-policy
That has to print 1. Not >= 1. Put it in whatever checks the deployment, because the failure mode here is silent, and the symptom - a resource blocked for no visible reason - sends people looking at the policy string instead of at how many of them there are.
The same trap, elsewhere
It is not specific to CSP. Any header a proxy can append and an application can also set has the same shape: Strict-Transport-Security, X-Frame-Options, Cache-Control. The rule is one owner per header, and the check is a count, not a grep for the value.