Published on

Nginx add_header

In **Nginx**, `add_header` is used to **add HTTP response headers** (for security, caching, CORS, debugging, etc.).

Below is a **clear, practical guide** with common pitfalls and best practices.

---

## Basic syntax

```nginx
add_header <header-name> <value> [always];

Example:

add_header X-Custom-Header "Hello World";

This adds to the response headers sent to the client.


⚠️ Important rule (most people get this wrong)

add_header only applies to successful responses (2xx, 3xx) by default

That means 404 / 500 / 401 responses will NOT include the header unless you use always.

add_header X-Frame-Options "DENY" always;

Scope & inheritance (VERY important)

add_header does NOT inherit the way you might expect.

Rule

  • If any add_header exists at a lower level, all parent add_headers are ignored

Hierarchy:

http
 └── server
      └── location

❌ Common mistake

server {
    add_header X-A "A";

    location / {
        add_header X-B "B";
    }
}

Result in /:

  • X-A is missing
  • ✅ Only X-B exists

✅ Correct way

server {
    add_header X-A "A";

    location / {
        add_header X-A "A";
        add_header X-B "B";
    }
}

Common real-world examples

1. Security headers

add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'" always;

2. CORS headers

add_header Access-Control-Allow-Origin "*" always;
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS" always;
add_header Access-Control-Allow-Headers "Authorization, Content-Type" always;

For OPTIONS preflight:

if ($request_method = OPTIONS) {
    add_header Content-Length 0;
    add_header Content-Type text/plain;
    return 204;
}

3. Disable caching

add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate" always;
add_header Pragma "no-cache" always;
add_header Expires "0" always;

4. Debug / inspection header

add_header X-Served-By $hostname always;

Overriding or removing headers

Replace a header

Just redefine it:

add_header Cache-Control "max-age=3600" always;

Remove headers (needs headers_more module)

more_clear_headers Server;
more_clear_headers X-Powered-By;

proxy_set_headeradd_header

This is a very common confusion:

DirectivePurpose
add_headerAdds headers to response sent to client
proxy_set_headerSets headers sent to upstream server

Example:

proxy_set_header X-Forwarded-For $remote_addr;

⬆️ This does not affect client response headers.


Quick checklist (best practice)

✅ Always use always ✅ Repeat headers at each level where you use add_header ✅ Put global headers at server or http level ❌ Don’t assume inheritance ❌ Don’t confuse with proxy_set_header