How to Set Up HSTS (HTTP Strict Transport Security)

Share
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 forces browsers to use HTTPS only. Add the header Strict-Transport-Security: max-age=31536000; includeSubDomains after verifying your site works over HTTPS. Start with a short max-age (300 seconds) for testing. Only use includeSubDomains if all subdomains support HTTPS. Consider preload list submission for maximum security.

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. This is called a "protocol downgrade attack" or "SSL stripping." HSTS prevents this by making browsers refuse HTTP connections entirely.

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 on the preload list, removal is difficult and takes months. Your domain will be hardcoded in browsers to always use HTTPS. Only submit if you're certain your domain and ALL subdomains 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. It prevents downgrade attacks where attackers force HTTP connections to intercept traffic. Without HSTS, even with HTTP->HTTPS redirects, the first request can be intercepted.

What max-age should I use for HSTS?

Start with max-age=300 (5 minutes) for testing. Use max-age=31536000 (1 year) for production. For preload list submission, you need at least 1 year. Never use max-age=0 unless intentionally disabling HSTS.

What happens if I enable HSTS but my HTTPS breaks?

Browsers refuse HTTP connections until max-age expires or you send max-age=0. Start with a short max-age for testing. If on the preload list, HSTS is hardcoded in browsers and cannot be easily disabled.

Should I use includeSubDomains?

Only if ALL subdomains support HTTPS. This applies HSTS to every subdomain, including future ones. If any subdomain doesn't support HTTPS, users can't access it.

What is the HSTS preload list?

A list of domains hardcoded into browsers to always use HTTPS. Browsers never attempt HTTP for preloaded domains, even on first visit. Requires 1-year max-age, includeSubDomains, and preload directive.

Check Your HSTS Configuration

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

Start Free Scan
How-To Guides

How to Set Up HSTS (HTTP Strict Transport Security)