How to Configure Apache to Use HSTS

How to configure Apache to use HSTS

A few years ago I audited a client’s site that had SSL properly installed, yet a simple test showed their visitors were still vulnerable to SSL-stripping attacks on public Wi-Fi. The fix was a single header: HSTS. In this post, I’ll explain what HSTS is, why it matters, and exactly how I configure it on Apache.

What Is HSTS?

HTTP Strict Transport Security (HSTS) is a response header that tells browsers “always connect to this site over HTTPS, never HTTP, for the next X seconds.” Once a browser has seen this header, it will automatically upgrade any future HTTP request to HTTPS before it even leaves the browser — closing the window that attackers exploit in man-in-the-middle and SSL-stripping attacks.

Without HSTS, even if you redirect HTTP to HTTPS server-side, the very first request a user makes over HTTP is still vulnerable, because that redirect itself can be intercepted.

Why I Enable HSTS on Every Site I Manage

  • Prevents SSL-stripping attacks on unsecured networks like public Wi-Fi
  • Removes the vulnerable first request by having the browser upgrade automatically after the first visit
  • Improves security audit and compliance scores (PCI-DSS, security headers scanners, etc.)
  • Signals to browsers and search engines that you’re serious about security

Prerequisites

  • A valid SSL/TLS certificate already installed and working (HSTS assumes HTTPS is already functioning correctly)
  • Apache 2.4+ with mod_headers enabled
  • Root or sudo access
  • Confidence that your entire site — and all subdomains you plan to include — genuinely work over HTTPS before you flip this on

I can’t stress that last point enough: HSTS instructs the browser to refuse HTTP entirely for the specified duration. If any part of your site is broken over HTTPS, get that fixed first.

Step 1: Confirm mod_headers Is Enabled

sudo a2enmod headers
sudo systemctl restart apache2

On CentOS/RHEL, mod_headers is typically loaded by default; verify with:

httpd -M | grep headers

Step 2: Add the HSTS Header

I add this inside the SSL virtual host block — never in the plain HTTP block, since HSTS should only ever be sent over an already-secure connection:

<VirtualHost *:443>
    ServerName example.com
    # ... existing SSL configuration ...

    <IfModule mod_headers.c>
        Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
    </IfModule>
</VirtualHost>

Let’s break down that header:

  • max-age=31536000 — tells the browser to remember this policy for 31,536,000 seconds (one year)
  • includeSubDomains — applies the policy to all subdomains, not just the exact domain

Step 3: Start With a Shorter max-age While Testing

I never jump straight to a one-year max-age on a domain I haven’t tested. My rollout process:

  1. Start with a short duration, like max-age=300 (5 minutes), and confirm everything — including subdomains — genuinely works over HTTPS.
  2. Gradually increase to max-age=86400 (1 day), then max-age=604800 (1 week).
  3. Once confident, move to the full max-age=31536000 (1 year).
Header always set Strict-Transport-Security "max-age=300; includeSubDomains"

Step 4: Force HTTP to HTTPS Redirects

HSTS only works after the browser has seen the header at least once, so you still need a server-side redirect for that very first visit:

<VirtualHost *:80>
    ServerName example.com
    Redirect permanent / https://example.com/
</VirtualHost>

Step 5: Consider the HSTS Preload List

For maximum protection, browsers maintain a hardcoded “preload list” of domains that should always use HTTPS, even before the first visit. To qualify for Chrome’s HSTS preload list, add the preload directive:

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

Once your header has been live with max-age of at least one year, includeSubDomains, and preload for a reasonable period, you can submit your domain at hstspreload.org. I only recommend this step once you’re fully confident every subdomain works over HTTPS permanently, because removal from the preload list can take months to propagate.

Step 6: Test Your Configuration

After restarting Apache, I always verify the header is actually present:

curl -sI https://example.com | grep -i strict-transport-security

I also run the domain through SSL Labs’ SSL Server Test and securityheaders.com to confirm the configuration scores well and nothing is misconfigured.

Real-World Use Case

For a fintech client, adding HSTS with preload was actually a compliance requirement from their payment processor’s security review. After implementing it — following the gradual max-age rollout — their SSL Labs score moved from an A to an A+, and the preload submission was approved within a few weeks.

Common Mistakes to Avoid

  • Enabling HSTS before confirming HTTPS works everywhere, including subdomains covered by includeSubDomains. This can lock out users from parts of your site that aren’t SSL-ready.
  • Jumping straight to a one-year max-age without testing — mistakes become very hard to undo once browsers cache the policy.
  • Forgetting the server-side HTTP-to-HTTPS redirect, which is still needed for first-time visitors.
  • Submitting to the preload list prematurely, since removal is slow and affects every subdomain.

Troubleshooting Tips

If users report being unable to access your site after enabling HSTS, check whether any subdomain is missing a valid SSL certificate — includeSubDomains applies the policy universally. If you need to back out of HSTS entirely, set max-age=0, deploy it, and wait for the max-age window to expire in visitors’ browsers before removing the header completely.

Security and Performance Best Practices

  • Combine HSTS with a strong TLS configuration (disable old protocols like TLS 1.0/1.1).
  • Pair it with a Content-Security-Policy header for defense in depth.
  • Monitor certificate expiry closely — an expired cert combined with active HSTS will lock out all visitors until it’s renewed.
  • Use automated certificate renewal (like Certbot) to avoid HSTS-related outages from expired certificates.

Frequently Asked Questions

Can I remove HSTS once it’s enabled? Not immediately for users who’ve already cached the policy — you need to set max-age=0 and wait for the cached duration to expire in each visitor’s browser.

Does HSTS work without a valid SSL certificate? No. HSTS is meaningless without HTTPS already fully functional, since the header itself is only honored over a secure connection.

What’s the difference between HSTS and a simple HTTP-to-HTTPS redirect? A redirect happens server-side after the browser has already made an HTTP request, which can be intercepted. HSTS makes the browser itself refuse to make that insecure request in the first place, after the first visit.

Is includeSubDomains always safe to use? Only if every subdomain, including ones you might add in the future, works over HTTPS. If any subdomain relies on plain HTTP, omit this directive or fix that subdomain first.

Summary and Key Takeaways

HSTS closes a real security gap that a simple HTTP-to-HTTPS redirect leaves open. Enable mod_headers, add the Strict-Transport-Security header inside your SSL virtual host, roll out the max-age gradually, and confirm every subdomain genuinely supports HTTPS before committing to a long duration or the preload list. It’s a small configuration change with an outsized security benefit.

References

Total
1
Shares

Leave a Reply

Previous Post
How to set up HTTPS and SSL certificates for Apache

How to Set Up HTTPS and SSL Certificates for Apache

Next Post
How to protect against common Apache vulnerabilities

How to protect against common Apache vulnerabilities

Related Posts