Introduction

HTTP Strict Transport Security (HSTS) tells browsers to always use HTTPS for your domain, preventing downgrade attacks and cookie hijacking. Once a browser receives the HSTS header, it won't attempt HTTP connections for the specified duration. Misconfigured HSTS can cause redirect loops, lock users into HTTPS even when certificates expire, and complicate testing. Proper configuration is essential for security without operational problems.

Symptoms

  • HSTS warning in browser: Strict-Transport-Security header not found
  • SSL Labs shows: Strict Transport Security (HSTS) not implemented
  • Users cannot access site via HTTP even when needed
  • Certificate problems cause complete site unavailability (no HTTP fallback)
  • Redirect loops when HSTS conflicts with redirect configuration
  • Subdomains not receiving HSTS protection
  • Preload submission rejected

Common Causes

  • HSTS header not sent at all
  • Max-age too short or too long
  • Missing includeSubDomains directive
  • preload directive missing for HSTS preload list
  • HSTS on HTTP responses (ignored by browsers)
  • Multiple HSTS headers with conflicting values
  • HSTS set on error pages (not cached properly)
  • Certificate issues while HSTS is active (no bypass possible)

Step-by-Step Fix

Step 1: Check Current HSTS Configuration

```bash # Check if HSTS header is present curl -I https://example.com | grep -i "Strict-Transport-Security"

# Should show something like: # strict-transport-security: max-age=31536000; includeSubDomains; preload

# Check via SSL Labs # https://www.ssllabs.com/ssltest/analyze.html?d=example.com

# Check HSTS preload status # https://hstspreload.org/?domain=example.com ```

Step 2: Configure HSTS in Web Server

Nginx HSTS configuration:

```nginx server { listen 443 ssl; server_name example.com;

# HSTS header - 1 year duration add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

# Important: use 'always' to send header on all responses # Not just successful responses

ssl_certificate /etc/ssl/certs/example.com.crt; ssl_certificate_key /etc/ssl/private/example.com.key; }

# HTTP redirect server (no HSTS here - invalid on HTTP) server { listen 80; server_name example.com; return 301 https://$host$request_uri; } ```

bash
nginx -t && systemctl reload nginx

Apache HSTS configuration:

```apache <VirtualHost *:443> ServerName example.com SSLEngine on

# HSTS header Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"

SSLCertificateFile /etc/ssl/certs/example.com.crt SSLCertificateKeyFile /etc/ssl/private/example.com.key </VirtualHost> ```

bash
apachectl configtest && systemctl reload apache2

HAProxy HSTS configuration:

```haproxy frontend https bind *:443 ssl crt /etc/ssl/certs/example.com.pem

# HSTS via rspadd http-response set-header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" ```

Step 3: Understand HSTS Directives

```bash # max-age: Duration in seconds browser remembers HSTS # 31536000 = 1 year (recommended for production) # 300 = 5 minutes (for testing) # 86400 = 1 day (for gradual rollout)

# includeSubDomains: Apply HSTS to all subdomains # Recommended - prevents subdomain downgrade attacks

# preload: Request inclusion in browser preload lists # Chrome, Firefox, Safari, Edge include preloaded domains # First visit doesn't need to receive HSTS header

# Example values: # Basic: max-age=31536000 # With subdomains: max-age=31536000; includeSubDomains # Full preload-ready: max-age=31536000; includeSubDomains; preload ```

Step 4: Test HSTS Before Full Deployment

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

# This lets you revert if problems found # After testing successful, increase to 1 year

# Test redirect behavior curl -I http://example.com # Should redirect to HTTPS

# Check HSTS header received curl -I https://example.com | grep Strict-Transport-Security ```

Step 5: Submit for HSTS Preload

```bash # Requirements for preload: # 1. max-age at least 31536000 (1 year) # 2. includeSubDomains directive present # 3. preload directive present # 4. Working HTTPS on domain and all subdomains # 5. HTTP redirects to HTTPS on same host

# Submit at: # https://hstspreload.org/

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

# Note: Preload removal is slow (months) # Be certain before submitting ```

Step 6: Handle HSTS Issues During Certificate Problems

```bash # When HSTS is active and certificate has issues: # Users cannot bypass the warning (no HTTP fallback)

# Solutions: # 1. Fix certificate quickly (primary solution) # 2. Clear HSTS in browser (user action): # Chrome: chrome://net-internals/#hsts # Firefox: clear HSTS data in about:preferences # Safari: Clear browser data

# Prevent this scenario: # - Test certificates before deployment # - Set up certificate expiration monitoring # - Keep certificate renewal automated ```

Step 7: Verify HSTS Configuration

```bash # Check header is sent on all responses curl -I https://example.com/200 | grep Strict-Transport-Security curl -I https://example.com/404 | grep Strict-Transport-Security curl -I https://example.com/500 | grep Strict-Transport-Security

# All should show HSTS header (if using 'always')

# Check subdomains curl -I https://sub.example.com | grep Strict-Transport-Security curl -I https://api.example.com | grep Strict-Transport-Security

# SSL Labs HSTS check # https://www.ssllabs.com/ssltest/ ```

Step 8: Monitor and Maintain HSTS

```bash # Set up monitoring for HSTS header presence # Regular SSL Labs scans

# Keep certificate management robust # HSTS + expired cert = complete outage

# Document HSTS settings for your domain # Team needs to understand implications

# Review preload status periodically # Removal from preload is difficult ```

Common Pitfalls

  • Setting HSTS on HTTP responses (browsers ignore it)
  • Using too short max-age initially, then forgetting to increase
  • Not using 'always' directive, header missing on error pages
  • HSTS active but certificate expires = users locked out
  • Submitting preload before confirming all subdomains work
  • Multiple HSTS headers with different values
  • Removing preload is slow (months), not instant

Best Practices

  • Start with short max-age (days) during testing
  • Use 'always' directive to send header on all responses
  • Include includeSubDomains for comprehensive protection
  • Ensure robust certificate management before HSTS
  • Test thoroughly before requesting preload
  • Document HSTS implications for operations team
  • Monitor HSTS header presence regularly
  • Have plan for HSTS-related incidents
  • SSL Redirect Loop
  • HTTP to HTTPS Redirect Not Working
  • SSL Certificate Expired
  • SSL Mixed Content Warning