Skip to Content
FrontendSecurityImplement HTTP Strict Transport Security (HSTS)

Implement HTTP Strict Transport Security (HSTS)

Introduction

HSTS is a header that forces the browser to always use HTTPS for a domain, for a defined duration — even if the user types http:// or clicks an old unencrypted link.

Strict-Transport-Security: max-age=63072000; includeSubDomains; preload

This page documents the fleet-wide audit tracked in issue #326  (September 2026).


The problem it solves: downgrade / SSL-stripping

Even if a site only ever serves HTTPS, there’s still a gap: the very first request. If a user types example.com without https://, or clicks an old http:// link, the browser sends that first request unencrypted — before the server gets a chance to redirect it to HTTPS.

On a shared network (public wifi, a compromised router), an attacker can intercept that first HTTP request and read or redirect it before the HTTPS redirect ever happens. This is a downgrade attack (SSL-stripping): the attacker keeps the conversation on unprotected HTTP while the user believes they’re safe.

HSTS closes this gap: the first time the browser visits the site over HTTPS, it remembers this header for max-age seconds. After that, it rewrites http:// to https:// locally, before sending anything on the network — so there is nothing left to intercept.

Different protection scope than CSP: CSP protects what happens inside the page once it’s loaded (scripts, styles). HSTS protects the connection itself, before the page starts loading at all.


The 3 directives

DirectiveMeaning
max-age=63072000Duration in seconds — 2 years. The browser enforces the rule for this long after the last visit.
includeSubDomainsAlso protects every subdomain (dashboard.phpr.link, login.phpr.link, …), not just the exact domain.
preloadSubmits the domain to a list built into browsers (Chrome, Firefox, …) via hstspreload.org . Without it, the browser needs one HTTPS visit to “learn” the rule — with it, protection exists from the very first visit, ever.

preload has a cost: once a domain is accepted into the browsers’ preload list, it’s slow and hard to remove (can take months to propagate through browser updates). Only enable it once HTTPS is genuinely stable and permanent across the domain and all its subdomains.


How to add it

Unlike CSP, HSTS’s value never changes between requests — no nonce, nothing dynamic. next.config.js’s headers() is enough; no middleware.ts needed.

// next.config.js const nextConfig = { async headers() { return [ { source: "/(.*)", // apply to all routes headers: [ { key: "Strict-Transport-Security", value: "max-age=63072000; includeSubDomains; preload", }, ], }, ]; }, };

Some apps gate it behind process.env.NODE_ENV === "production" — reasonable, since forcing HTTPS on localhost during local dev gets in the way.

How to verify: DevTools → Network tab → select the document request → Response Headers → look for Strict-Transport-Security.


Rollout status (September 2026 audit)

AppStatus
Dashboard, Account, Doc, Login, Inventory, ReportAlready present
POSAlready present, but was missing preload — added for consistency with the rest of the fleet
Feedback, Print, Email, Punch, CRUDv2, Account CRUD v2, Configuration CRUD v2, Ticket CRUD v2Added — had no headers() at all before

The tracking issue’s checklist wasn’t fully up to date — Inventory was already implemented but unchecked. Verify the actual next.config.js rather than trusting the checklist alone.


Security checklist
Last updated on