HTTP Strict Transport Security looks simple: send one header, force HTTPS, move on. In practice, HSTS failures are boring, subtle, and easy to miss until production traffic hits an unexpected edge case.

I’ve seen teams “enable HSTS” and still leave redirect loops, missing subdomains, CDN overrides, and preload-breaking configs in place. The header was technically there, but the deployment was not actually trustworthy.

If you want to audit HSTS properly, compare multiple methods. No single check catches everything.

What you’re auditing

At minimum, you want answers to these questions:

  • Is the Strict-Transport-Security header present on HTTPS responses?
  • Is it absent on HTTP responses?
  • Is max-age long enough for your policy?
  • Are includeSubDomains and preload used correctly?
  • Are all subdomains actually HTTPS-ready before you enforce them?
  • Do redirects behave cleanly from http:// to https://?
  • Are your CDN, reverse proxy, app server, and framework all aligned?

A valid HSTS header looks like this:

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

A weaker but still common version:

Strict-Transport-Security: max-age=86400

That second one is useful during rollout, but I would not call it a strong final configuration.

Method 1: Manual header inspection

This is the fastest place to start. Use curl, inspect live responses, and verify the exact header value yourself.

Example

curl -I https://example.com

You want to see something like:

HTTP/2 200
strict-transport-security: max-age=31536000; includeSubDomains; preload

Then check the HTTP endpoint:

curl -I http://example.com

You should usually see a redirect:

HTTP/1.1 301 Moved Permanently
Location: https://example.com/

Pros

  • Fast and reliable for spot checks
  • Shows the exact live response
  • Easy to script in CI or deployment verification
  • Great for checking specific hosts, paths, and environments

Cons

  • Easy to miss subdomains
  • Doesn’t tell you if browser behavior is actually correct
  • Won’t catch preload eligibility issues by itself
  • Humans are bad at repeating this consistently across many hosts

What to look for

I usually check at least these cases:

curl -I https://example.com
curl -I https://www.example.com
curl -I http://example.com
curl -I http://www.example.com

And if the site uses app, api, admin, or static subdomains:

for host in example.com www.example.com api.example.com admin.example.com; do
  echo "=== $host ==="
  curl -s -I https://$host | grep -i strict-transport-security
done

If you use includeSubDomains, this kind of sweep stops being optional.

Method 2: Browser-based validation

Manual headers tell you what the server says. Browsers tell you what the client actually enforces. That difference matters.

For example, a browser may already have cached an HSTS policy. You might think your redirect behavior is fine, but the browser never even tries plain HTTP anymore. That can hide server-side mistakes.

What to test

  • Visit the HTTPS site and confirm the HSTS header is received
  • Try loading the HTTP version after policy is cached
  • Inspect DevTools network responses
  • Clear browser HSTS state when testing rollout changes

In Chromium-based browsers, you can inspect network headers in DevTools. For lower-level behavior, browser internal HSTS tools are useful during debugging, though I prefer them only as a secondary check.

Pros

  • Validates real client behavior
  • Helps catch cached policy effects
  • Useful for debugging redirects and mixed deployment states
  • Better than curl for understanding user impact

Cons

  • Slower and more manual
  • Browser cache can confuse results
  • Harder to automate well
  • Different browsers may behave differently around edge cases

My take

Browser validation is where you catch the “works on paper” problems. I would never stop at curl alone, especially after changing HSTS duration or enabling includeSubDomains.

Method 3: Automated security header scans

Automated scans are the easiest way to get broad coverage and catch obvious mistakes quickly. For a quick audit, run your domain through a scanner like Headertest.

These tools usually report:

  • Whether HSTS is present
  • Header syntax issues
  • Missing includeSubDomains
  • Missing preload
  • Weak max-age
  • Other security headers you probably should audit at the same time

Pros

  • Fastest way to get a high-level view
  • Easy for repeat checks after releases
  • Good for teams that want a simple pass/fail signal
  • Often catches related header gaps beyond HSTS

