Writing
One add_header in a location Block Deleted Every Security Header on That Page
Inheritance applies only if the current level has no add_header at all, so a single cache directive removed four security headers.
Writing
Inheritance applies only if the current level has no add_header at all, so a single cache directive removed four security headers.
Notes
The config looked right. nginx reloaded without complaint. And the login page of my own site was the only page on it serving no security headers at all.
At the server level I had the usual set: Content-Security-Policy, X-Frame-Options, Referrer-Policy, Permissions-Policy. Then, in the location block for the login page, one line:
Reasonable thing to want. A page behind a password should not be cached. What I did not know is that adding it removed the other four from that page.
The nginx documentation says it plainly:
"These directives are inherited from the previous configuration level if and only if there are no add_header directives defined on the current level."
Read that again with the word "any" in mind. It is not per header. One anywhere in the block and the entire inherited set is gone. There is no merge.
The name is the trap. reads like an addition, and at the level you are looking at, it is. Across levels it behaves like a replacement of the whole set.
Nothing errors. passes, because the config is valid. The page renders normally. The only way to see it is to read the headers coming back from the live server, and most people check headers once when they set them up, at the root, on the homepage.
In my case the page that lost its headers was the password page, which is the page you would pick if you were choosing one to protect.
Avoid inside entirely. When the value needs to vary by path, produce the value at the server level with a map and keep a single directive:
then one at the server level. The parameter, added in 1.7.5, makes the header apply regardless of response code, which matters because by default only fires for a specific list of codes and your 403 and 500 responses go out bare.
For content type, use rather than . It sets the type without touching header inheritance.
Count the headers on the live response, per path, not per config file:
That should return four. Run it against every path that has its own location block, especially the ones you added later for one small reason. The small reason is exactly how this happens: nobody adds a location block for a login page and thinks about the security headers they are silently dropping.
More