How to Set Up HSTS (HTTP Strict Transport Security)

How-To Guide

How to Set Up HSTS (HTTP Strict Transport Security)

Force HTTPS connections and prevent downgrade attacks

TL;DR

TL;DR (15 minutes)

HSTS tells browsers to skip HTTP entirely. Add Strict-Transport-Security: max-age=31536000; includeSubDomains once you've confirmed HTTPS works on your whole site. Start with max-age=300 for testing so you can increase it safely. Don't use includeSubDomains unless every subdomain is already on HTTPS, and don't submit to the preload list until you're sure you'll never need HTTP again.

Prerequisites

  • Valid SSL/TLS certificate installed (Let's Encrypt, commercial CA, etc.)
  • Your site accessible via HTTPS without certificate errors
  • All resources (images, scripts, styles) loaded over HTTPS
  • HTTP to HTTPS redirect already configured
  • If using includeSubDomains: all subdomains must support HTTPS

What is HSTS?

HSTS (HTTP Strict Transport Security) tells browsers to only connect to your site using HTTPS, even if a user types http:// or clicks an HTTP link.

Why HSTS Matters

Without HSTS, an attacker can intercept the initial HTTP request before your redirect happens. That's SSL stripping: your server sends a 301 to HTTPS, but the plaintext request is already exposed. HSTS stops this. Browsers refuse HTTP before sending a single byte, with no round trip to your server.

HSTS Header Syntax

Strict-Transport-Security: max-age=<seconds>; includeSubDomains; preload
DirectiveRequiredDescription
max-ageYesHow long (in seconds) browsers should remember to use HTTPS
includeSubDomainsNoApply HSTS to all subdomains (*.example.com)
preloadNoConsent to being added to browser preload lists

Common max-age Values

ValueDurationUse Case
3005 minutesInitial testing
864001 dayExtended testing
6048001 weekStaging/verification
315360001 yearProduction (recommended)
630720002 yearsMaximum security

Step-by-Step Implementation

1

Verify HTTPS is Working Properly

Before enabling HSTS, ensure your site is fully HTTPS-ready:

# Check SSL certificate
curl -vI https://yoursite.com 2>&1 | grep -A 6 "Server certificate"

# Check for mixed content (HTTP resources on HTTPS page)
# Open Chrome DevTools > Console > Look for "Mixed Content" warnings

# Verify redirect is in place
curl -I http://yoursite.com
# Should return 301/302 redirect to https://

Pre-flight Checklist

  • SSL certificate is valid and not expiring soon
  • All pages accessible via HTTPS
  • No mixed content warnings in browser console
  • HTTP to HTTPS redirect working
  • All subdomains support HTTPS (if using includeSubDomains)
2

Start with a Short max-age for Testing

Add HSTS with a 5-minute max-age to test safely:

nginx:

server {
    listen 443 ssl http2;
    server_name yoursite.com;

    # HSTS - Start with short max-age for testing
    add_header Strict-Transport-Security "max-age=300" always;

    # ... rest of config
}

Apache:

<VirtualHost *:443>
    ServerName yoursite.com

    # HSTS - Start with short max-age for testing
    Header always set Strict-Transport-Security "max-age=300"

    # ... rest of config
</VirtualHost>

Express.js:

const helmet = require('helmet');

// HSTS with short max-age for testing
app.use(helmet.hsts({
  maxAge: 300, // 5 minutes
  includeSubDomains: false,
  preload: false
}));

Vercel (vercel.json):

{
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        {
          "key": "Strict-Transport-Security",
          "value": "max-age=300"
        }
      ]
    }
  ]
}

Netlify (_headers):

