How to check if HSTS is enabled

Request the host over HTTPS and print the response headers with curl -s -o /dev/null -D - https://example.com/, then grep for strict-transport-security. HSTS is on when that line comes back carrying a non-zero max-age. No line at all means a browser that has not visited the host yet still tries plain HTTP first.

Checker offline. Follow the manual steps below, they give the same answer.

Why check this

HSTS turns the first request of a session into an HTTPS request that the browser rewrites before it leaves the machine. Run the check at staging sign-off and again after any change to the CDN, the load balancer or the vhost, because all three can drop a response header without failing a deploy. When the header is missing, a user who types the bare hostname sends one HTTP request, and any cookie without the Secure attribute rides along in clear text before the redirect lands.

Prerequisites

Steps

  1. Step 1.

    Read the header from the HTTPS response.

    curl -s -o /dev/null -D - https://www.cloudflare.com/ | grep -i 'strict-transport-security'
    
    strict-transport-security: max-age=31536000; includeSubDomains
  2. Step 2.

    Run the same command against a host that sends no policy, so you know what absence looks like.

    curl -s -o /dev/null -D - https://example.com/ | grep -ci 'strict-transport-security'
    
    0

    A count of 0 is the failing result. Without -c, grep prints nothing and exits non-zero, which reads like a broken command.

  3. Step 3.

    Pull max-age out as a number you can compare against the policy you agreed on.

    curl -s -o /dev/null -D - https://developer.mozilla.org/en-US/ | grep -i 'strict-transport-security' | grep -o -E 'max-age=[0-9]+'
    
    max-age=63072000

    63072000 seconds is 730 days.

  4. Step 4.

    Read the plain HTTP response and confirm the policy is not being served there instead.

    curl -s -o /dev/null -D - http://www.cloudflare.com/ | grep -i -E '^HTTP/|^Location|strict-transport-security'
    
    HTTP/1.1 301 Moved Permanently
    Location: https://www.cloudflare.com/

    No policy line here is the correct result. RFC 6797 tells browsers to ignore the header on any response that did not arrive over TLS, so a config that sets it only on the HTTP vhost protects nobody.

How to read the result

| What you see | What it means | What to do | | --- | --- | --- | | max-age=31536000; includeSubDomains | One year, this host and every subdomain | Nothing. This is the shape to aim for. | | max-age=300 | The policy expires five minutes after the last visit | Raise it. A short window is a rollback switch, not a policy. | | max-age=0 | The server is clearing the stored policy | Someone left a rollback value in the config. Treat it as a release blocker. | | No header on the HTTPS response | The host has no HSTS | The first request of every new session can still leave over HTTP. | | includeSubDomains missing | Only this exact host is covered | Decide whether every subdomain, internal ones included, can serve HTTPS. |

Common mistakes

Sign: curl -I on the http:// URL shows the header, so the check is recorded as passing.Cause: Browsers discard Strict-Transport-Security on any response that did not come over TLS. The header on the HTTP vhost is dead weight, and reading it there hides the fact that the HTTPS response carries none.
Sign: The apex passes and a subdomain is still reachable over plain HTTP.Cause: The apex and www are separate hosts with separate policies. Measured on 2026-09-11, cloudflare.com returned max-age=15780000 while www.cloudflare.com returned max-age=31536000 on the same day. Check every hostname in the certificate.
Sign: The header is present yet the tester still sees a plain HTTP request in the proxy log.Cause: A browser applies HSTS only after it has seen one successful HTTPS response from that host. The first visit from a clean profile is unprotected unless the host ships in the browser preload list.

Thresholds

max-age=31536000 seconds, one year, is the minimum a host must send to qualify for the browser preload list Source: https://hstspreload.org/
max-age=0 tells the browser to delete the stored policy for that host Source: https://www.rfc-editor.org/rfc/rfc6797#section-6.1.1

What to check next

FAQ

How to check the strict-transport-security header?

The command in step 1 is the check. -D - prints response headers to stdout and -o /dev/null throws the body away, so grep sees headers only. Any header name works in place of this one.

How to check the HSTS header on a subdomain?

Run step 1 again with the subdomain in the URL. includeSubDomains on the parent covers browser behaviour, but it does not make the subdomain send its own header, so reading the subdomain directly can return nothing.

Does a 301 to HTTPS make HSTS unnecessary?

No. The redirect is served over HTTP, so the request that triggers it is already in clear text and can be rewritten in transit. HSTS moves the decision into the browser, before any packet leaves.

What max-age is safe during a rollout?

Start at 300 seconds so a mistake ages out in five minutes, confirm no subdomain breaks, then raise it to 31536000. Lowering the value later does not reach browsers that already stored the long one.

Verified

Verified by Maks Vernycurl 8.21.0

Each output block is what the command above it printed on that date, on the host named in the step. Figures read from a live site move between runs. Compare the shape of the answer rather than the digits, and see the methodology for how a page is re-verified.

basic3 minpublished updated Maks Verny