HSTS preloading is one of those rare web security features that’s both boring and incredibly useful. If you run a real production site, especially one that handles logins, payments, admin panels, or anything remotely sensitive, getting onto the HSTS preload list is usually worth doing.
Why? Because normal HSTS only starts protecting users after they’ve visited your site once over HTTPS and received the Strict-Transport-Security header. Preloading removes that first-visit gap. Browsers ship with your domain baked into a hardcoded HTTPS-only list, so they’ll never attempt plain HTTP in the first place.
That’s the good news. The catch is that preloading is intentionally strict. You need to configure HSTS correctly, commit to HTTPS across your whole domain tree, and understand that rolling it back can be slow and annoying.
This tutorial walks through what the preload list is, the exact requirements, how to configure headers, how to submit your domain, and what can go wrong.
What the HSTS preload list actually does
Normally, HSTS works like this:
- User visits
https://example.com - Your server sends:
Strict-Transport-Security: max-age=31536000; includeSubDomains
- The browser remembers that
example.commust use HTTPS for the next year
That’s fine, but it doesn’t protect the very first visit. A user typing example.com might still start with http://example.com, and a network attacker could potentially interfere before the redirect to HTTPS happens.
The preload list fixes that by letting browser vendors ship domains that should always use HTTPS from the start. If your domain is preloaded, browsers know before making any request that your site must be contacted only over HTTPS.
In practice, this means:
http://example.comis upgraded internally to HTTPS by the browser- certificate errors can’t be bypassed the usual way
- subdomains are also forced to HTTPS if you preload correctly
It’s a strong commitment. That last point matters a lot more than people think.
Before you preload: understand the commitment
Preloading is not just “turn on a stronger header.” It’s more like saying:
- my apex domain supports HTTPS forever
- all current subdomains support HTTPS
- all future subdomains will support HTTPS too
- I will not need a plain HTTP-only host anywhere under this domain
That includes weird legacy names like:
old-admin.example.commta-sts.example.comhelpdesk.example.comdev.example.comprinter.example.com
If any subdomain is publicly reachable and doesn’t support HTTPS properly, preloading can break it for users in major browsers.
So before doing anything else, inventory your subdomains. Seriously. This is where most bad preload attempts go wrong.
The preload requirements
To get on the preload list, your domain generally needs to meet these requirements:
- serve a valid certificate
- redirect all HTTP traffic to HTTPS
- serve HSTS on the HTTPS response
- use
max-ageof at least31536000 - include
includeSubDomains - include the
preloadtoken - apply the policy on the apex domain, usually
example.com - apply the policy on
www.example.comtoo if it exists
A valid preload header looks like this:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
That exact shape matters. If you leave out includeSubDomains or use a shorter max-age, your submission will fail.
Step 1: make sure HTTP always redirects to HTTPS
Before touching HSTS, make sure every HTTP request gets a clean redirect to HTTPS.
Nginx example
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
Apache example
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
Redirect permanent / https://example.com/
</VirtualHost>
If you want to preserve the host and path more explicitly in Apache:
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
</VirtualHost>
Caddy example
example.com, www.example.com {
redir https://{host}{uri} permanent
}
This redirect should happen for every path, not just the homepage.
Step 2: serve the HSTS header on HTTPS
Do not send HSTS over HTTP. Browsers ignore it there anyway. Send it only on HTTPS responses.
Nginx example
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com www.example.com;
ssl_certificate /etc/ssl/example/fullchain.pem;
ssl_certificate_key /etc/ssl/example/privkey.pem;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
root /var/www/html;
}
That always flag is important in Nginx because it ensures the header is added even on error responses.
Apache example
<VirtualHost *:443>
ServerName example.com
ServerAlias www.example.com
SSLEngine on
SSLCertificateFile /etc/ssl/example/fullchain.pem
SSLCertificateKeyFile /etc/ssl/example/privkey.pem
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
</VirtualHost>
You’ll need mod_headers enabled for that.
Express / Node.js example
If you terminate TLS in Node directly:
app.use((req, res, next) => {
if (req.secure) {
res.setHeader(
'Strict-Transport-Security',
'max-age=31536000; includeSubDomains; preload'
);
}
next();
});
If you’re behind a reverse proxy, make sure req.secure is trustworthy:
app.set('trust proxy', true);
app.use((req, res, next) => {
if (req.secure) {
res.setHeader(
'Strict-Transport-Security',
'max-age=31536000; includeSubDomains; preload'
);
}
next();
});
In many setups, it’s cleaner to set HSTS at the reverse proxy or load balancer instead of inside the app.
Step 3: start safely before going full preload
Here’s my opinionated advice: don’t jump straight to preload in production unless you’re absolutely sure your domain is clean.
A safer rollout looks like this:
Phase 1: short max-age
Strict-Transport-Security: max-age=300
That’s five minutes. Verify nothing weird breaks.
Phase 2: longer max-age without preload
Strict-Transport-Security: max-age=86400; includeSubDomains
That’s one day. Then maybe move to a week:
Strict-Transport-Security: max-age=604800; includeSubDomains
Then to a year:
Strict-Transport-Security: max-age=31536000; includeSubDomains
Phase 3: add preload token
Once you’re confident all subdomains are HTTPS-safe:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Yes, preloading requires one year. No, there’s no practical shortcut around that.
Step 4: verify your domain and subdomains
At minimum, check:
https://example.comhttps://www.example.comhttp://example.comhttp://www.example.com
You should also test any known subdomains that users might hit directly.
Use curl to inspect the response headers:
curl -I http://example.com
Expected result: a redirect to HTTPS.
HTTP/1.1 301 Moved Permanently
Location: https://example.com/
Then check HTTPS:
curl -I https://example.com
Expected result includes:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Also test www:
curl -I https://www.example.com
And if you redirect www to apex or the other way around, make sure the HSTS header is still present on the HTTPS response involved in that path.
Test your HSTS configuration and other security headers at headertest.com - free, instant, no signup required.
Step 5: submit to the preload list
The canonical submission point is the HSTS preload service used by browsers. You submit your domain, it runs checks, and if everything passes, your domain becomes eligible for inclusion in the generated preload list.
The practical flow is:
- set the required header on your production site
- confirm redirects and certificate validity
- confirm
includeSubDomainswon’t break anything - submit the apex domain
- wait for browser release cycles
This last part surprises people: passing submission checks does not mean every user is instantly protected. It still takes time for browser vendors to ship updated preload data.
So don’t think of preload as a same-day switch. Think of it as a permanent hardening step that gradually propagates.
Common mistakes that block preload
Missing includeSubDomains
This is the classic one. Without it, preload won’t happen.
Bad:
Strict-Transport-Security: max-age=31536000; preload
Good:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Max-age too short
Also common.
Bad:
Strict-Transport-Security: max-age=10886400; includeSubDomains; preload
Good:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Redirecting to HTTPS but not sending HSTS on the HTTPS site
A redirect alone is not enough. You need the actual HSTS header on the final HTTPS response.
Broken subdomains
This is the preload killer that shows up after submission. Maybe blog.example.com is fine, but legacy.example.com has an expired cert, or dev.example.com is public and only works over HTTP. With includeSubDomains, that’s now your problem.
CDN or proxy stripping the header
Sometimes your origin sends HSTS correctly, but the CDN normalizes or removes it. Always test the public edge response, not just the backend.
How to check subdomains before preloading
You don’t need a perfect map of every internal hostname, but you do need confidence that anything publicly resolvable under your domain won’t become unusable under HTTPS-only rules.
A practical approach:
Query DNS records
List known hosts from your DNS provider and zone files.
Look for:
AAAAACNAME
Crawl certificates
Certificate transparency logs can expose subdomains that your org has used publicly.
Test known hosts with curl
curl -I http://sub.example.com
curl -I https://sub.example.com
You want one of these outcomes:
- HTTPS works with a valid cert
- the hostname doesn’t publicly resolve
- the hostname is intentionally retired and inaccessible
What you do not want is a live HTTP-only service.
How to remove a domain from the preload list
You can request removal, but this is not a quick undo button. Removal takes time, browser updates take time, and users may stay on old versions for a while.
That means if preloading breaks something critical, the blast radius can last weeks or longer.
This is why I’m pretty conservative about recommending preload for organizations with messy DNS, random inherited subdomains, or teams that love spinning up public test environments under the main production domain.
If that sounds like your company, fix your naming and subdomain hygiene first.
Best practices if you plan to preload
Use a separate domain for experiments
Don’t put half-broken dev systems under your main preloaded domain if you can avoid it.
For example, instead of:
dev.example.comqa.example.com
consider using a separate non-production domain entirely.
Automate certificate management
If preloading means every subdomain must support HTTPS, manual certificate processes become a liability. Use ACME automation where possible.
Standardize redirects and headers at the edge
Set HTTPS redirects and HSTS in one place:
- load balancer
- ingress controller
- CDN
- reverse proxy
That reduces drift between apps.
Document the preload commitment
Future teams will forget. Leave a note in:
- infra repos
- runbooks
- DNS management docs
- platform standards
Make it explicit that new public subdomains must support HTTPS from day one.
A simple production-ready example
Here’s a common Nginx setup for apex plus www, with HTTP redirected and HSTS enabled:
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
return 301 https://example.com$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
return 301 https://example.com$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
root /var/www/example;
index index.html;
}
This is boring, and that’s exactly what you want. Security headers should be boring.
Final advice
If your site is stable, fully HTTPS, and you control your subdomains responsibly, get on the preload list. It closes a real first-visit security gap and strengthens your HTTPS posture in a way users never have to think about.
But don’t treat it like a checkbox. Preloading is a long-term domain policy, not a tweak.
My practical recommendation is:
- enable HTTPS everywhere
- roll out HSTS gradually
- verify all public subdomains
- add
includeSubDomains - add
preload - submit only when you’re confident you won’t regret it
That last step matters most. HSTS preload is excellent when you’re ready for it, and deeply annoying when you aren’t.