Cons

  • Can miss app-specific routing issues
  • Usually tests a limited set of URLs
  • May not cover all subdomains
  • Can encourage checkbox security if you rely on them too much

My take

Scanners are great for triage, not enough for sign-off. I use them early and often, but I still verify critical hosts manually.

Method 4: Infrastructure config review

This is the least glamorous part of the audit, and usually the most revealing.

If your stack includes a CDN, load balancer, reverse proxy, ingress controller, and app framework, HSTS might be injected in one layer, stripped in another, duplicated somewhere else, or only applied on some routes.

Review the actual config.

Nginx example

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

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

    location / {
        proxy_pass http://app;
    }
}

The always keyword matters. Without it, some error responses may not include the header.

Apache example

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

Express example

import express from "express";
import helmet from "helmet";

const app = express();

app.use(
  helmet.hsts({
    maxAge: 31536000,
    includeSubDomains: true,
    preload: true,
  })
);

app.get("/", (req, res) => {
  res.send("ok");
});

app.listen(3000);

Pros

  • Finds the root cause, not just symptoms
  • Helps prevent drift across environments
  • Essential for debugging inconsistent behavior
  • Lets you verify error pages and non-app routes

Cons

  • Slower than scanning
  • Requires access to infra and deployment details
  • Easy to overlook inherited or generated config
  • Needs coordination across teams

If your audit ends with “the scanner says it’s fine,” you haven’t really finished.

Method 5: Preload readiness review

If you plan to use preload, audit more aggressively. Preloading is not a casual toggle. Once browsers bake your domain into preload lists, rollback gets painful.

Preload-oriented checklist

  • max-age is at least 31536000
  • includeSubDomains is present
  • preload token is present
  • All subdomains serve valid HTTPS
  • HTTP redirects cleanly to HTTPS
  • No internal-only subdomains will break under forced HTTPS

Pros

  • Forces you to think about the whole domain
  • Reduces first-visit downgrade risk
  • Strongest HSTS posture when done right

Cons

  • Higher blast radius for mistakes
  • Painful if you forgot legacy or internal subdomains
  • Rollback is much slower than a normal header change

I’m opinionated here: don’t preload until you’ve inventoried subdomains properly. I’ve seen old mail, staging, and forgotten admin hosts turn into avoidable incidents because someone added includeSubDomains; preload before checking what existed.

Common audit failures

These show up constantly:

1. HSTS sent over HTTP

That does nothing useful. Browsers only honor HSTS over HTTPS.

2. Tiny max-age

A value like max-age=300 may be fine for testing, but it’s not a mature production policy.

3. Missing includeSubDomains

If your goal is domain-wide protection, omitting this leaves obvious gaps.

4. Broken subdomains under includeSubDomains

This is the classic self-own. The policy is strong, the estate is not.

5. Header missing on redirects or error responses

If only successful app responses send HSTS, your coverage is incomplete.

6. Conflicting headers from multiple layers

I’ve seen duplicate HSTS headers with different max-age values. That usually means nobody owns the final response path.

A practical audit workflow

If I were auditing a production site today, I’d do it in this order:

  1. Run a quick external scan for a baseline.
  2. Use curl against apex, www, and critical subdomains.
  3. Check HTTP-to-HTTPS redirect behavior.
  4. Validate in a real browser with cached and cleared state.
  5. Review CDN, proxy, ingress, and app config.
  6. Inventory subdomains before approving includeSubDomains or preload.
  7. Re-test after deployment from outside the network.

That combination gives you speed, accuracy, and enough context to avoid false confidence.

When your HSTS config is “good enough”

For most production sites, I’d consider this a solid target:

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

And if you truly meet preload requirements and understand the blast radius:

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

If you’re still early in rollout, start smaller:

Strict-Transport-Security: max-age=86400

Then increase after verifying every edge.

That’s really the core of auditing HSTS: don’t just check whether the header exists. Check whether your whole platform can survive the policy you’re telling browsers to enforce.