HSTS on Vercel is one of those settings that looks trivial right up until you lock yourself out of a subdomain for a year.
If you deploy on Vercel, you already get HTTPS by default. That solves transport encryption. HSTS solves a different problem: making browsers refuse plain HTTP for your domain after they’ve seen your policy once.
That sounds great, and usually it is. But HSTS is also sticky, cached aggressively by browsers, and very easy to over-apply. I’ve seen teams flip on includeSubDomains without thinking through preview apps, legacy subdomains, or weird internal tools hanging off the same parent domain.
So here’s the real comparison guide: when HSTS on Vercel is worth it, when it’s risky, and how to set it up without creating your own outage.
What HSTS actually does
HSTS stands for HTTP Strict Transport Security. It’s sent as a response header:
Strict-Transport-Security: max-age=31536000; includeSubDomains
Once a browser sees that over HTTPS, it remembers:
- always use HTTPS for this host
- never allow the user to click through certificate errors for this host
- optionally apply the rule to all subdomains if
includeSubDomainsis present
That means HSTS helps block:
- downgrade attempts from HTTPS to HTTP
- accidental insecure links like
http://example.com - some SSL stripping attacks on first-party revisits
What it does not do:
- magically secure your app logic
- fix mixed content you already ship
- protect the very first visit unless you use preload
- replace redirects, CSP, or sane cookie settings
Why HSTS fits Vercel well
Vercel is actually a pretty good environment for HSTS because the platform already enforces a lot of the plumbing you want:
- automatic TLS
- managed certificates
- straightforward custom domain support
- easy header configuration through app config
That lowers the operational cost of HSTS. On an old self-managed stack, I’d worry more about cert renewal drift and random edge cases. On Vercel, the baseline is cleaner.
For most production sites on a stable domain, HSTS is a solid default.
The main options on Vercel
You generally have a few ways to send HSTS on Vercel deployments:
- set headers in
vercel.json - set headers in
next.config.jsornext.config.mjsfor Next.js - inject headers in middleware or application code
My strong preference: configure it statically when possible.
Static config is easier to audit, easier to reason about, and less likely to disappear during a refactor.
Option 1: vercel.json
This works well for many Vercel apps:
{
"headers": [
{
"source": "/(.*)",
"headers": [
{
"key": "Strict-Transport-Security",
"value": "max-age=31536000; includeSubDomains"
}
]
}
]
}
Option 2: Next.js headers()
If you’re running Next.js, this is usually the cleanest app-level option:
/** @type {import('next').NextConfig} */
const nextConfig = {
async headers() {
return [
{
source: '/(.*)',
headers: [
{
key: 'Strict-Transport-Security',
value: 'max-age=31536000; includeSubDomains',
},
],
},
]
},
}
export default nextConfig
Official docs for Next.js headers are here: https://nextjs.org/docs/app/api-reference/config/next-config-js/headers
For Vercel project configuration, see: https://vercel.com/docs/project-configuration
Pros of enabling HSTS on Vercel
1. It closes the HTTP back door
Even if you redirect HTTP to HTTPS, there’s still a moment where the browser may attempt plain HTTP first. HSTS removes that on repeat visits.
For user-facing production domains, that’s exactly what you want.
2. It hardens cookie security in practice
If your site depends on secure cookies, OAuth callbacks, login state, or account pages, forcing the browser to stay on HTTPS reduces accidental insecure requests.
It’s not a replacement for Secure cookies, but it complements them well.
3. It’s low-maintenance on a managed platform
This is the big Vercel-specific advantage. Since Vercel handles certificates and HTTPS delivery cleanly, HSTS is less scary than it is on hand-rolled infrastructure.
You still need to think, but you don’t need to babysit the transport layer.
4. It helps enforce good domain hygiene
Once you commit to HSTS, you stop treating HTTP as a fallback. That pressure is healthy. It forces teams to clean up old links, old redirects, and weird assumptions.
I like that. Security headers should push architecture in the right direction.
Cons of enabling HSTS on Vercel
1. Browser caching makes mistakes painful
This is the big one.
If you ship:
Strict-Transport-Security: max-age=31536000
you’ve told browsers to remember that policy for a year.
If you break TLS, move traffic badly, or realize you shouldn’t have applied HSTS to that domain, users may keep getting forced to HTTPS until the cache expires.
This is why I never start with a 1-year policy on a fresh domain setup.
2. includeSubDomains can break things you forgot existed
This is where teams get burned.
If your main site is example.com and you send:
Strict-Transport-Security: max-age=31536000; includeSubDomains
then browsers will enforce HTTPS for:
www.example.comapi.example.comold.example.comstatus.example.com- every forgotten subdomain someone created three years ago
On Vercel, your production app might be fine while some unrelated subdomain outside Vercel is not. HSTS doesn’t care who owns the infrastructure. It applies to the whole domain tree.
3. Preview and non-production hostname strategy matters
Vercel preview URLs use Vercel-owned domains, so your own apex HSTS policy won’t automatically spill into those. That’s good.
But if your team uses custom preview subdomains like:
staging.example.comqa.example.comfeature-x.example.com
then includeSubDomains absolutely affects them.
If those environments aren’t consistently HTTPS-clean, don’t enable broad HSTS yet.
4. Preload is even less forgiving
If you go beyond normal HSTS and aim for preload, you’re asking browsers to hardcode HTTPS for your domain family.
That’s powerful and, frankly, easy to romanticize. But preload is not where you start. Preload is where you end up after you’ve proven your domain setup is boring and stable.
For preload, the header usually looks like:
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
I would not recommend this for most teams until they’ve lived happily with standard HSTS for a while.
Best rollout strategy for Vercel
This is the part that saves you from regret.
Phase 1: start small
Ship this first:
Strict-Transport-Security: max-age=300
Five minutes. That’s it.
Confirm:
- the header appears on HTTPS responses
- HTTP redirects still behave as expected
- no broken subdomain assumptions exist
- certs are healthy everywhere you care about
You can verify your headers with browser devtools or run a quick scan at https://headertest.com.
Phase 2: increase duration
Then move to something like:
Strict-Transport-Security: max-age=86400
Then:
Strict-Transport-Security: max-age=2592000
Then eventually:
Strict-Transport-Security: max-age=31536000
I like gradual rollouts because browser cache behavior is unforgiving. Being conservative here is not cowardice; it’s competence.
Phase 3: decide on includeSubDomains
Only add it if you can answer yes to this:
- Are all current subdomains HTTPS-only and certificate-valid?
- Are future subdomains controlled enough that this won’t become a surprise?
- Are there no legacy tools, old mail panels, forgotten DNS entries, or vendor-hosted subdomains hanging around?
If the answer is “probably,” that means “no.”
Recommended configurations
Safer baseline for a production domain
const nextConfig = {
async headers() {
return [
{
source: '/(.*)',
headers: [
{
key: 'Strict-Transport-Security',
value: 'max-age=31536000',
},
],
},
]
},
}
export default nextConfig
Stronger policy for a well-managed domain family
const nextConfig = {
async headers() {
return [
{
source: '/(.*)',
headers: [
{
key: 'Strict-Transport-Security',
value: 'max-age=31536000; includeSubDomains',
},
],
},
]
},
}
export default nextConfig
Preload-ready policy only for mature setups
const nextConfig = {
async headers() {
return [
{
source: '/(.*)',
headers: [
{
key: 'Strict-Transport-Security',
value: 'max-age=63072000; includeSubDomains; preload',
},
],
},
]
},
}
export default nextConfig
If you’re considering preload, read the official HSTS reference first: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security
My opinionated take
For Vercel production deployments, HSTS is usually a yes.
For includeSubDomains, it’s a “slow down and prove it.”
For preload, it’s a “not unless your domain operations are boring enough that nobody has to think about them.”
That’s the real comparison:
- HSTS without subdomains: strong default, low drama
- HSTS with
includeSubDomains: stronger, but requires domain discipline - HSTS preload: strongest, highest commitment, least reversible
If your site is a normal production app on Vercel, I’d enable HSTS. I’d start with a short max-age, increase it gradually, and only expand scope once I knew every subdomain was under control.
That approach is less flashy than dropping a massive preload header on day one, but it’s the one that avoids emergency Slack threads later.