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.

Scan your Nginx site for missing headers โ†’

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"
📚 HttpFixer Blog โ€” fix guides, explainers, and references โ†’