Stack guide
Express.js HSTS — adding Strict-Transport-Security to a Node app
Adding HSTS to an Express app is a one-liner with Helmet, or a five-line custom middleware. The bigger question is *where* to set it: in Node, at your reverse proxy (nginx, Caddy), or at your CDN edge. This guide covers all three so you don't accidentally double-set the header (which causes some browsers to ignore both).
1. The Helmet path (recommended)
If you can take a dependency, `helmet` is the canonical choice. `app.use(helmet.hsts({ maxAge: 15552000, includeSubDomains: true }))` ships HSTS plus a sensible default Content-Security-Policy and the rest of the security-header baseline in one line. The 6-month `maxAge` is conservative — bump to 1 year (`31536000`) once the site has been stable on HTTPS for a quarter, then consider preload.
HSTS missing — nginx (server / location) (nginx)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; # If TLS terminates at a CDN, set the header there instead.
HSTS missing — Express (helmet) (typescript)
import helmet from "helmet";
app.use(helmet.hsts({ maxAge: 31_536_000, includeSubDomains: true }));HSTS missing — Apache (vhost) (apache)
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
Related Scorifya checks: missing_hsts, weak_hsts
2. Manual middleware if you don't want Helmet
If you'd rather avoid the dependency, a five-line middleware does the same thing: check `req.secure`, set `Strict-Transport-Security`, call `next()`. Skip the header on local dev (`process.env.NODE_ENV !== 'production'`) so HSTS doesn't get cached against `http://localhost`, which would force HTTPS for every project that uses port 3000.
HSTS missing — nginx (server / location) (nginx)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; # If TLS terminates at a CDN, set the header there instead.
HSTS missing — Express (helmet) (typescript)
import helmet from "helmet";
app.use(helmet.hsts({ maxAge: 31_536_000, includeSubDomains: true }));HSTS missing — Apache (vhost) (apache)
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
Related Scorifya checks: missing_hsts
3. When to let your reverse proxy or CDN handle it
If your Express app sits behind nginx, Caddy, Cloudflare, or an AWS ALB, set HSTS there instead. Two reasons: 1) the edge handles the header consistently across all routes including static assets your Node app never sees, and 2) you avoid the trap where a misconfigured `app.use` order lets some routes through without the header. If you do set HSTS at the edge, remove it from Express to avoid the duplicate-header problem.
Background
What is HSTS? HTTP Strict Transport Security explained →
How HSTS works, why the bootstrap window matters, what max-age and includeSubDomains do, and when (or whether) to submit your domain to the browser preload list.
Read more
Verify with a fresh scan
After deploy, run the scanner on the affected hostname. Headers and TLS settings update on the very next request, so you should see the score move within seconds.