Documentation

HSTS: HTTP Strict Transport Security

ESSENTIAL SECURITY RFC 6797 MANDATORY FOR HTTPS

Critical HTTPS Protection

HSTS is a mandatory security header for all HTTPS websites. It protects against protocol downgrade attacks and SSL stripping, vulnerabilities that affect even sites with valid HTTPS certificates.

What is HSTS?

SECURITY POLICY

Simple Definition: HSTS (HTTP Strict Transport Security) is a security policy mechanism that forces browsers to only interact with a website using HTTPS, preventing protocol downgrade attacks and cookie hijacking.

What HSTS Does:

  • Forces HTTPS connections only (never HTTP)
  • Blocks protocol downgrade attacks (SSL stripping)
  • Protects cookies from being sent over HTTP
  • Works even for first-time visitors (with preload)

Real-World Analogy

Think of HSTS like a "members only" club that stamps your hand:

  • First visit: Club stamps your hand (browser saves HSTS policy)
  • Future visits: Bouncer (browser) won't let you use the back door (HTTP)
  • You MUST always use the secure front entrance (HTTPS)
  • The stamp lasts for a specified time (max-age parameter)
  • No exceptions - even if you try to use HTTP, browser upgrades to HTTPS

RFC 6797 STANDARD

HSTS is an IETF standard (RFC 6797) adopted in 2012, now supported by all major browsers.


The Problem HSTS Solves

SSL STRIPPING ATTACK

Critical Vulnerability Without HSTS

Even websites that fully support HTTPS are vulnerable to SSL stripping attacks. An attacker on the network can intercept the initial HTTP request before it's upgraded to HTTPS, stealing credentials and session cookies.

The Vulnerability: Why HTTPS Alone Isn't Enough

Even when a website supports HTTPS, users can still be vulnerable to these attacks:

SSL Stripping - Downgrade HTTPS to HTTP Cookie Hijacking - Steal session cookies sent over HTTP Man-in-the-Middle - Intercept initial connection Protocol Downgrade - Force use of insecure HTTP

Step 1: User Types URL

User types: http://bank.com
(Forgot the 's' in https)

Step 2: Initial HTTP Request

Browser → Server: GET / HTTP/1.1
                  Host: bank.com

Step 3: Attacker Intercepts

┌─────────┐     HTTP     ┌──────────┐     HTTPS    ┌────────┐
│ Browser │ ──────────> │ Attacker │ ──────────> │ Server │
└─────────┘  (Plaintext) └──────────┘  (Encrypted) └────────┘
                              ↑
                         Steals data!

What Attacker Sees:

  • Cookies (session tokens)
  • Passwords (if submitted)
  • Sensitive data

Common Vulnerable Scenarios

  1. User Types HTTP

    http://bank.com → 301 Redirect → https://bank.com
    

    First request is vulnerable!

  2. Old Bookmarks

    Bookmark from 2015: http://example.com
    Still goes to HTTP first
    
  3. Mixed Content Links

    html<a href="http://bank.com/login">Login</a>
    

    Clicking link uses HTTP

  4. DNS Hijacking

    Attacker intercepts DNS → Points to attacker's server
    

How HSTS Works

The HSTS Header

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

Parameters:

Parameter Value Purpose
max-age Seconds How long browser remembers (31536000 = 1 year)
includeSubDomains Optional Apply to all subdomains
preload Optional Submit to browser's built-in list

HSTS Flow

User types: http://bank.com

┌─────────┐                           ┌────────┐
│ Browser │                           │ Server │
└────┬────┘                           └────┬───┘
     │                                     │
     │ 1. GET / HTTP/1.1                  │
     │────────────────────────────────────>│
     │                                     │
     │ 2. HTTP 301 Moved Permanently      │
     │    Location: https://bank.com       │
     │<────────────────────────────────────│
     │                                     │
     │ 3. GET / HTTP/1.1 (HTTPS)          │
     │────────────────────────────────────>│
     │                                     │
     │ 4. HTTP 200 OK                      │
     │    Strict-Transport-Security:       │
     │    max-age=31536000                 │
     │<────────────────────────────────────│
     │                                     │
     │ Browser remembers:                  │
     │ "Always use HTTPS for bank.com"     │
     └─────────────────────────────────────┘
User types: http://bank.com

┌─────────┐                           ┌────────┐
│ Browser │                           │ Server │
└────┬────┘                           └────┬───┘
     │                                     │
     │ Browser checks HSTS cache:          │
     │ "bank.com → Always HTTPS"           │
     │                                     │
     │ Browser internally upgrades:        │
     │ http:// → https://                  │
     │                                     │
     │ 1. GET / HTTP/1.1 (HTTPS)          │
     │────────────────────────────────────>│
     │                                     │
     │ NO HTTP request sent!               │
     │ Attacker can't intercept!           │
     │                                     │
     │ 2. HTTP 200 OK                      │
     │<────────────────────────────────────│
     └─────────────────────────────────────┘

Key Benefit: NO HTTP traffic, NO interception possible!


