HSTS: enforcing HTTPS at the browser level in WordPress

HSTS tells the browser never to request the site over HTTP again. An essential layer beyond the standard redirect.

HSTS, short for HTTP Strict Transport Security, is an HTTP response header that tells the browser: 'For the next year, request this site only over HTTPS, even if the user typed http on purpose'. It is a critical layer above the standard 301 redirect - not a replacement, but a complement.

Why this matters

Forcing HTTPS via a 301 redirect alone leaves a vulnerability window on the very first request. Picture this scenario: a user at a coffee shop joins the cafe Wi-Fi and types 'example.com' in the address bar. The browser sends an HTTP request. An attacker on the same Wi-Fi (an SSL-stripping attack) intercepts, returns your page without a redirect to HTTPS, and keeps the session on HTTP - so they can read login cookies. With HSTS, if the user has visited the site at least once before, the browser remembers the directive and never even sends the HTTP request - it goes straight to HTTPS, denying the attacker any window. A user with a stale http:// bookmark is also protected: the next click silently upgrades. HSTS also prevents the user from clicking through a certificate warning - once HSTS is active, the browser refuses to bypass.

How to detect

The audit issues a HEAD against the homepage and reads the Strict-Transport-Security header. Missing = red. Even if present, max-age below 2592000 (30 days) is considered weak. Manually:

curl -I https://example.com | grep -i strict
should return something like: 'Strict-Transport-Security: max-age=31536000; includeSubDomains'. securityheaders.com gives a graded report.

How to fix

  1. Prerequisite: verify the site has been on stable HTTPS for at least two weeks with no mixed-content issues. Enabling HSTS on a fragile site causes the browser to block resources still served over HTTP.
  2. Start with a short max-age to confirm: 300 seconds (5 minutes). Add the header.
  3. Verify it appears in curl. Verify the site still works.
  4. Then raise to 31536000 (one year).
  5. Apache in .htaccess at the document root:
    <IfModule mod_headers.c>
        Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
    </IfModule>
  6. Nginx inside the server block:
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
  7. Add includeSubDomains only when you are sure every subdomain is also HTTPS. If blog.example.com still runs HTTP, it will break.
  8. Verify the header is sent: curl -I https://example.com | grep -i strict.
  9. Consider submitting to the Chrome HSTS preload list at hstspreload.org - that requires the preload directive in the header. This is nearly irreversible: removal from the preload list takes months.

Common mistakes

Do not enable HSTS before HTTPS is stable - browsers will block the site until max-age expires, even if you remove the header. Do not enable preload on day one - it requires 1) max-age >= 31536000, 2) includeSubDomains, 3) the preload directive, 4) HTTPS on every subdomain. Do not enable includeSubDomains without verifying every subdomain is ready. Do not end the value with a stray colon - bad syntax burns hours of debugging.

Verifying the fix

Run curl -I https://example.com - the header should be present. Test on securityheaders.com - your grade should rise to A or A+. Open http://example.com - the browser should upgrade to HTTPS before the request even reaches the server (Chrome DevTools shows 'HSTS internal redirect').

Tip: Do not enable preload until you are 100% sure. The preload list is shipped with Chrome, Firefox and Safari. Reverting takes months, during which some users cannot reach your site until their browser cache clears.