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
Steps to add custom response headers in Nginx:
- Decide which headers and values should be sent.
Common site-wide security examples are X-Frame-Options: SAMEORIGIN, X-Content-Type-Options: nosniff, and Referrer-Policy: strict-origin-when-cross-origin.
- Open the Nginx site configuration file for the host that should send the headers.
$ 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.
- Locate the server block that serves the target site.
server { listen 80; listen [::]:80; server_name example.com; root /var/www/html; location / { try_files $uri $uri/ =404; } } - Add the required add_header directives inside that scope.
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; } }- add_header can be repeated for each header that should be sent.
- always keeps the header on locally generated non-success responses such as 404 Not Found and 500 Internal Server Error instead of limiting it to the default success and redirect status codes.
- If the header should apply to every virtual host, place the directives higher in the http block instead of repeating them in each server block.
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.
- Repeat required headers in nested location blocks that add their own headers, or use add_header_inherit merge; on Nginx 1.29.3 and newer builds.
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.
- Test the Nginx configuration before reloading it.
$ sudo nginx -t nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful
- Reload Nginx so new worker processes pick up the header rules.
$ sudo systemctl reload nginx
On systems that do not use systemd, sudo nginx -s reload asks the running master process to reload the configuration.
- Verify the headers on a normal response.
$ 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.
- Verify the headers on an error response to confirm the always behavior.
$ 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.
- Check for duplicate header lines after the change.
$ 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.
Mohd Shakir Zakaria is a cloud architect with deep roots in software development and open-source advocacy. Certified in AWS, Red Hat, VMware, ITIL, and Linux, he specializes in designing and managing robust cloud and on-premises infrastructures.