Implementation

Basic Configuration

nginxserver {
    listen 443 ssl http2;
    server_name example.com;

    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;

    # Add HSTS header
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

    location / {
        # Your application
    }
}

# Redirect HTTP to HTTPS
server {
    listen 80;
    server_name example.com;
    return 301 https://$server_name$request_uri;
}

HSTS Parameters Explained

max-age

Purpose: How long browsers should remember HSTS policy

Values:

  • max-age=0 - Disable HSTS (removes from cache)
  • max-age=300 - 5 minutes (testing)
  • max-age=86400 - 1 day
  • max-age=2592000 - 30 days
  • max-age=31536000 - 1 year (recommended for production)
  • max-age=63072000 - 2 years (maximum recommended)

Best Practice:

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

# After testing, increase gradually
add_header Strict-Transport-Security "max-age=86400";

# Production: 1 year
add_header Strict-Transport-Security "max-age=31536000";

includeSubDomains

Purpose: Apply HSTS to all subdomains

Example:

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

Applies to:

  • example.com ✓
  • www.example.com ✓
  • api.example.com ✓
  • blog.example.com ✓
  • *.example.com ✓

Warning: ALL subdomains must support HTTPS!

# If you set includeSubDomains:
Strict-Transport-Security: max-age=31536000; includeSubDomains

# But dev.example.com doesn't have HTTPS:
https://dev.example.com → Browser error! ❌

# Users can't access that subdomain anymore!

Solution: Only use includeSubDomains when ALL subdomains have HTTPS.

preload

Purpose: Include domain in browser's built-in HSTS list

Requirement for preload:

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

All three required:

  • max-age ≥ 31536000 (1 year)
  • includeSubDomains present
  • preload directive

HSTS Preload List

MAXIMUM SECURITY

What is HSTS Preload?

Problem: First visit still uses HTTP (vulnerable!)

Solution: Hardcode HTTPS domains into browsers

The List:

  • Maintained by Chromium project
  • Built into Chrome, Firefox, Safari, Edge, IE 11
  • Domains are ALWAYS accessed via HTTPS
  • Protects even the very first visit

How to Submit

1. Visit: https://hstspreload.org

2. Requirements:

  • ✅ Serve valid HTTPS certificate
  • ✅ Redirect all HTTP to HTTPS (port 80 → 443)
  • ✅ Serve HSTS header on all subdomains
  • ✅ HSTS header on base domain with:
    • max-age ≥ 31536000 (1 year)
    • includeSubDomains directive
    • preload directive

3. Configuration Example:

