HSTS mistakes that make your HTTPS migration risky
Por Seaworthy · 7 de julio de 2026
Photo by Towfiqu barbhuiya on Unsplash
HSTS in the wild
HTTP Strict Transport Security is a one-liner header. One line in your server config, and browsers will refuse to connect over plain HTTP for the duration of your max-age. Simple enough that AI gets it wrong in at least four distinct ways.
I've looked at enough AI-generated Express and Nginx configs to have opinions about this. Here is what goes wrong.
The most common problem: not setting it at all
A vanilla Express app has no HSTS header. Zero. If you ask an AI to "set up a secure Express server," the output often configures HTTPS and maybe a redirect from port 80 to 443, but stops there. No Strict-Transport-Security header means browsers will happily follow HTTP links on future visits, making your TLS setup meaningless against a network attacker who can intercept that first request.
The fix is helmet or a manual header:
// Using helmet (recommended)
const helmet = require('helmet');
app.use(helmet.hsts({
maxAge: 31536000,
includeSubDomains: true
}));
// Or manually
app.use((req, res, next) => {
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
next();
});
max-age=3600 is not a real policy
AI tools often reach for max-age=3600 as a "safe" starting value. One hour. That is not a security policy, that is a flag day. HSTS only protects users who have already visited your site and had the header cached. A one-hour window means the protection expires before most of your users come back.
The standard recommendation is max-age=31536000, one year. That is the value that means something. For a new deployment, you might start shorter while you confirm your HTTPS setup is stable, but bump it to a year once you are confident.
Forgetting includeSubDomains
This one is subtle. Without includeSubDomains, your HSTS policy only covers the exact domain. An attacker who controls staging.yourdomain.com (via a misconfigured DNS record pointing at an unclaimed cloud resource, for example) can serve cookies over HTTP on that subdomain. If your cookies lack the Secure flag, they are exposed.
includeSubDomains forces HTTPS across all subdomains. It is uncomfortable if you have HTTP-only internal services hanging off your domain, but that discomfort is the point. Fix those services rather than carving out an exemption.
The migration trap
Here is the part that actually breaks sites. HSTS is cached by the browser, not the server. Once a browser receives your HSTS header, it will force HTTPS for every request to your domain until the max-age expires, regardless of what your server is doing.
If you enable HSTS while your HTTPS setup still has rough edges, such as a certificate that might fail renewal or a load balancer that is not forwarding port 443 correctly, and something breaks later, users who cached the header cannot reach your site at all. Not even to get an error. The browser silently upgrades the request and gets nothing back.
The safe migration sequence:
- Deploy HTTPS and confirm it works completely.
- Add HSTS with a short
max-age, something like 86400 (one day). - Monitor for problems over a few days.
- Increase
max-ageto 31536000.
Do not skip step 3. The short max-age gives you a recovery window if something goes wrong. Once you are at one year, you are committed.
The preload list
The preload list is a hardcoded list of HSTS domains shipped inside Chrome, Firefox, Safari, and Edge. Sites on the list get HSTS enforcement even on a user's very first visit, before they have ever received a header from your server.
To qualify, your header must include all three of these: max-age of at least 31536000, includeSubDomains, and the preload directive. Then you submit at hstspreload.org.
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
The always directive ensures Nginx sends the header on error responses as well as successful ones. Without it, a 500 response skips the header entirely.
The catch with preloading is removal. Getting off the list takes months and requires browsers to ship a new release. If you ever need to serve HTTP on your domain or any subdomain, you will be waiting. Preloading is appropriate for mature, stable deployments, not for a site mid-migration.
The bottom line
HSTS done wrong falls into two failure modes: too weak to matter (no header, or a one-hour max-age) or locked in too early (preloading before the setup is solid). Both are avoidable. Set the header, use a real max-age, cover subdomains, and only add preload once you are confident your infrastructure is not going to change.
Seaworthy flags missing and undersized HSTS headers as part of its ops basics checks.
Este artículo fue generado por IA y resume fuentes públicamente disponibles.
Seaworthy analiza tu repositorio en busca de los problemas que se tratan en artículos como este.
Brechas de seguridad, secretos expuestos y configuraciones erróneas detectados antes de desplegar. Gratis, sin cuenta necesaria.