Documented behaviour side by side
| Aspect | Vercel | Netlify |
|---|---|---|
| Where custom headers go | A 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 redirect | The 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-Security | Documented 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-Policy | Documented 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 cover | Confirm 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 headers | Confirm 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.
{
"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.
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 5Frequently 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.