/*
  Strict-Transport-Security: max-age=300
3

Verify the Header is Being Sent

# Check HSTS header with curl
curl -I https://yoursite.com | grep -i strict-transport-security

# Expected output:
# strict-transport-security: max-age=300

Or check in browser DevTools:

  1. Open DevTools (F12)
  2. Go to Network tab
  3. Reload the page
  4. Click on the document request
  5. Find strict-transport-security in Response Headers
4

Test HSTS Behavior

Verify browsers redirect HTTP to HTTPS without contacting your server:

  1. Visit your site via HTTPS to receive the HSTS header
  2. Type http://yoursite.com in the address bar
  3. The browser should redirect to HTTPS immediately (internal redirect)
  4. Check Network tab - you should see a "307 Internal Redirect"

Check HSTS status in Chrome:

# Visit this URL in Chrome
chrome://net-internals/#hsts

# Query your domain to see HSTS status
# Delete the entry if you need to reset during testing
5

Increase max-age for Production

Once verified, increase to 1 year (required for preload list):

nginx:

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

Apache:

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

Express.js:

app.use(helmet.hsts({
  maxAge: 31536000, // 1 year
  includeSubDomains: true,
  preload: false // Don't add preload yet
}));

Vercel:

{
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        {
          "key": "Strict-Transport-Security",
          "value": "max-age=31536000; includeSubDomains"
        }
      ]
    }
  ]
}

Netlify:

/*
  Strict-Transport-Security: max-age=31536000; includeSubDomains
6

Submit to HSTS Preload List (Optional)

For maximum security, submit your domain to the browser preload list:

Requirements for preload:

  • Serve a valid SSL certificate
  • Redirect all HTTP to HTTPS on the same host
  • All subdomains must support HTTPS
  • HSTS header with: max-age of at least 1 year (31536000)
  • HSTS header with: includeSubDomains directive
  • HSTS header with: preload directive

Update your HSTS header for preload:

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

Submit at: hstspreload.org

Preload Warning

Once you're on the preload list, getting off it takes months. Removal goes through a formal process and then has to ship in browser releases. Don't submit unless you're genuinely certain your domain and every subdomain will support HTTPS permanently.

HSTS Security Checklist

  • SSL certificate is valid and set to auto-renew
  • Tested with short max-age first before production values
  • All subdomains support HTTPS (if using includeSubDomains)
  • No planned migration to HTTP-only subdomains
  • Have a process for SSL certificate renewal
  • Understand preload list implications before submitting

How to Verify It Worked

Method 1: Command Line

# Check HSTS header
curl -sI https://yoursite.com | grep -i strict-transport-security

# Expected output:
strict-transport-security: max-age=31536000; includeSubDomains

Method 2: Browser DevTools

  1. Open DevTools (F12) > Network tab
  2. Reload your HTTPS site
  3. Click on the document request
  4. Check Response Headers for strict-transport-security

Method 3: Check Internal Redirect

  1. First, visit your HTTPS site to cache the HSTS policy
  2. Open DevTools > Network tab
  3. Type http://yoursite.com in address bar
  4. You should see "307 Internal Redirect" (not a server redirect)

Method 4: Chrome HSTS Inspector

# Open in Chrome
chrome://net-internals/#hsts

# In "Query HSTS/PKP domain" section:
# Enter your domain and click "Query"
# Should show "Found" with your max-age

Method 5: Online Scanner

Use securityheaders.com to verify HSTS is correctly configured.

Common Errors and Troubleshooting

HSTS header not appearing

  • Not on HTTPS: HSTS header is only sent over HTTPS, not HTTP
  • CDN stripping headers: Check CDN configuration for header passthrough
  • Server config not reloaded: Restart nginx/Apache after changes

Can't access site after enabling HSTS

  • Certificate expired: Renew your SSL certificate immediately
  • Clear HSTS cache: In Chrome, go to chrome://net-internals/#hsts and delete the entry
  • Wait for max-age: If using a long max-age, you may need to wait or clear browser data

Subdomain not accessible

  • includeSubDomains issue: If you used includeSubDomains but a subdomain doesn't support HTTPS
  • Solution: Add HTTPS to the subdomain, or remove includeSubDomains from the main domain
  • Clear HSTS cache: Users who visited before need to clear their HSTS cache

Can't remove from preload list

  • Long process: Submit removal request at hstspreload.org
  • Takes months: Changes propagate with browser releases
  • Send max-age=0: While waiting, send max-age=0 to clear client caches

Pro Tip: Gradual HSTS Rollout

Increase max-age gradually: 5 min -> 1 day -> 1 week -> 1 month -> 1 year. This gives you time to identify issues before browsers cache a long-lived policy. Monitor for certificate renewal issues and subdomain problems at each stage.

Frequently Asked Questions

What is HSTS and why do I need it?

HSTS tells browsers to only connect via HTTPS. Without it, an attacker can intercept the very first HTTP request before your redirect fires. That initial request is the hole. HSTS closes it by making browsers refuse HTTP before a connection is even attempted.

What max-age should I use for HSTS?

Start with max-age=300 (5 minutes) for testing, then ramp up to max-age=31536000 (1 year) for production. The preload list requires at least 1 year. Don't use max-age=0 unless you're intentionally disabling HSTS.

What happens if I enable HSTS but my HTTPS breaks?

Browsers won't make HTTP connections until max-age expires or you explicitly send max-age=0. That's why you start with a short max-age. If you're on the preload list, it's worse: the browser has your domain hardcoded and you can't override it from a server header.

Should I use includeSubDomains?

Only if all subdomains support HTTPS, including ones you haven't created yet. That's the catch: includeSubDomains covers every *.yourdomain.com, so spinning up an HTTP-only staging subdomain months later will break it for anyone with the HSTS policy cached.

What is the HSTS preload list?

A list of domains hardcoded directly into Chrome, Firefox, Safari, and Edge to always use HTTPS. Unlike a normal HSTS header, the protection kicks in on the very first visit before your server has ever responded. Requirements: max-age of at least 1 year, includeSubDomains, and the preload directive.

Check Your HSTS Configuration

Scan your site to verify HSTS is properly configured and check for other security headers.

How-To Guides

How to Set Up HSTS (HTTP Strict Transport Security)