โ† Blog  ยท  October 2026

HSTS Monitoring: Detect When Your HTTPS Header Goes Missing

HSTS (HTTP Strict-Transport-Security) is one of the most important security headers you can ship. It is also one of the most common to silently disappear after a deployment.

You add Strict-Transport-Security: max-age=31536000; includeSubDomains to your nginx config. Your SSL Labs score jumps to A+. Six weeks later, a CDN rule change silently drops the header. Your score reverts. Nobody notices until a security audit or a bug report.

This guide explains what HSTS does, the most common ways it regresses, how to check it manually, and how to monitor it automatically.

๐Ÿ” Check your HSTS header now: Free security headers checker โ€” see if your Strict-Transport-Security is set correctly โ†’

What HSTS does

Strict-Transport-Security tells browsers: "Only ever connect to this domain over HTTPS โ€” even if the user types http://, even if a link says http://." The browser caches this instruction for the duration of max-age (in seconds).

Without HSTS, a user on a coffee shop WiFi who navigates to http://yourdomain.com can be intercepted by an attacker before the redirect to HTTPS happens. The attacker performs an SSL stripping attack โ€” your server thinks it's talking to the user over HTTPS, but the user's browser is talking to the attacker over HTTP. HSTS prevents this by making the browser refuse to connect over HTTP in the first place.

# A proper HSTS header looks like this:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

The three parts:

Important: HSTS is only sent over HTTPS. If you check it over HTTP, you won't see it โ€” and that's correct. Use curl -s -I https://yourdomain.com, not http://.

Why HSTS headers regress

CDN header rules overriding your server

If you set HSTS in Cloudflare's "Manage Headers" Transform Rules or via the HSTS toggle in SSL/TLS settings, it can be overridden or dropped when you modify other Cloudflare settings. Cloudflare's HSTS setting under SSL/TLS โ†’ Edge Certificates is separate from custom response headers โ€” having one active doesn't mean both survive a config change.

nginx inheritance bug

nginx's add_header directive is not inherited by child location blocks if those blocks define their own add_header. This means adding a single new header in a nested location block can silently remove HSTS from all requests handled by that block:

server {
    add_header Strict-Transport-Security "max-age=31536000" always;

    location / { try_files $uri $uri/ =404; }

    location /api/ {
        # Adding this new header here silently removes HSTS from /api/ responses:
        add_header X-API-Version "2.0";
        proxy_pass http://localhost:3000;
    }
}

The fix is to include all security headers in every location block, or to use nginx's headers-more module, or to set headers at a level that isn't affected by child block inheritance.

Switching reverse proxies or hosting platforms

Moving from Heroku (which strips most security headers) to a VPS, from Apache to nginx, or from a managed hosting provider to a serverless platform resets your headers to whatever the new platform defaults to. New platforms don't import your header config โ€” you have to add it explicitly.

Load balancer SSL termination changes

When SSL termination moves from your application server to a load balancer (AWS ALB, GCP LB, Azure App Gateway), HSTS is often set at the application layer but not re-added at the load balancer layer. The result: HSTS disappears from production responses without any application deployment.


How to check your HSTS header

Quick curl check

curl -s -I https://yourdomain.com | grep -i strict-transport
# Should output something like:
# strict-transport-security: max-age=31536000; includeSubDomains; preload

If the command produces no output, HSTS is not set. If you see max-age=0, HSTS has been explicitly disabled (this is sometimes done intentionally during migration, but dangerous if unintentional).

Check with the free security headers tool

The security headers checker shows your full security header grade, including whether HSTS is present, the configured max-age, and whether includeSubDomains and preload are set. It checks all six major security headers in one pass.

Check SSL Labs score

Qualys SSL Labs grades HSTS as part of its overall score. An A+ requires HSTS with a max-age of at least 15768000 (6 months) and includeSubDomains. A missing HSTS drops you from A+ to A or lower.


How to monitor HSTS automatically

Manual checks work for the deployment you remember to check after. They miss infrastructure changes, CDN config updates, and any regression that happens between deployments.

Cron + curl script

The simplest automated monitor is a cron job that checks your HSTS header and sends an alert if it's missing:

#!/bin/bash
# /usr/local/bin/check-hsts.sh
DOMAIN="yourdomain.com"
EMAIL="you@yourdomain.com"

HSTS=$(curl -s -I "https://$DOMAIN" | grep -i "strict-transport-security")

if [ -z "$HSTS" ]; then
  echo "HSTS MISSING on $DOMAIN" | mail -s "Security alert: HSTS regression on $DOMAIN" "$EMAIL"
fi
# crontab: run every 6 hours
0 */6 * * * /usr/local/bin/check-hsts.sh

This works well if you have a server to run it on. The limitation: it only monitors from one location, it requires maintaining a cron job, and it won't send alerts if your server is the same one that lost HSTS (infrastructure-level monitoring blind spot).

GitHub Actions scheduled workflow

name: HSTS Monitor
on:
  schedule:
    - cron: '0 8 * * *'  # Daily at 8am UTC

jobs:
  check-hsts:
    runs-on: ubuntu-latest
    steps:
      - name: Check HSTS header
        run: |
          HSTS=$(curl -s -I https://yourdomain.com | grep -i strict-transport-security)
          if [ -z "$HSTS" ]; then
            echo "::error::HSTS header is missing!"
            exit 1
          fi
          echo "HSTS present: $HSTS"

GitHub Actions failed runs send email notifications to the repo owner. This works reasonably well for catching daily regressions, though it only runs once per day and requires a GitHub repo.

Dedicated security header monitoring service

The most reliable approach is a service that continuously monitors your security headers from external infrastructure and alerts you the moment something changes โ€” without requiring you to maintain a cron job or GitHub Action.

๐Ÿ›ก๏ธ Monitor your HSTS and all security headers automatically

HeaderGuard checks your HTTP security headers daily and sends an email alert the moment any header disappears or its value changes โ€” HSTS, CSP, X-Frame-Options, and more.

Add your domain once. Get notified before a bug bounty hunter or security audit finds it for you.

Join the HeaderGuard waitlist โ†’ Check your headers free

HSTS values reference

ValueWhat it meansRecommendation
max-age=31536000Cache for 1 yearโœ… Good
max-age=15768000Cache for 6 monthsโœ… Minimum for SSL Labs A+
max-age=3600Cache for 1 hourโš ๏ธ Too short for meaningful protection
max-age=0Revoke HSTSโš ๏ธ Only use intentionally during migration
includeSubDomainsApply to all subdomainsโœ… Include unless subdomains need HTTP
preloadEligible for browser preload listโœ… Add and submit to hstspreload.org

Check your current HSTS configuration with the free security headers checker โ€” it shows exactly which directive values are set and flags anything missing or misconfigured.