Guides / Missing security headers on Vercel or Netlify: how to check and add them

last checked oct 2026

Missing security headers on Vercel or Netlify: how to check and add them

Security headers are a few lines of config that tell the browser to refuse things your app never meant to allow, like being framed by another site or loading scripts from anywhere. Vercel and Netlify won't add most of them for you.

The short answer

Run curl -sI https://your-app.com and look for Strict-Transport-Security, Content-Security-Policy, X-Frame-Options (or CSP frame-ancestors), X-Content-Type-Options: nosniff, Referrer-Policy and Permissions-Policy. Add the missing ones with a headers block in vercel.json or next.config.js on Vercel, or a _headers file on Netlify. Roll out CSP in report-only mode first so you don't break your own app.

Headers won't fix an open database or a leaked key. They're a cheap extra layer, and scanners and security reviewers check for them, so they're worth ten minutes.

How to check

curl -sI https://your-app.com | grep -iE \
  "strict-transport|content-security|x-frame|x-content-type|referrer-policy|permissions-policy"

Check a deep page too (for example /dashboard), not just the homepage, because header rules are matched by path. You can also see the response headers in DevTools → Network, by clicking the document request. Our free Leak Checker reports missing security headers as part of its passive check.

On Vercel you'll usually see strict-transport-security already there, since Vercel lists HSTS among its default response headers (max-age=63072000). The rest are up to you.

What each header does

How to fix it on Vercel

Any framework: vercel.json

Put this in the project root. source: "/(.*)" applies it to every path:

{
  "$schema": "https://openapi.vercel.sh/vercel.json",
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        { "key": "X-Content-Type-Options", "value": "nosniff" },
        { "key": "X-Frame-Options", "value": "DENY" },
        { "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" },
        { "key": "Permissions-Policy", "value": "camera=(), microphone=(), geolocation=()" },
        { "key": "Strict-Transport-Security", "value": "max-age=63072000" },
        { "key": "Content-Security-Policy-Report-Only",
          "value": "default-src 'self'; img-src 'self' data: https:; object-src 'none'; base-uri 'self'; frame-ancestors 'none'" }
      ]
    }
  ]
}

Next.js: next.config.js

This is the same idea, set by the framework, so it also applies when you run the app somewhere other than Vercel:

// next.config.js
const securityHeaders = [
  { key: 'X-Content-Type-Options', value: 'nosniff' },
  { key: 'X-Frame-Options', value: 'DENY' },
  { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
  { key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' },
]

module.exports = {
  async headers() {
    return [{ source: '/:path*', headers: securityHeaders }]
  },
}

If you need a strict CSP with nonces for inline scripts, Next.js has a dedicated Content Security Policy guide that uses middleware. That's worth following instead of hand-rolling one.

How to fix it on Netlify

Option A: a _headers file

The file has to end up in your publish directory, the folder Netlify deploys. That's usually dist/, build/ or out/. With Vite, putting it in public/ gets it copied there at build time.

/*
  X-Content-Type-Options: nosniff
  X-Frame-Options: DENY
  Referrer-Policy: strict-origin-when-cross-origin
  Permissions-Policy: camera=(), microphone=(), geolocation=()
  Strict-Transport-Security: max-age=31536000
  Content-Security-Policy-Report-Only: default-src 'self'; img-src 'self' data: https:; object-src 'none'; base-uri 'self'; frame-ancestors 'none'

Option B: netlify.toml

[[headers]]
  for = "/*"
  [headers.values]
    X-Content-Type-Options = "nosniff"
    X-Frame-Options = "DENY"
    Referrer-Policy = "strict-origin-when-cross-origin"
    Permissions-Policy = "camera=(), microphone=(), geolocation=()"

Netlify's custom headers only apply to files Netlify serves itself. Responses from functions, edge functions, SSR pages or proxied URLs don't get them, so set the headers in that code instead. Check an SSR route with curl after deploying.

Rolling out CSP without breaking your app

  1. Ship it as Content-Security-Policy-Report-Only first, as in the examples above. The browser logs violations in the console but blocks nothing.
  2. Click through every page, including login, checkout and anything with embeds, and add the domains you actually use. Typical ones are your Supabase project URL in connect-src and your payment provider in script-src and frame-src.
  3. When the console is quiet, rename the header to Content-Security-Policy to enforce it.

Then run the curl command again on the live site to confirm everything shows up.

next step

Check your live app for free

Our free Leak Checker looks at your deployed app the way any visitor's browser does and flags exposed secret keys, public source maps and missing security headers. Paste your app's URL to see which security headers it's missing, along with any exposed secret keys or public source maps.

If you'd rather have the whole app looked at, the $99 Scan + Report covers your repo and live app, including Supabase RLS, leaked keys and auth checks.

Sources

Every product step above was checked against these official pages. Last checked Oct 2026. Products change; if a menu moved, the docs below are the source of truth.