How to Disable HSTS Headers in Apache 2.4 When Not Using SSL/TLS


2 views

I recently encountered a puzzling situation where Apache 2.4 was sending HSTS (HTTP Strict Transport Security) headers even though:

  • I wasn't using SSL/TLS at all
  • mod_headers and mod_ssl weren't enabled
  • No HSTS directives existed in any config files
$ curl -I http://django.dev
HTTP/1.1 200 OK
Date: Mon, 15 Mar 2021 14:28:51 GMT
Server: Apache/2.4.29 (Ubuntu)
Strict-Transport-Security: max-age=31536000; includeSubDomains

After extensive debugging, I discovered several potential sources:

  1. Browser cache: Modern browsers cache HSTS policies
  2. System-wide headers: Some Linux distros enable security headers by default
  3. Residual configs: Previous SSL configurations might leave traces

For Apache 2.4, explicitly disable HSTS by adding this to your VirtualHost:

<VirtualHost *:80>
    ServerName django.dev
    # ... other configs ...
    
    # Disable HSTS explicitly
    Header always unset Strict-Transport-Security
    
    # Alternative if above doesn't work
    Header set Strict-Transport-Security "max-age=0;"
</VirtualHost>

After making changes:

$ sudo apachectl configtest
$ sudo systemctl reload apache2
$ curl -I http://django.dev | grep -i strict-transport
# Should return empty

If you still see HSTS behavior in browsers:

  • Chrome/Edge: Visit chrome://net-internals/#hsts and delete domain
  • Firefox: Clear HSTS state in about:config
  • Safari: Reset all website data

Recently while setting up a development environment using Vagrant with Ubuntu 18.04 and Apache 2.4.29, I encountered an unexpected HSTS header being sent in responses to my virtual host requests. Here's what my configuration looked like:


    ServerName django.dev
    ServerAlias www.django.dev
    ServerAdmin webmaster@localhost
    DocumentRoot /var/www/

    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined

Despite having no SSL configuration and mod_ssl being disabled, Apache was still sending this header:

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

After extensive debugging, I discovered several potential sources for HSTS headers in Apache:

  • Browser preload lists (especially for .dev domains)
  • Apache modules like mod_headers or mod_security
  • Global Apache configuration files
  • Browser cache from previous HTTPS visits

The key revelation was that Google has included all .dev domains in Chrome's HSTS preload list since 2017. This means:

  1. Browsers automatically redirect .dev to HTTPS
  2. They enforce HSTS even without server headers
  3. This behavior persists even when using local DNS

Option 1: Change your development domain
Use something other than .dev (like .test or .localhost):

ServerName django.test
ServerAlias www.django.test

Option 2: Force HTTP in your VirtualHost
Add header modification:


    # ... existing config ...
    Header always set Strict-Transport-Security "max-age=0;"

Option 3: Clear browser HSTS cache
For Chrome:

  1. Visit chrome://net-internals/#hsts
  2. Delete domain security policies for your .dev domain

Use curl to test without browser interference:

curl -I http://django.dev

Should return no HSTS header after implementing the solutions.

When setting up new development environments:

  • Avoid using .dev, .foo, or other TLDs in browser preload lists
  • Use .test (RFC 2606 reserved) or .localhost
  • Document your domain choice in project README
# Recommended development domain format
ServerName projectname.localhost
ServerAlias www.projectname.localhost