What preload adds over a normal HSTS header

The Strict-Transport-Security header tells a browser: for the next max-age seconds, only ever talk to this host over HTTPS. Type http://, follow an old link, and the browser upgrades the request itself before anything leaves the machine. Certificate errors also stop being click-through warnings and become hard failures.

The gap is the first visit. A browser that has never seen your header makes its first request over plain HTTP if that’s what the user typed or clicked, and an attacker on the network can intercept that one request and keep the user on HTTP. The same gap reopens if a visitor doesn’t return before max-age runs out.

The preload list closes that gap. It’s a list of domains compiled into Chromium’s source code. Chrome and other Chromium-based browsers use it directly, and Firefox and Safari maintain their own lists based on it. A browser treats a listed domain as HTTPS-only from the moment it’s installed, with no first request needed.

That’s the whole benefit: the first request. Everything else the header already gives you.

Should you do it at all?

My view: send the header, with a long max-age and includeSubDomains if you can, on every HTTPS site. Preload only when all three of these are true:

  • The entire domain, every subdomain included, is HTTPS-only today and will stay that way.
  • You control (or can at least enumerate) every subdomain, including internal ones.
  • The site is a target worth protecting on the first request: banking, email, admin consoles, anything with logins that attackers have reason to intercept.

If any of those is shaky, preload’s upside is small and its downside is that you can’t quickly undo it.

The requirements, exactly

hstspreload.org lists these, and the submission form checks them automatically:

  1. Serve a valid certificate.
  2. Redirect HTTP to HTTPS on the same host, if you listen on port 80. http://example.com must go to https://example.com first, not straight to https://www.example.com. That way the browser sees your HSTS header on the apex.
  3. Serve all subdomains over HTTPS. In particular www must work over HTTPS if a DNS record for it exists.
  4. Serve the HSTS header on the base domain over HTTPS, with:
    • max-age of at least 31536000 (one year),
    • includeSubDomains,
    • preload. If https://example.com redirects somewhere else, the redirect response itself must carry the header.

Only registrable domains can be submitted (example.com, not shop.example.com), and the entry covers every name under it.

Check where you are now

# 1. HTTP should redirect to HTTPS on the same host
curl -s -o /dev/null -D - http://example.com/ | grep -iE '^(HTTP/|location:)'
# HTTP/1.1 301 Moved Permanently
# Location: https://example.com/

# 2. The HTTPS apex (even if it redirects) must send the header
curl -s -o /dev/null -D - https://example.com/ | grep -iE '^(HTTP/|location:|strict-transport-security:)'
# HTTP/2 301
# location: https://www.example.com/
# strict-transport-security: max-age=63072000; includeSubDomains; preload

# 3. www must work over HTTPS
curl -sI https://www.example.com/ | grep -i '^HTTP/'

The HTTP check shows the redirect chain and the HSTS header the final page sends. The SSL check reads the HSTS header too and scores it as part of the grade (details at the end of this page). Neither tells you whether a domain is actually on the preload list; for that, use the status lookup on hstspreload.org.

The real risk: every subdomain, forever

includeSubDomains in a preloaded entry means every name under your domain, now and in future, is HTTPS-only in every browser that ships the list. That includes names you forgot about:

  • intranet.example.com resolving on the office DNS to a wiki served over HTTP.
  • A printer, NAS, router or camera admin page at printer.office.example.com.
  • dev.example.com on a developer’s box with a self-signed certificate.
  • A vendor-hosted service on status.example.com or mail.example.com that redirects to HTTP somewhere.
  • Something someone will set up next year without reading this page.

On those names the browser refuses plain HTTP. If the certificate is self-signed, expired or for the wrong name, the user gets an error page with no “proceed anyway” option, because HSTS forbids it. The only fix is to give that host a valid certificate, and until you do, it’s unreachable in a browser.

Inventory before you submit:

  • Export your public DNS zone and test each name over HTTPS.
  • Check certificate transparency logs for names that have ever had public certificates. The subdomain finder lists them from CT logs.
  • Then check internal DNS. CT logs only see names that got a public certificate, and the HTTP-only internal hosts that preload breaks are exactly the ones that never did.

If some subdomains can’t be HTTPS-only, don’t preload this domain. Use the header without includeSubDomains, or host those services under a different domain.

Ramp up max-age instead of jumping

Start short, so a mistake expires quickly, and lengthen once nothing breaks. This is the ramp hstspreload.org recommends:

max-age=300; includeSubDomains          # 5 minutes
max-age=604800; includeSubDomains       # 1 week
max-age=2592000; includeSubDomains      # 1 month
max-age=63072000; includeSubDomains; preload   # 2 years, then submit

Stay on each step long enough for real users to hit every subdomain, and watch support channels for “can’t reach” reports from internal services. Two years is the commonly recommended final value; one year (31536000) is the minimum for preload.

If a step goes wrong, max-age=0 over HTTPS tells browsers to forget the dynamic HSTS entry for your host. That works only for what the header taught them, not for a preload entry.

Setting the header

nginx:

add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;

always adds it to error responses too. Watch nginx’s inheritance rule: if a location block has any add_header of its own, it doesn’t inherit the ones from the server block, and the HSTS header silently disappears for those URLs. Check with nginx -T | grep -n add_header and test a few different paths with curl.

Apache (mod_headers):

Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"

Put it in the HTTPS virtual host. Browsers ignore the header when it arrives over plain HTTP, so sending it on port 80 does nothing.

On Cloudflare, HSTS is under SSL/TLS → Edge Certificates, with separate switches for max-age, includeSubDomains and preload. The same caution applies, since it covers every hostname in the zone.

Submitting, and what happens next

Submit the base domain at hstspreload.org. The form re-checks every requirement against your live site and tells you exactly what fails. Once accepted, the domain is added to Chromium’s list and reaches users only when a browser release containing it ships; that typically takes weeks to a few months, and other browsers pick it up on their own schedules.

Keep sending the header with preload afterwards. hstspreload.org’s guidance is that you must keep meeting the requirements.

Some top-level domains are preloaded as a whole: Google’s .app and .dev, for example. Any name under them is HTTPS-only already, whether or not you submit.

Removal is slow, plan as if it’s permanent

If you need to get off the list, remove preload from your header and submit the domain at hstspreload.org/removal. Removal then has to ship in browser releases just as inclusion did, which takes months. Users on older browser versions keep the old list until they update, and some never do. During all of that, any HTTP-only subdomain stays unreachable for them.

That’s the real reason for the checklist. Preloading takes a few minutes; undoing it can’t be done on your schedule.

Checking your own browser

chrome://net-internals/#hsts in Chrome (edge://net-internals/#hsts in Edge) lets you query a domain. A preloaded domain shows static_upgrade_mode: FORCE_HTTPS; one learned from the header shows a dynamic_upgrade_mode entry. “Delete domain security policies” on the same page clears only the dynamic entry. You can’t delete a preload entry locally.

How DechoNet scores HSTS

The SSL check gives the HSTS header 20 of its 100 points, from the header the HTTPS site sends, not from the preload list:

HeaderPoints
max-age ≥ 31536000 with includeSubDomains and preload20
max-age ≥ 31536000 with includeSubDomains15
max-age ≥ 31536000 only10
shorter max-age5
no header0

So a missing preload directive costs 5 points even if you have deliberately chosen not to preload, and a preloaded domain that stops sending the header loses all 20. The HTTP check treats HSTS separately, as one of six scored security headers, and marks it weak when max-age is under a year. The HTTP security headers guide covers the other headers.

Check your own domain now

Free, no sign-up. Runs the exact check this guide describes and shows what to fix.