Adding custom headers in Nginx standardizes HTTP responses for security, caching, and client behavior, keeping browser defaults predictable and reducing surprises across applications.
Response headers in Nginx are typically set with the add_header directive inside http, server, or location blocks. Without the always parameter, add_header only applies to successful (and some redirect) responses, so security headers can disappear exactly when an error page is served; always forces those headers onto error responses too.
Headers can also be injected by CDNs, load balancers, and upstream applications, and add_header adds rather than replaces, making duplicate header lines a common pitfall. Strict policies (notably Content-Security-Policy and Strict-Transport-Security) can break embeds, logins, or subdomains when rolled out with overly aggressive values, so changes belong in staging first and should be verified with real client requests before broad deployment.
Related: How to secure Nginx web server
Related: How to enable HSTS in Nginx
Steps to add custom headers in Nginx:
- Decide which response headers are required and define the exact values.
Common security defaults: X-Frame-Options: SAMEORIGIN, X-Content-Type-Options: nosniff, Referrer-Policy: strict-origin-when-cross-origin.
- Open the target virtual host configuration file in an editor.
$ sudoedit /etc/nginx/conf.d/example.conf
Common locations include /etc/nginx/conf.d and /etc/nginx/sites-available (often symlinked into /etc/nginx/sites-enabled).
- Add add_header directives in the relevant server (or location) block.
server { listen 80; server_name example.com; ##### snipped ##### 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; }add_header directives are inherited only when the child block defines no other add_header directives; if a location block adds one header, all required headers usually need to be repeated in that block.
Policies like Content-Security-Policy and Permissions-Policy can break embedded content, third-party scripts, and OAuth flows when values are too strict.
- Test the Nginx configuration for syntax errors.
$ 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 to apply changes without dropping established connections.
$ sudo systemctl reload nginx
On non-systemd systems, sudo nginx -s reload triggers a reload using the running master process.
- Verify headers on a normal response.
$ curl --head --silent http://127.0.0.1/ | 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
- Verify headers on an error response to confirm the always flag 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
Missing headers on 4xx/5xx responses usually means always is not present on the relevant add_header lines.
- Check for duplicate header lines when multiple layers inject the same name.
$ curl --head --silent http://127.0.0.1/ | grep -i '^x-frame-options:' X-Frame-Options: SAMEORIGIN X-Frame-Options: SAMEORIGIN
Repeated lines usually mean the header is set in more than one place; keep a single source of truth or hide upstream headers with proxy_hide_header/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.
Comment anonymously. Login not required.
