Moving a site from HTTP to HTTPS sounds easy until you do it on a real production system with old links, mixed content, proxies, CDNs, and a few mystery services nobody wants to touch. Then HSTS enters the picture and raises the stakes, because once browsers cache that policy, mistakes stop being easy to undo.

That does not mean HSTS is risky in a bad way. I’d still recommend it for most public sites. But I would not enable it blindly on day one with a one-year max-age and preload unless I was very sure the whole stack was clean.

Here’s the practical comparison: how to migrate, what you gain, what can go wrong, and how to roll it out without locking users into a broken setup.

What changes when you move to HTTPS

Plain HTTP gives attackers too many opportunities: passive snooping, active content injection, cookie theft, and downgrade tricks. HTTPS fixes transport confidentiality and integrity, assuming your TLS setup is sane.

HSTS, via the Strict-Transport-Security header, adds another layer: it tells browsers to stop using HTTP for your domain after the first secure visit. That blocks protocol downgrade attacks and removes the “first try HTTP, then redirect” pattern for returning users.

A typical header looks like this:

Strict-Transport-Security: max-age=31536000; includeSubDomains

And if you are going all in:

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

The preload token is not magic by itself. It is just a signal that your domain is intended for browser preload lists, and that comes with strict requirements. Treat preload as the final stage, not the starting point.

Quick comparison: HTTPS redirect only vs HTTPS + HSTS

HTTPS with redirects only

Pros

  • Easy to roll out
  • Easy to back out
  • Good first migration step
  • Works fine for many internal or lower-risk sites

Cons

  • First request may still hit HTTP
  • Users can be exposed to downgrade or SSL stripping on hostile networks
  • Browsers keep trying HTTP unless links and bookmarks are already updated

HTTPS with HSTS

Pros

  • Stronger downgrade protection
  • Browsers automatically switch to HTTPS for repeat visits
  • Helps enforce secure cookie and session handling habits
  • Required if you want browser preload protection

Cons

  • Misconfiguration hurts more
  • Bad certificates become user-visible outages
  • includeSubDomains can break forgotten legacy hosts
  • Preload is hard to reverse quickly

That is the real tradeoff: HSTS gives stronger guarantees, but it reduces your room for operational mistakes.

A sane migration plan

I like a phased rollout. It is boring, and boring is good in production.

Phase 1: Make HTTPS fully work before forcing it

Before you enable HSTS, get the site clean over HTTPS:

  • Valid certificate chain
  • No mixed content
  • No HTTP-only asset references in templates, CSS, or JS
  • Cookies marked Secure where appropriate
  • Redirect HTTP to HTTPS consistently
  • APIs, static assets, images, fonts, and third-party scripts all loading over HTTPS

If your page still pulls http:// assets, browsers will block some of them and quietly degrade others. Fix that first.

A basic redirect in Nginx:

server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;
    return 301 https://$host$request_uri;
}

And your TLS server:

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name example.com www.example.com;

    ssl_certificate /etc/ssl/certs/fullchain.pem;
    ssl_certificate_key /etc/ssl/private/privkey.pem;

    root /var/www/html;
    index index.html;
}

Apache version:

<VirtualHost *:80>
    ServerName example.com
    ServerAlias www.example.com
    Redirect permanent / https://example.com/
</VirtualHost>

<VirtualHost *:443>
    ServerName example.com
    ServerAlias www.example.com

    SSLEngine on
    SSLCertificateFile /path/to/fullchain.pem
    SSLCertificateKeyFile /path/to/privkey.pem
</VirtualHost>

Phase 2: Add a short HSTS policy

Start with a low max-age. Something like 5 minutes to a day is reasonable during validation.

Nginx:

add_header Strict-Transport-Security "max-age=300" always;

Apache:

Header always set Strict-Transport-Security "max-age=300"

The always part matters. Without it, some responses may miss the header, especially error responses. That makes rollout less predictable.

At this stage, test everything:

  • Main site
  • Login flow
  • Logout flow
  • Static assets
  • Error pages
  • Redirect responses
  • Mobile app webviews if you have them
  • Alternate hostnames like www, m, cdn, static, api