nginx# ALL requirements met:
server {
    listen 443 ssl http2;
    server_name example.com *.example.com;

    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;

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

server {
    listen 80;
    server_name example.com *.example.com;
    return 301 https://$host$request_uri;
}

4. Submit:

1. Go to https://hstspreload.org
2. Enter your domain: example.com
3. Click "Check HSTS preload status and eligibility"
4. If eligible, click "Submit"
5. Wait for review (can take weeks/months)

Preload List Benefits

First Visit Protected - No HTTP request ever sent Maximum Security - Built into browser Long-term Protection - Can't be removed easily

Preload List Warnings

WARNING

Removal is SLOW - Takes months to remove from browsers Permanent Decision - Hard to reverse All Subdomains - Must work for ALL subdomains forever Breaking Changes - Can break HTTP-only subdomains

Only submit when:

  • You're 100% committed to HTTPS
  • All subdomains support HTTPS
  • You understand the implications

Testing HSTS

Browser Developer Tools

Chrome DevTools:

1. Open DevTools (F12)
2. Network tab
3. Look for response headers:
   Strict-Transport-Security: max-age=31536000

Check HSTS Status:

Chrome: chrome://net-internals/#hsts
Firefox: about:config → search "hsts"

Query HSTS Status:

1. Go to chrome://net-internals/#hsts
2. Enter domain in "Query HSTS/PKP domain"
3. Check if domain is in HSTS cache

Delete HSTS Entry (for testing):

1. Go to chrome://net-internals/#hsts
2. Enter domain in "Delete domain security policies"
3. Click "Delete"

Command Line Testing

bash# Check HSTS header
curl -I https://example.com | grep -i strict

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

# Check preload status
curl https://hstspreload.org/api/v2/status?domain=example.com

# Test redirect
curl -I http://example.com

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

Online Testing Tools

SSL Labs:

https://www.ssllabs.com/ssltest/analyze.html?d=example.com
  • Checks HSTS configuration
  • Verifies preload status
  • Grade scoring

Security Headers:

https://securityheaders.com/?q=example.com
  • Analyzes HSTS header
  • Checks max-age value
  • Verifies directives

HSTS Preload:

https://hstspreload.org/
  • Check eligibility
  • View current status
  • Submit for preload

Common HSTS Mistakes

Mistake #1: Setting HSTS on HTTP

nginx# ❌ WRONG: HSTS on HTTP server
server {
    listen 80;
    server_name example.com;

    # This won't work! Browsers ignore HSTS over HTTP
    add_header Strict-Transport-Security "max-age=31536000";

    return 301 https://$host$request_uri;
}

Why Wrong: HSTS must be served over HTTPS. Browsers ignore it on HTTP.

Fix:

nginx# ✅ CORRECT: HSTS only on HTTPS
server {
    listen 443 ssl http2;
    add_header Strict-Transport-Security "max-age=31536000" always;
}

server {
    listen 80;
    return 301 https://$host$request_uri;
}

Mistake #2: Too Short max-age

http# ❌ BAD: Too short
Strict-Transport-Security: max-age=300

Problem: 5 minutes = no real protection

Fix:

http# ✅ GOOD: At least 1 year
Strict-Transport-Security: max-age=31536000

Mistake #3: Using includeSubDomains Without Planning

http# ❌ DANGEROUS: If ANY subdomain lacks HTTPS
Strict-Transport-Security: max-age=31536000; includeSubDomains

Breaking scenario:

https://example.com → Works ✓
https://www.example.com → Works ✓
https://dev.example.com → HTTP only → BROKEN! ❌

Fix: Only use when ALL subdomains have HTTPS.

Mistake #4: Jumping Straight to Preload

http# ❌ RISKY: Immediately going to preload
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

Problem: Hard to reverse!

Better approach:

Week 1:  max-age=300 (5 minutes) - Test
Week 2:  max-age=86400 (1 day) - Monitor
Week 3:  max-age=2592000 (30 days) - Ensure stable
Month 2: max-age=31536000; includeSubDomains
Month 3: Add preload directive and submit

Mistake #5: Not Using 'always' Flag (Nginx)

nginx# ❌ WRONG: Missing 'always'
add_header Strict-Transport-Security "max-age=31536000";

# Header only sent for 200 responses!
# Not sent for 301, 404, 500, etc.

Fix:

nginx# ✅ CORRECT: Add 'always'
add_header Strict-Transport-Security "max-age=31536000" always;

# Header sent for ALL responses

Removing HSTS

Emergency: Remove HSTS

If you need to disable HSTS:

http# Set max-age=0
Strict-Transport-Security: max-age=0

Process:

  1. Update server configuration to send max-age=0
  2. Users visit site over HTTPS
  3. Browser removes HSTS entry
  4. HTTP access possible again

Clearing from Browser:

Chrome:   chrome://net-internals/#hsts
Firefox:  Delete browsing history > Clear site preferences

Removing from Preload List

Much harder!

  1. Update server: Set max-age=0
  2. Submit removal: https://hstspreload.org/removal/
  3. Wait: Can take 3-12 months for browser updates
  4. Users with old browsers: Still see domain as preloaded

Lesson: Only preload when absolutely certain!


HSTS Best Practices

Deployment Checklist

  • [ ] Valid HTTPS certificate installed
  • [ ] All subdomains support HTTPS (if using includeSubDomains)
  • [ ] HTTP → HTTPS redirect configured
  • [ ] Start with short max-age (300 seconds)
  • [ ] Test thoroughly on staging
  • [ ] Monitor logs for issues
  • [ ] Gradually increase max-age
  • [ ] Use 1 year in production (31536000)
  • [ ] Add 'always' flag (Nginx)
  • [ ] Consider includeSubDomains carefully
  • [ ] Only add preload when ready for permanent commitment

Production Configuration

nginx# Production-ready HSTS configuration
server {
    listen 443 ssl http2;
    server_name example.com www.example.com;

    ssl_certificate /path/to/fullchain.pem;
    ssl_certificate_key /path/to/privkey.pem;

    # HSTS header
    add_header Strict-Transport-Security \
        "max-age=31536000; includeSubDomains" always;

    # Other security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;

    location / {
        # Your application
    }
}

# Redirect HTTP to HTTPS
server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://$server_name$request_uri;
}

Security Benefits

Protections Provided

SSL Stripping Attacks - Prevents attacker from downgrading to HTTP Cookie Hijacking - Cookies can't be stolen over HTTP Man-in-the-Middle - Eliminates HTTP interception opportunity Protocol Downgrade - Forces HTTPS at browser level Mixed Content - Helps prevent insecure resources

Real-World Impact

Without HSTS:

Attacker on WiFi → Intercepts HTTP → Steals session cookie → Account compromised

With HSTS:

Browser → Enforces HTTPS → No HTTP traffic → Attacker gets nothing

Next Steps

TLS/SSL Basics - Understanding HTTPS Certificate Management - Managing certificates Cipher Suites - Encryption configuration

Tools & Resources

HSTS Preload - Submit to preload list SSL Labs - Test HSTS configuration Security Headers - Scan security headers


Protected by Layerd AI

Layerd AI Cloud WAF automatically manages HSTS:

HSTS Injection - Automatically adds HSTS headers Configuration Validation - Ensures proper HSTS setup Preload Management - Assists with preload submission Monitoring - Tracks HSTS policy compliance

Learn more about Layerd AI Protection →


Last updated: November 2025