VibeSecurity

Comparison

Vercel vs Netlify security headers: defaults and setup

Security headers tell the browser how to treat your pages: force HTTPS, block framing, restrict scripts. Vercel and Netlify both let you set them, in different files and with different caveats. This page compares the documented behaviour without ranking the hosts.

By the VibeSecurity team3 min read

Documented behaviour side by side

Taken from each vendor's own documentation on the date above.
AspectVercelNetlify
Where custom headers goA headers array in vercel.json, with a source pattern and key and value pairs. Frameworks such as Next.js can also set headers in their own config.A _headers file in the publish directory, or [[headers]] tables in netlify.toml.
HTTPS redirectThe docs say HTTP requests are forwarded to HTTPS with a 308 and that this cannot be disabled.Confirm in the vendor's current documentation and test your own domain with curl.
Strict-Transport-SecurityDocumented as automatic: a preloaded value with includeSubDomains on .vercel.app, and max-age=63072000 on custom domains, which you can modify with custom headers.The HTTPS docs show adding the header yourself in _headers or netlify.toml for HSTS preload, and warn that preload is not easily reversible.
Content-Security-PolicyDocumented as something you define. The docs recommend starting with Content-Security-Policy-Report-Only.Set it as a custom header. Confirm any managed options in the vendor's current documentation.
Responses the rules coverConfirm in the vendor's current documentation how vercel.json headers interact with function and framework responses, then test.The docs say custom headers apply only to files served from Netlify's own store. Proxied content and URLs handled by a function or edge function, such as server-rendered pages, do not get them.
Per-environment headersConfirm in the vendor's current documentation.The docs say headers in _headers or netlify.toml are global for all builds and cannot be scoped to a branch or deploy context without a build step.

Setting the same baseline on each host

These two snippets set the same three headers. Add a Content-Security-Policy once you have tested it in report-only mode, because a wrong policy can break your own scripts.

vercel.json, then netlify.toml
{
  "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" }
      ]
    }
  ]
}

[[headers]]
  for = "/*"
  [headers.values]
    X-Content-Type-Options = "nosniff"
    X-Frame-Options = "DENY"
    Referrer-Policy = "strict-origin-when-cross-origin"

What Vercel leaves to you

  • Every header other than the documented HTTPS redirect and Strict-Transport-Security default, including Content-Security-Policy, framing protection and Referrer-Policy.
  • Deciding whether to extend HSTS to subdomains or preload on a custom domain. The documented custom-domain default covers only that host name.
  • Checking that API routes and server-rendered pages return the headers you expect, not only static files.
  • Building and testing a CSP. Vercel's docs describe CSP as a second line of defence and recommend report-only mode first.

What Netlify leaves to you

  • Adding every security header you want through _headers or netlify.toml.
  • Returning headers from your own function or server-rendered code, because the docs say custom header rules do not apply to those responses.
  • Handling headers on anything you proxy from another origin.
  • Being careful with HSTS preload. Netlify's docs warn it is hard to remove once set.

What to check whichever you pick

Run this against your own live domain and read the response headers. Repeat it for an API route and a server-rendered page.

  • Strict-Transport-Security is present on your custom domain.
  • Plain HTTP redirects to HTTPS.
  • X-Content-Type-Options is nosniff.
  • Framing is restricted with X-Frame-Options or a CSP frame-ancestors directive.
  • A Content-Security-Policy is present, or you have a report-only policy collecting data.
  • The same headers appear on dynamic routes, not only on static files.
Inspect response headers
curl -sI https://your-domain.example/ | grep -iE "strict-transport|content-security|x-frame|x-content-type|referrer-policy|permissions-policy"
curl -sI https://your-domain.example/api/health | grep -iE "strict-transport|content-security|x-content-type"
curl -sI http://your-domain.example/ | head -n 5

Frequently asked questions

Does Vercel add security headers automatically?

Vercel's docs describe an automatic HTTPS redirect and a default Strict-Transport-Security header. Other headers such as Content-Security-Policy are yours to configure in vercel.json or your framework config.

Why are my Netlify _headers not applied to some pages?

Netlify's docs say custom headers apply only to files served from its own store. Pages handled by a function or edge function, including server-rendered pages, and proxied content do not receive them, so the function must return the headers itself.

Which is better for security headers, Vercel or Netlify?

Both can serve the same headers. The practical difference is where you configure them and which responses the rules reach. Check the live response with curl on either host.

Should I enable HSTS preload?

Only once every subdomain works over HTTPS. Netlify's docs warn that the preload directive is not easily reversible and tells browsers that the domain and all its subdomains are served only over HTTPS.

Sources

  1. 1.Vercel docs: Encryption and TLS
  2. 2.Vercel docs: Content Security Policy
  3. 3.Vercel docs: Static configuration with vercel.json
  4. 4.Netlify docs: Custom headers
  5. 5.Netlify docs: HTTPS (SSL)
  6. 6.Next.js docs: headers in next.config.js
  7. 7.OWASP HTTP Headers Cheat Sheet
  8. 8.MDN: Content Security Policy