Configure Nginx Security Headers for Grade A
HTTP Security Headers are response headers that instruct browsers how to handle content, providing protection against XSS, Clickjacking, MIME sniffing, and information leakage. They can be added to Nginx with just a few lines of configuration, but the security improvement is substantial.
Essential Security Headers
Create a separate snippet file to include in all sites:
sudo nano /etc/nginx/snippets/security-headers.conf
# Prevent Clickjacking attacks
add_header X-Frame-Options "SAMEORIGIN" always;
# Prevent MIME type sniffing
add_header X-Content-Type-Options "nosniff" always;
# XSS protection for older browsers
add_header X-XSS-Protection "1; mode=block" always;
# Force HTTPS (set this after obtaining SSL certificate)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# Control Referrer behavior
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Restrict browser features
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
# Hide Nginx version
server_tokens off;
# Include in server block of each site
sudo nano /etc/nginx/sites-available/example.com
server {
...
include snippets/security-headers.conf;
...
}
Content Security Policy (CSP)
CSP is the most complex header but provides the most comprehensive protection. It defines where the browser is allowed to load resources from:
# Basic CSP (start with report-only first)
add_header Content-Security-Policy-Report-Only
"default-src 'self';
script-src 'self' 'unsafe-inline' https://www.google-analytics.com;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
img-src 'self' data: https:;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self';
frame-ancestors 'self';" always;
Content-Security-Policy-Report-Only — this header instructs the browser to report violations to the console without actually blocking them. This lets you test whether CSP will break anything, then switch to the enforcing Content-Security-Policy header once you've confirmed the policy works.
Prevent Information Leakage
sudo nano /etc/nginx/nginx.conf
http {
# Hide Nginx version
server_tokens off;
# Hide PHP version from headers (also set in php.ini)
# expose_php = Off
}
# Remove X-Powered-By header sent by PHP
fastcgi_hide_header X-Powered-By;
proxy_hide_header X-Powered-By;
Verify Security Headers are Working Correctly
# Check headers being sent
curl -sI https://example.com | grep -iE "x-frame|x-content|strict-transport|referrer|permissions|x-xss"
# Test nginx configuration
sudo nginx -t
# Reload nginx
sudo systemctl reload nginx
# Test with Security Headers API (online)
curl -s "https://api.securityheaders.com/?q=https://example.com&hide=on" | python3 -m json.tool
Common Issues & Troubleshooting
CSP Blocking CSS/JavaScript from Loading
CSP blocks resources not in the whitelist. Check browser console for violations and add the domain to your policy:
# View CSP violations in Chrome DevTools Console
# Or add report-uri to receive reports
add_header Content-Security-Policy
"default-src 'self'; report-uri /csp-report" always;
HSTS Preventing HTTP Access
HSTS instructs the browser that this domain must use HTTPS only. Once set, the browser will refuse HTTP connections. You must have a valid SSL certificate first:
# To remove HSTS (wait for max-age to expire or clear browser cache)
# Change max-age to 0 and reload
add_header Strict-Transport-Security "max-age=0" always;
X-Frame-Options Breaking Self-Hosted Iframes
SAMEORIGIN allows iframes from the same domain. For cross-domain iframes, use CSP frame-ancestors instead:
# Use CSP frame-ancestors instead of X-Frame-Options
# (supports multiple domains and is more flexible)
add_header Content-Security-Policy "frame-ancestors 'self' https://trusted-partner.com" always;
Headers Disappearing in Location Blocks
In Nginx, if a location block has its own add_header directive, it overrides all parent headers. You must re-declare or use the always flag and include the snippet:
location /api/ {
include snippets/security-headers.conf; # Include again
add_header Cache-Control "no-store" always;
...
}
Frequently Asked Questions
How is HSTS Preload Different from Regular HSTS?
Regular HSTS: the browser remembers the policy after visiting. HSTS Preload: the domain is included in the browser's built-in preload list, forcing HTTPS on the very first visit. You must submit at hstspreload.org and include includeSubDomains; preload in the header.
Content-Security-Policy Seems Too Complex. Where Do I Start?
Start with default-src 'self' and check console errors to whitelist resources one by one. Or use Google's CSP Evaluator tool at csp-evaluator.withgoogle.com to audit your existing policy.
Do Security Headers Impact SEO?
Not directly on rankings. However, HTTPS (enforced by HSTS) is a Google ranking signal, and certain security headers help prevent your site from being blacklisted, which significantly impacts SEO.