Nginx add_header vs proxy_hide_header explained

Direct Answer: Execution Context & Header Flow

Exact Configuration & Diagnostic Commands

curl -sI -X GET https://target-domain.com/api/v1 | grep -iE '(strict|content-security|x-frame|x-content-type|server|x-powered)'
tail -f /var/log/nginx/access.log | grep -E 'upstream_response_time'

Combine with a custom log_format using $upstream_http_x_powered_by to verify upstream header stripping at the log level.

server {
 listen 443 ssl;
 server_name app.yourdomain.com;

 # Strip upstream metadata before response generation
 proxy_hide_header X-Powered-By;
 proxy_hide_header Server;
 proxy_hide_header X-AspNet-Version;
 proxy_hide_header X-AspNetMvc-Version;

 location / {
 proxy_pass http://backend_upstream;
 proxy_set_header Host $host;
 proxy_set_header X-Real-IP $remote_addr;

 # Inject hardened security headers (always flag covers 4xx/5xx)
 add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
 add_header X-Content-Type-Options "nosniff" always;
 add_header X-Frame-Options "SAMEORIGIN" always;
 add_header Referrer-Policy "strict-origin-when-cross-origin" always;
 }
}

Verification & Testing Pipeline

  1. Baseline Check: Run curl -I https://target-domain.com/endpoint. Confirm stripped headers are absent and injected headers are present.
  2. Inheritance Override Test: Add a nested location /static/ block containing a single add_header. Request /static/asset.css and verify that parent security headers disappear from the response.
  3. Scope Validation: Execute nginx -T | grep -E '(add_header|proxy_hide_header)' to audit compiled configuration scope and detect accidental overrides.
  4. Error Response Validation: Temporarily stop the upstream service to trigger 502/504 errors. Verify the always flag preserves injected security headers on error responses.

Edge Cases, Conflicts & Rollback Procedures