You can use browser dev tools and a scanner like HeaderTest to confirm the header is present where you expect it.

Phase 3: Increase max-age gradually

Once the site is stable:

  • Move to max-age=86400 (1 day)
  • Then max-age=2592000 (30 days)
  • Then max-age=31536000 (1 year)

This phased approach is less exciting than flipping the “secure” switch immediately, but it gives you a way to catch edge cases before browsers cache your policy for months.

The biggest migration decision: includeSubDomains or not

This is where people get burned.

Strict-Transport-Security: max-age=31536000; includeSubDomains

includeSubDomains means every subdomain must be HTTPS-capable and stay that way. Not just the obvious ones. All of them.

Pros of includeSubDomains

  • Stronger protection across the whole domain tree
  • Prevents weak HTTP-only subdomains from becoming attack paths
  • Usually required for preload readiness

Cons of includeSubDomains

  • Breaks forgotten hosts like old-admin.example.com
  • Can impact internal tools if they share the public parent domain
  • Forces you to inventory infrastructure you may not fully control

My opinion: use includeSubDomains only after you have a real subdomain inventory. If your org has years of DNS cruft, this is not optional.

Preload: powerful, but unforgiving

Preload puts your domain into browser-maintained HSTS lists so even the first visit is HTTPS-only. That is the strongest version of this setup.

Pros of preload

  • Protects first-time visitors
  • Eliminates initial HTTP exposure
  • Great for high-value public domains

Cons of preload

  • Hard to undo quickly
  • Requires long-term commitment to HTTPS everywhere
  • Operational mistakes can take a while to clear from browsers
  • Usually requires max-age >= 31536000, includeSubDomains, and a redirecting HTTP endpoint

I only recommend preload when:

  1. Every public subdomain is known
  2. Every one of them supports HTTPS correctly
  3. You are confident this will remain true

If that sounds like a governance problem, that is because it is.

Common breakage during migration

Mixed content

You moved the page to HTTPS, but CSS, JS, images, fonts, or XHR still use http://.

Fix hardcoded URLs, or better, use root-relative or explicit HTTPS URLs.

Bad:

<script src="http://static.example.com/app.js"></script>

Good:

<script src="https://static.example.com/app.js"></script>

Secure cookies missing

Session cookies sent over HTTP are a gift to attackers.

Use Secure and usually HttpOnly:

Set-Cookie: session=abc123; Path=/; Secure; HttpOnly; SameSite=Lax

Proxy/CDN confusion

Your app thinks requests are HTTP because TLS terminates upstream, so it generates insecure redirects or wrong absolute URLs.

Make sure your framework trusts the right forwarding headers and that your proxy sets them correctly.

Partial HSTS deployment

You set HSTS on the homepage, but not on redirects, errors, or app responses behind another service. Browsers cache based on what they receive. Inconsistent delivery means inconsistent protection.

How to back out if something goes wrong

You can reduce HSTS by sending:

Strict-Transport-Security: max-age=0

That tells browsers to delete the cached policy. But there are two catches:

  1. Users must successfully reach your site over HTTPS to receive that header.
  2. If you preloaded the domain, browser preload lists are a separate process and not an instant rollback.

That is why I keep saying not to rush preload.

For most developer teams running a public site:

  1. Fix HTTPS everywhere
  2. Redirect all HTTP to HTTPS
  3. Set secure cookies
  4. Start HSTS with max-age=300
  5. Increase to 1 day, then 30 days
  6. Add includeSubDomains only after a real audit
  7. Move to 1 year
  8. Consider preload only if you truly control the whole domain

If you want the shortest opinionated version: HTTPS redirects are table stakes. HSTS is the upgrade. Preload is the commitment ceremony.

That is the comparison nobody says out loud. The security benefit increases at each step, but so does the cost of being wrong.

For implementation details on server configuration and HSTS behavior, the official docs are worth checking:

And before you extend max-age or enable includeSubDomains, run a quick verification with HeaderTest. It is faster than discovering a broken subdomain after browsers have already cached your policy.