Custom response headers let Nginx stamp every reply with security policy, cache instructions, routing hints, or integration metadata without changing application code. That is useful when a site needs consistent browser behavior, an operator needs proof of which layer served a request, or a proxy must attach a site-wide policy before the response leaves the server.
In Nginx, response headers are added with add_header name value [always]; inside the http, server, location, or if-inside-location contexts. By default the directive only applies to a defined set of success and redirect status codes, so headers can disappear on 404 and 500 responses unless the always parameter is used. Multiple add_header lines are allowed, and current upstream documentation also notes that newer builds can change inheritance behavior with add_header_inherit.
The clearest place to start is the target site's server block so the change stays scoped to one hostname or application. The examples use the common Debian and Ubuntu layout (/etc/nginx/sites-available/ and the nginx service name). If the same header already comes from the application, a CDN, or another proxy layer, keep one source of truth to avoid duplicate header lines. A current Ubuntu 24.04 package example (nginx/1.24.0) still uses the older inheritance model and does not support add_header_inherit yet.
Related: How to improve Nginx security
Related: How to enable HSTS in Nginx
Common site-wide security examples are X-Frame-Options: SAMEORIGIN, X-Content-Type-Options: nosniff, and Referrer-Policy: strict-origin-when-cross-origin.
$ sudoedit /etc/nginx/sites-available/example.com
Site configs are commonly stored under /etc/nginx/sites-available/ (symlinked into /etc/nginx/sites-enabled/) or /etc/nginx/conf.d/ depending on the distribution.
server {
listen 80;
listen [::]:80;
server_name example.com;
root /var/www/html;
location / {
try_files $uri $uri/ =404;
}
}
server {
listen 80;
listen [::]:80;
server_name example.com;
root /var/www/html;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
location / {
try_files $uri $uri/ =404;
}
}
Policies such as Content-Security-Policy, Permissions-Policy, and Strict-Transport-Security can break embeds, third-party scripts, OAuth flows, or subdomains when values are too strict. Validate them in staging before broad rollout.
location /api/ {
add_header Content-Security-Policy "default-src 'self'" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
try_files $uri =404;
}
If a nested block defines any add_header directive, older builds stop inheriting headers from the parent block. When nginx -t reports unknown directive "add_header_inherit", repeat the required headers manually in the child block.
Current upstream Nginx documentation says add_header_inherit merge; became available in version 1.29.3 and appends parent headers to the current block instead of replacing them.
$ sudo nginx -t nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful
$ sudo systemctl reload nginx
On systems that do not use systemd, sudo nginx -s reload asks the running master process to reload the configuration.
$ curl --head --silent http://127.0.0.1/index.html | grep -iE '^(HTTP/|x-frame-options:|x-content-type-options:|referrer-policy:)' HTTP/1.1 200 OK X-Frame-Options: SAMEORIGIN X-Content-Type-Options: nosniff Referrer-Policy: strict-origin-when-cross-origin
Use the real site URL instead of 127.0.0.1 when a reverse proxy, load balancer, or CDN sits in front of Nginx and the final client response matters more than the local origin response.
$ curl --head --silent http://127.0.0.1/does-not-exist | grep -iE '^(HTTP/|x-frame-options:|x-content-type-options:|referrer-policy:)' HTTP/1.1 404 Not Found X-Frame-Options: SAMEORIGIN X-Content-Type-Options: nosniff Referrer-Policy: strict-origin-when-cross-origin
If the headers appear on the 200 OK response but disappear on the 404 response, the relevant add_header lines are probably missing the always parameter.
$ curl --head --silent http://127.0.0.1/index.html | grep -i '^x-frame-options:' X-Frame-Options: SAMEORIGIN
Repeated lines usually mean the same header is being set in more than one layer. Keep one authoritative source or hide upstream copies with proxy_hide_header or fastcgi_hide_header where appropriate.