I run a passive launch-readiness scan on freshly launched products every day. Public response headers and homepage HTML only — exactly what any visitor's browser sees on first load.
Across the last two scan roundups I published: 15 of 18 launches shipped with no Content-Security-Policy. Most were missing 4+ of the six baseline headers. Not because the makers are careless — because nothing in the deploy pipeline fails when they're absent.
This is the fix-it post. Six headers, what each one actually does, and the copy-paste config for the four platforms most indie launches run on. Twenty minutes, start to green.
The six, in one table
| Header | What it stops |
|---|---|
Content-Security-Policy |
Injected scripts (XSS via compromised deps, ad tags, CDN assets) |
Strict-Transport-Security |
First-visit HTTP downgrade / SSL stripping |
X-Frame-Options (or CSP frame-ancestors) |
Clickjacking — your site framed inside someone else's |
X-Content-Type-Options: nosniff |
Browsers MIME-sniffing a uploaded file into executable content |
Referrer-Policy |
Full URLs (with tokens/params) leaking to every third-party request |
Permissions-Policy |
Camera/mic/geo being available to any script by default |
A sane starter CSP
Don't cargo-cult a huge policy. Start here and tighten:
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; base-uri 'none'; form-action 'self'; object-src 'none'
If you use third-party analytics or fonts, add exactly those origins — no wildcards. frame-ancestors 'none' makes X-Frame-Options redundant, but set both; older browsers only read the header.
Vercel (Next.js)
next.config.js:
async headers() {
return [{
source: '/(.*)',
headers: [
{ key: 'Content-Security-Policy', value: "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'none'; base-uri 'none'; object-src 'none'" },
{ key: 'Strict-Transport-Security', value: 'max-age=31536000; includeSubDomains' },
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
{ key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' },
],
}];
}
One caveat: Next.js inline scripts may need a nonce or 'unsafe-inline' in script-src while you migrate. Ship it in Content-Security-Policy-Report-Only mode for a day first if you're unsure — you'll see violations in the console without breaking anything.
Netlify
_headers file in your publish directory:
/*
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'none'; base-uri 'none'; object-src 'none'
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
Cloudflare (in front of anything)
Dashboard → Rules → Transform Rules → Modify Response Header → set static values for each of the six. Free plan covers it. This is the fix for most of the launches I scan — the app is behind Cloudflare already, and the headers are a five-minute ruleset, not a redeploy.
nginx
Inside your server block:
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'none'; base-uri 'none'; object-src 'none'" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
The always matters — without it nginx drops the headers on error responses, which is exactly when you want them.
Two warnings from real launches
-
HSTS
includeSubDomains: only add it if every subdomain you own is HTTPS-ready. One maker locked himself out of a staging subdomain this way. -
Test after deploying.
curl -sI https://yoursite.com— you want to see all six lines. I've watched makers "fix" headers that never shipped because the CDN in front stripped them.
Why I care
Last week I flagged a missing CSP in a launch's comment thread. By evening the maker had all six deployed — a few hours from "never thought about it" to a fully green re-scan. That's the norm, not the exception: these are config lines, not engineering projects.
If you'd rather have someone run the check and hand you the exact fix list: I do a Launch-Ready Quick Scan — passive headers/TLS/HTML checks plus a written readout of what to fix first, delivered within 24h. It's $29 during launch period. Grab one here.
But honestly, the configs above are the whole job for most sites. Steal them.
Top comments (0)