Nginx add_header Inheritance: Solving the Mystery of Missing Security Headers

intermediate Nginx2026-07-01| Any Linux distribution (Ubuntu, CentOS, Debian) running Nginx (all versions).

Error Message

X-Frame-Options/Content-Security-Policy header không xuất hiện trong response dù đã khai báo ở server block
#nginx#web-security#devops#sysadmin

The Vanishing Header Mystery

You just finished a security audit, and the results are frustrating. The report claims your production site is missing X-Frame-Options and Content-Security-Policy (CSP) headers. You specifically remember adding these to your server block in nginx.conf. When you test the homepage, everything looks perfect. However, as soon as you hit an API endpoint like /api/v1/status, those critical security headers simply vanish.

This specific error—X-Frame-Options/Content-Security-Policy header không xuất hiện trong response dù đã khai báo ở server block—is a common trap for Nginx users.

The Debug Process

Avoid relying on browser developer tools, as caches can lie to you. Instead, use curl to see exactly what the server is sending. Run a test against your root domain and then compare it to a specific sub-path.

# Headers are likely here
curl -I https://example.com/

# Headers often disappear here
curl -I https://example.com/api/data

If the first command shows your headers but the second one doesn't, your configuration probably looks like this snippet:

server {
    listen 443 ssl;
    add_header X-Frame-Options "SAMEORIGIN";
    add_header Content-Security-Policy "default-src 'self';";

    location /api/ {
        # This single line breaks everything above
        add_header X-API-Version "1.0";
        proxy_pass http://backend;
    }
}

In this scenario, the /api/ response will show the X-API-Version. Unfortunately, Nginx will silently drop the X-Frame-Options and CSP headers for that specific path.

Why Nginx Behaves This Way

Most Nginx directives flow down from parent to child seamlessly. But add_header behaves like a jealous sibling. If a location block defines even one add_header, it ignores every add_header defined in the server or http blocks above it.

Nginx doesn't merge these header lists. It overwrites them entirely. This design choice catches even veteran engineers off guard during routine deployments.

The Fix: Two Proven Methods

Method 1: The "Include" Strategy (Best Practice)

Don't copy-paste headers into every block. Instead, keep your configuration DRY (Don't Repeat Yourself) by using a partial config file. This ensures consistency across your entire site.

  1. Create a file at /etc/nginx/conf.d/security_headers.conf:
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Content-Security-Policy "default-src 'self';" always;
  1. Reference this file in your server block and any location block that needs extra headers:
server {
    include conf.d/security_headers.conf;

    location /api/ {
        include conf.d/security_headers.conf;
        add_header X-API-Version "1.0" always;
        proxy_pass http://backend;
    }
}

Method 2: Using the headers_more Module

If you use OpenResty or can install custom modules, headers_more is a life-saver. Its more_set_headers directive is much smarter than the default. It inherits from parents even if the child block adds its own headers.

server {
    more_set_headers "X-Frame-Options: SAMEORIGIN";

    location /api/ {
        more_set_headers "X-API-Version: 1.0";
        proxy_pass http://backend;
    }
}

With this module, both headers will appear in your response without any extra include lines.

Verification: Confirming the Fix

Before you walk away, always validate your syntax. A single missing semicolon can take down your site.

nginx -t

If the test passes, reload the service to apply changes:

systemctl reload nginx

Finally, verify the specific API path again. You should now see the full stack of headers in your terminal output.

Pro-Tips for Nginx Headers

- **The "always" flag is mandatory:** By default, Nginx only sends headers for successful responses like 200 or 302. Adding `always` ensures your security headers stay active during 404 or 500 errors.
- **Inheritance is all-or-nothing:** Remember that adding one header in a child block wipes the slate clean.
- **Centralize your security:** Use `include` files to manage security settings. This prevents human error when you add new endpoints six months from now.

Related Error Notes