Security Headers Checklist: CSP, HSTS, and Modern Web Hardening
Most security reviews start with the same question: what does your site’s browser-facing security posture look like? One of the fastest, highest-signal answers comes from your HTTP response headers. They’re not “silver bullets”, but they do meaningfully reduce real-world risk—especially when combined with sane auth/session design and safe content handling.
This guide gives you:
- A repeatable audit workflow (copy/paste commands).
- A practical explanation of what each header does.
- Safe starter defaults and what can break.
- A decision checklist you can use in reviews.
If you’re doing a fast investigation on a suspicious site, start with Domain Lookup (DNS + basic security signals) and then use the commands below to verify headers directly.
Why security headers matter (in practice)
Headers help you limit damage from common web attack classes:
- Clickjacking: invisible overlays trick users into clicking buttons they didn’t intend to.
- XSS (cross-site scripting): attacker-controlled scripts run in your origin and steal sessions/data.
- Mixed content & downgrade risk: users get bounced to insecure HTTP or load resources insecurely.
- Privacy leakage: full URLs leak via referrers to third parties.
Even if you have perfect backend input validation, headers can still reduce blast radius when something slips through (or when third-party scripts misbehave).
Quick audit: grab headers in 10 seconds
Use curl to fetch response headers for your homepage (or any route):
curl -sSI https://example.com | sed -n '1,40p'
If you want to verify redirects + final destination headers:
curl -sSIL https://example.com | sed -n '1,80p'
Tip: test a few pages:
/(public landing)/account(auth-required pages)- any route that renders untrusted content (comments, user profiles, uploads)
The core header set (what to use and why)
1) Strict-Transport-Security (HSTS)
Goal: force HTTPS for your domain after the first secure visit.
Recommended baseline:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Notes:
- Don’t use
preloadunless you understand the operational impact (you’re committing to HTTPS everywhere, permanently enough to be on a preload list). - If you have any subdomain that cannot do HTTPS, don’t include
includeSubDomains.
2) Content-Security-Policy (CSP)
Goal: control where scripts/styles/images can load from and reduce XSS impact.
Start with Report-Only to avoid breaking production:
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'
Then move to enforced CSP:
Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'
Reality check:
- CSP is often the “hardest” header because real apps load analytics, fonts, CDNs, and embedded content.
- Resist
script-src 'unsafe-inline'andscript-src 'unsafe-eval'unless you absolutely must. If you have to allow inline scripts, prefer nonces.
3) Clickjacking: frame-ancestors / X-Frame-Options
Modern control is CSP:
Content-Security-Policy: ...; frame-ancestors 'none'
Legacy fallback:
X-Frame-Options: DENY
If you need embedding (e.g., your own subdomain embeds your app), use:
frame-ancestors 'self' https://trusted-embedder.example
4) X-Content-Type-Options
Prevents some MIME-sniffing behaviors:
X-Content-Type-Options: nosniff
5) Referrer-Policy
Avoid leaking full path/query strings to third-party sites:
Referrer-Policy: strict-origin-when-cross-origin
This is a sensible default for most sites.
6) Permissions-Policy
Disable browser features you don’t need:
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()
Only enable what you actually use.
“Modern isolation” headers (advanced, but worth knowing)
These are increasingly relevant for apps with sensitive data or complex JS:
Cross-Origin-Opener-Policy(COOP)Cross-Origin-Embedder-Policy(COEP)Cross-Origin-Resource-Policy(CORP)
They help enforce stronger cross-origin isolation, but they can break third-party embeds and some resource loading patterns. Treat these as an intentional hardening phase, not a default you blindly copy into every app.
Common mistakes (and how to avoid them)
Mistake: “Set CSP to * and call it done”
A CSP that effectively allows everything provides minimal protection. If your CSP contains broad wildcards and unsafe-inline, it’s often worse than no CSP because it creates false confidence.
Mistake: forgetting redirects and subdomains
Your security headers should be consistent across:
- The apex (
example.com) www.example.com- critical subdomains (auth, app, api)
Mistake: leaving framing enabled unintentionally
If your app is frameable by default, it’s clickjacking-friendly. Explicitly decide if you want embedding. Most products should default to not allowing it.
A practical review checklist
Use this when you’re auditing a site:
- HTTPS enforced? (check redirects)
- HSTS present and sane for your subdomain strategy
- CSP present (at least Report-Only) and improving over time
-
frame-ancestorsorX-Frame-Optionsconfigured intentionally -
X-Content-Type-Options: nosniff - Referrer policy limits leakage
- Permissions policy disables unused sensors/features
Want to combine headers with domain-level signals? Start with Domain Lookup (DNS + security hints), then use the Domain Performance page to understand the site’s technology footprint before you recommend fixes.
Final word: headers are a control layer, not a strategy
Security headers help you reduce exposure, but they don’t replace:
- server-side validation and output encoding
- safe auth/session handling
- dependency hygiene and patching
- logging + alerting (so you know when you’re under attack)
If you want a next step after headers, read: Phishing Triage Workflow.
Tools mentioned in this article
Run the same diagnostics to follow along with the guide.