DEV Community

Amit Feldman
Amit Feldman

Posted on

The 6 security headers your launch is probably missing — and the one-line fix for each

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'
Enter fullscreen mode Exit fullscreen mode

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=()' },
    ],
  }];
}
Enter fullscreen mode Exit fullscreen mode

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=()
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

The always matters — without it nginx drops the headers on error responses, which is exactly when you want them.

Two warnings from real launches

  1. HSTS includeSubDomains: only add it if every subdomain you own is HTTPS-ready. One maker locked himself out of a staging subdomain this way.
  2. 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)