HTTP Strict Transport Security sounds simple: send one response header and browsers stop using HTTP for your site.
That’s true, but HSTS is one of those headers that can quietly break things if you turn it on too aggressively. I’ve seen teams copy a preload-ready header from a blog post, deploy it, and then discover some forgotten subdomain is still serving plain HTTP. Now users can’t reach it at all.
If you’re hosting on Firebase Hosting, the good news is the setup is straightforward. The tricky part is choosing the right policy and rolling it out without locking yourself into a bad configuration.
What HSTS actually does
HSTS tells browsers:
- always use HTTPS for this host
- optionally apply the rule to all subdomains
- optionally allow the domain to be included in browser preload lists
A typical header looks like this:
Strict-Transport-Security: max-age=31536000; includeSubDomains
That means:
max-age=31536000: remember the rule for 1 yearincludeSubDomains: apply it towww,app,api, and every other subdomain too
Once a browser sees that header over HTTPS, future HTTP requests get upgraded to HTTPS before the request is even sent.
That matters because redirects from HTTP to HTTPS are not enough on their own. Without HSTS, the first request can still be intercepted or downgraded on hostile networks.
What Firebase Hosting gives you by default
Firebase Hosting already serves your hosted site over HTTPS and provisions certificates for your connected domains. That solves transport encryption.
HSTS is separate. Firebase does not automatically force a strict browser-side HSTS policy for your domain. You need to add the header yourself in firebase.json.
Official docs for custom headers live here:
https://firebase.google.com/docs/hosting/full-config#headers
Basic HSTS setup in Firebase Hosting
Firebase Hosting lets you set response headers by path pattern. The simplest starting point is to add HSTS for all responses.
Here’s a practical firebase.json example:
{
"hosting": {
"public": "public",
"headers": [
{
"source": "**",
"headers": [
{
"key": "Strict-Transport-Security",
"value": "max-age=86400"
}
]
}
]
}
}
This sets HSTS for every hosted response with a max-age of 1 day.
Deploy it with:
firebase deploy --only hosting
I like starting with 86400 seconds during rollout. One day is long enough to verify browsers are learning the policy, but short enough that a mistake won’t haunt you for months.
Why you should not start with preload
A lot of examples jump straight to this:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
That header is valid, but it’s not a beginner setting.
preload is basically you telling browser vendors: “hardcode my domain as HTTPS-only.” That can be a great end state, but only if:
- your apex domain works perfectly on HTTPS
wwwworks perfectly on HTTPS- every subdomain is HTTPS-ready if you use
includeSubDomains - you understand this is hard to undo
If you have old DNS entries, forgotten staging subdomains, email-related subdomains, or legacy services outside Firebase, preload can turn into a cleanup project you didn’t plan for.
My advice: treat preload as a final step, not a checkbox.
A safer rollout plan
Here’s the rollout I usually recommend.
Phase 1: low max-age
Start with 1 day:
{
"hosting": {
"headers": [
{
"source": "**",
"headers": [
{
"key": "Strict-Transport-Security",
"value": "max-age=86400"
}
]
}
]
}
}
Watch for problems:
- mixed content issues
- hardcoded
http://links - subdomains that still depend on HTTP
- backend redirects doing weird protocol handling
Phase 2: raise max-age
If everything looks good, move to something like 6 months or 1 year:
{
"hosting": {
"headers": [
{
"source": "**",
"headers": [
{
"key": "Strict-Transport-Security",
"value": "max-age=31536000"
}
]
}
]
}
}
Phase 3: add includeSubDomains
Only do this when you’re sure every subdomain is HTTPS-safe.
{
"hosting": {
"headers": [
{
"source": "**",
"headers": [
{
"key": "Strict-Transport-Security",
"value": "max-age=31536000; includeSubDomains"
}
]
}
]
}
}
Phase 4: consider preload
Only after you’ve run with includeSubDomains successfully and verified your entire domain space is ready.
{
"hosting": {
"headers": [
{
"source": "**",
"headers": [
{
"key": "Strict-Transport-Security",
"value": "max-age=31536000; includeSubDomains; preload"
}
]
}
]
}
}
Full Firebase Hosting config example
Here’s a more realistic config with HSTS and a few other useful security headers:
{
"hosting": {
"public": "dist",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
],
"headers": [
{
"source": "**",
"headers": [
{
"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": "X-Frame-Options",
"value": "DENY"
}
]
}
],
"rewrites": [
{
"source": "**",
"destination": "/index.html"
}
]
}
}
If you want to scan your deployed headers quickly, run your site through Headertest.
How to verify HSTS is actually working
After deployment, check the response headers directly.
Using curl:
curl -I https://your-domain.com
You should see something like:
HTTP/2 200
strict-transport-security: max-age=31536000; includeSubDomains
You can also inspect the Network tab in browser devtools and look at the response headers for the main document.
If you don’t see the header:
- make sure you deployed the correct Firebase project
- make sure your
headersrule matches the requested path - make sure you’re checking an HTTPS response, not HTTP
HSTS headers sent over plain HTTP are ignored by browsers. That’s intentional.
Common mistakes on Firebase Hosting
1. Enabling includeSubDomains too early
This is the classic mistake.
Your main site may be on Firebase Hosting, but your DNS probably points other subdomains to other systems. HSTS doesn’t care where they’re hosted. If the browser learns includeSubDomains, every subdomain must behave over HTTPS.
Inventory your subdomains first.
2. Using preload without understanding the commitment
Preload is not “extra secure mode.” It’s a long-term browser-level commitment. If you’re not fully sure, skip it.
3. Setting HSTS only on one route
If you only apply the header to /, but users land on /pricing or /app, the browser may never learn the policy.
Use a broad source pattern:
{
"source": "**"
}
4. Forgetting custom domains
If you serve both:
example.comwww.example.com
then both need correct HTTPS behavior and should send HSTS if they’re both user-facing. HSTS is host-specific unless you use includeSubDomains, and even then you need the hierarchy to make sense.
5. Thinking HSTS replaces redirects
It doesn’t.
You still want normal HTTP-to-HTTPS redirect behavior for first-time visitors and non-HSTS clients. HSTS strengthens that behavior after the browser has learned the rule.
Firebase Hosting already handles HTTPS delivery nicely, but you should still test the HTTP experience for your connected domains.
How to back out of HSTS
If you need to disable HSTS, the proper way is to send:
Strict-Transport-Security: max-age=0
In Firebase Hosting:
{
"hosting": {
"headers": [
{
"source": "**",
"headers": [
{
"key": "Strict-Transport-Security",
"value": "max-age=0"
}
]
}
]
}
}
Then deploy again:
firebase deploy --only hosting
That tells browsers to forget the policy after receiving the new HTTPS response.
Two catches:
- users must revisit over HTTPS to receive the clearing header
- if your domain is preloaded,
max-age=0does not magically remove it from browser preload lists
That’s another reason I’m conservative about preload.
My recommended HSTS policy for most Firebase projects
For a typical production app on Firebase Hosting, I’d usually aim for this once the domain setup is mature:
Strict-Transport-Security: max-age=31536000; includeSubDomains
And I’d use this during rollout:
Strict-Transport-Security: max-age=86400
That gets you strong protection without rushing into preload.
If your team is disciplined, your subdomains are clean, and you’ve verified HTTPS everywhere, then preload can make sense. But don’t copy it blindly just because it looks more “enterprise.”
Final deployment checklist
Before you call HSTS done, check these:
- site works on HTTPS for every user-facing hostname
- no critical subdomain still relies on HTTP
- HSTS header appears on real HTTPS responses
max-agestarted low and was increased deliberatelyincludeSubDomainswas added only after checking all subdomainspreloadwas considered carefully, not pasted from a snippet
Firebase Hosting makes the mechanics easy. The hard part is being honest about your domain sprawl. If you keep the rollout boring and incremental, HSTS is one of the highest-value headers you can add with almost no runtime cost.
For Firebase Hosting header configuration details, use the official docs:
https://firebase.google.com/docs/hosting/full-config#headers