If you ship websites or web apps, you want two things:
- Real security (not scanner theater)
- A setup you can copy-paste everywhere and only adapt when features change
This page gives you exactly that.
You’ll leave with:
- The only headers that actually matter
- Base values that work almost everywhere
- Clear rules for where each header belongs
- A simple way to test and verify
- No fluff, no outdated advice, no fear-based BS
The modern set (the only ones you need)
The current, no-doubt set is:
- Strict-Transport-Security (HSTS)
- Content-Security-Policy (CSP)
- X-Content-Type-Options
- Referrer-Policy
- Permissions-Policy
That’s it.
Everything else is:
- deprecated
- redundant
- or advanced/optional
Where each header should live
Rule you can keep forever:
- HSTS → Edge / CDN / platform
- Everything else → Application code
Why:
- Identical behavior across environments
- No forgotten dashboard toggles
- CSP stays versioned and intentional
Copy-paste: the Core 5 (base values)
Use this as your reference.
You only add domains to CSP when your site needs them.
# 1) HSTS (edge / CDN)
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
# 2) CSP (application code)
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-{{NONCE}}';
connect-src 'self';
img-src 'self' data: https:;
style-src 'self' 'unsafe-inline';
font-src 'self';
frame-src 'self';
media-src 'self';
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
# 3) MIME hardening
X-Content-Type-Options: nosniff
# 4) Referrer control
Referrer-Policy: strict-origin-when-cross-origin
# 5) Browser feature lockdown
Permissions-Policy:
camera=(),
microphone=(),
geolocation=(),
accelerometer=(),
gyroscope=(),
magnetometer=(),
usb=(),
payment=(),
fullscreen=(self)
1) HSTS — Strict-Transport-Security
What it does
Tells the browser:
“This site is HTTPS-only. Never use HTTP.”
After the browser learns this, it upgrades requests before they are sent.
Why redirects are not enough
Redirects still start on HTTP.
HSTS removes HTTP entirely.
Base value
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
max-age: how long the browser remembers (1 year is standard)includeSubDomains: applies to subdomainspreload: allows browser preload lists (only enable if you’re sure)
Where to set it
Set this at:
- Cloudflare
- CDN
- load balancer
Not in app code.
How to check HSTS
Open DevTools → Network → click the document request → Response headers.
If you see Strict-Transport-Security, it’s set.
Redirects (301/302/307) ≠ HSTS.
2) CSP — Content-Security-Policy
What it does
CSP tells the browser:
“Only load and execute what I explicitly allow.”
This is the strongest browser-level protection against:
- XSS
- script injection
- malicious third-party scripts
- data exfiltration
CSP is a living file
CSP changes when features change.
That’s normal.
Add a service → add its domain.
Remove a service → remove its domain.
Base CSP (recommended)
default-src 'self';
script-src 'self' 'nonce-{{NONCE}}';
connect-src 'self';
img-src 'self' data: https:;
style-src 'self' 'unsafe-inline';
font-src 'self';
frame-src 'self';
media-src 'self';
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
Why nonces?
Only scripts you explicitly mark can run.
This is modern, strong, and scalable.
Why unsafe-inline for styles?
Pragmatism. Many SSR setups rely on inline styles.
You can tighten this later if needed.
Real-world extensions
YouTube embeds
frame-src 'self' https://www.youtube.com https://www.youtube-nocookie.com;
Stripe
script-src 'self' 'nonce-{{NONCE}}' https://js.stripe.com;
connect-src 'self' https://api.stripe.com;
frame-src 'self' https://js.stripe.com https://hooks.stripe.com;
PostHog (EU)
script-src 'self' 'nonce-{{NONCE}}' https://eu.i.posthog.com https://eu-assets.i.posthog.com;
connect-src 'self' https://eu.i.posthog.com https://eu-assets.i.posthog.com;
Report-Only mode
Start with:
Content-Security-Policy-Report-Only: ...
Then enforce once clean.
3) X-Content-Type-Options
What it does
Stops browsers from guessing file types.
Prevents files being treated as scripts accidentally.
Value
X-Content-Type-Options: nosniff
Set-and-forget.
4) Referrer-Policy
What it does
Controls how much URL information is shared when users click links.
Best default
Referrer-Policy: strict-origin-when-cross-origin
- Same-site → full referrer
- Cross-site → origin only
- HTTPS → HTTP → nothing
5) Permissions-Policy
What it does
Disables powerful browser features unless you allow them.
Best default
Permissions-Policy:
camera=(),
microphone=(),
geolocation=(),
accelerometer=(),
gyroscope=(),
magnetometer=(),
usb=(),
payment=(),
fullscreen=(self)
Astro implementation (SSR)
Create src/middleware.ts:
import type { MiddlewareHandler } from "astro";
export const onRequest: MiddlewareHandler = async (context, next) => {
const nonce = crypto.randomUUID();
context.locals.nonce = nonce;
const response = await next();
response.headers.set(
"Content-Security-Policy",
[
"default-src 'self'",
`script-src 'self' 'nonce-${nonce}'`,
"connect-src 'self'",
"img-src 'self' data: https:",
"style-src 'self' 'unsafe-inline'",
"font-src 'self'",
"frame-src 'self'",
"media-src 'self'",
"object-src 'none'",
"base-uri 'self'",
"form-action 'self'",
"frame-ancestors 'none'",
].join("; ")
);
response.headers.set("X-Content-Type-Options", "nosniff");
response.headers.set("Referrer-Policy", "strict-origin-when-cross-origin");
response.headers.set(
"Permissions-Policy",
[
"camera=()",
"microphone=()",
"geolocation=()",
"accelerometer=()",
"gyroscope=()",
"magnetometer=()",
"usb=()",
"payment=()",
"fullscreen=(self)",
].join(", ")
);
return response;
};
Testing locally
curl -I http://localhost:4321
Or check DevTools → Network → document request.
Production verification
Use:
- securityheaders.com
- headerscan.com
- MDN Observatory
Scanners are secondary.
Browser Network tab is the source of truth.
Common confusion (read once)
Redirects ≠ HSTS
HSTS is a header, not a redirect.
Cloudflare adds headers sometimes
Do not rely on that. Set your policy in code.
Related (but not security headers)
Cookies
Use:
SecureHttpOnlySameSite=Lax
Set-Cookie: session=...; Secure; HttpOnly; SameSite=Lax; Path=/;
CORS
Access policy, not security.
Report-To / NEL
Telemetry, often injected by CDNs.
SRI
HTML attribute, not a header.
COOP / COEP / CORP
Advanced isolation. Skip unless you know exactly why you need them.
Final checklist
- HTTPS everywhere
- HSTS at edge
- CSP with nonces
- nosniff
- Referrer-Policy
- Permissions-Policy
- Verified in browser
You’re done.