Fix Missing Security Headers on Nginx
Last updated: April 2026
Add these add_header directives to your Nginx server block. Use always to ensure headers are sent on error responses as well as successful ones.
Full security headers block โ paste into your server block
server {
listen 443 ssl;
server_name yoursite.com;
# HSTS โ start with short max-age, increase after testing
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# Clickjacking protection
add_header X-Frame-Options "SAMEORIGIN" always;
# MIME sniffing protection
add_header X-Content-Type-Options "nosniff" always;
# Referrer policy
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Permissions policy โ disable unused browser features
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
# Content Security Policy โ adjust sources to match your site
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; frame-ancestors 'none'; object-src 'none'" always;
}
HSTS โ ramp up safely
Start with a short max-age to verify HTTPS works on all routes before committing to a long duration:
# Step 1 โ test with 5 minutes add_header Strict-Transport-Security "max-age=300" always; # Step 2 โ after confirming HTTPS works, increase to 1 year add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; # Step 3 โ add preload only after all subdomains support HTTPS add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
X-Frame-Options
# Block all framing (recommended for login pages) add_header X-Frame-Options "DENY" always; # Allow same-origin framing only add_header X-Frame-Options "SAMEORIGIN" always;
Content Security Policy โ common patterns
# Strict โ no inline scripts (most secure) add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; frame-ancestors 'none'; object-src 'none'" always; # With Google Fonts add_header Content-Security-Policy "default-src 'self'; style-src 'self' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; frame-ancestors 'none'; object-src 'none'" always; # With inline styles (less secure but common) add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; frame-ancestors 'none'; object-src 'none'" always;
Apply headers to a specific location only
location /api/ {
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
proxy_pass http://backend;
}
โ
add_header directives in a child block (like location) override all add_header directives in the parent block. If you add headers in both server and location, repeat all headers in the location block.Validate and reload
# Validate config syntax nginx -t # Reload without downtime nginx -s reload # Verify headers on your live URL curl -sI https://yoursite.com | grep -iE "strict|x-frame|x-content|referrer|permissions|content-security"