Security Headers Every Site Should Send
01 May 2026Four headers, ten minutes, and you’ve closed off a chunk of the low-effort attacks that scanners find automatically. None of this requires a redesign.
Check where you stand first
curl -sI https://yourdomain.com
Or run it through securityheaders.com for a graded report. Most sites that haven’t touched this score a D or F, and it’s almost always the same four missing headers.
1. Strict-Transport-Security (HSTS)
Tells browsers to never load your site over plain HTTP again, closing the window for a downgrade attack:
Strict-Transport-Security: max-age=31536000; includeSubDomains
Don’t add preload until you’re certain every subdomain also supports HTTPS — preload lists are painful to get removed from, and one HTTP-only subdomain breaks for everyone once you’re on the list.
2. Content-Security-Policy (CSP)
The most effective header against XSS, and the one most likely to break something if you rush it. Start narrow:
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'
This blocks inline <script> tags and any script loaded from a third-party domain you haven’t explicitly allowed. If you use Google Fonts, Stripe.js, or any analytics script, you’ll need to add their domains to script-src or connect-src or things silently stop working. Test in Content-Security-Policy-Report-Only mode first — it logs violations to the console without blocking anything, so you can see what you’d break before you break it.
3. X-Content-Type-Options
One line, no downside, always add it:
X-Content-Type-Options: nosniff
Stops browsers from guessing a file’s MIME type and executing an uploaded “image” as a script when your server’s Content-Type header was wrong or missing.
4. X-Frame-Options (or frame-ancestors in CSP)
Prevents your site being loaded in an invisible iframe for clickjacking:
X-Frame-Options: DENY
Or fold it into CSP, which is the modern equivalent and supersedes this header where both are present:
Content-Security-Policy: frame-ancestors 'none'
Use SAMEORIGIN instead of DENY if you embed your own pages in your own iframes anywhere.
Setting them in nginx
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'" always;
The always flag matters — without it, nginx skips the header on non-2xx/3xx responses, so your error pages ship without protection, which is exactly when an attacker is more likely to be probing.
What breaks, and how you’ll know
CSP is the one that bites. If a third-party widget stops rendering or your browser console fills with Refused to load the script ... because it violates the following Content Security Policy directive errors, that’s the header working as intended — add the specific domain to the relevant directive rather than loosening it to 'unsafe-inline' or *, which defeats the purpose.
The concrete next action
Run the curl command above right now. If Content-Security-Policy is missing entirely, that’s the biggest gap — start it in report-only mode today rather than shipping it blind.