HSTS Redirects; WWW to non-WWW and HTTP to HTTPS

May 6, 2015 (updated September 28, 2019)

HSTS (HTTP Strict Transport Security) is a web security measure that forces all communication between a web server (a specific domain) and a conforming client (e.g. a browser) to be sent over secure HTTPS connections.

This is achieved with the use of a response header field named Strict-Transport-Security:

Strict-Transport-Security: max-age=31536000

The above header tells the client (the web browser) that all subsequent communication for the next 31536000 seconds (~1 year) should happen over HTTPS. The next time you try to visit the site through HTTP (e.g. http://example.com), your browser will automatically redirect you to the HTTPS version (https://example.com) before your request even hits the web server. All links and references to http://example.com will be translated at the client side to https://example.com.

According to the RFC, the Strict-Transport-Security header must only be sent with secure HTTPS responses:

An HSTS Host MUST NOT include the STS header field in HTTP responses conveyed over non-secure transport.

This means that responses from http://example.com should not include the header.

At the time of writing HSTS is supported by all real browsers (IE 11 added support in an update in June 2015).

Redirecting HTTP to HTTPS at the server side

A common practice is to configure the web server to redirect http://example.com to https://example.com. Then, as soon as your browser retrieves a response from https://example.com, the HSTS header is sent and all subsequent references and requests to http://example.com are translated at the client side to https://example.com.

However, the initial redirect is still insecure as https://example.com has not yet sent the HSTS header. In order to circumvent the insecure redirect you can add your domain to the HSTS preload list maintained by Google. This is a list of HTTPS-only sites, and it's hardcoded into Google Chrome and other major browsers. Browsers are instructed to go straight to the HTTPS-version of the sites listed, and translate all references to the sites to HTTPS.

Canonical redirects (www to non-www or vice versa)

Many web servers are configured to redirect traffic to a canonical URL, e.g. from www.example.com to example.com or vice versa.

It's important to note that the HSTS policy only applies for the host (domain) that sends the Strict-Transport-Security header. If https://example.com sends the header, the policy applies only to example.com, not to www.example.com. Thus access to www.example.com will not result in a secure redirect to HTTPS; it will merely hit the server side redirect, if configured, which is not secure.

That is, https://example.com and https://www.example.com do not set HSTS for each other.

Let's look at a scenario where the web server has been configured to redirect all non-canonical and non-HTTPS traffic to the canonical HTTPS URL https://example.com. The redirect rules are as follows:

  • http://www.example.com → https://example.com
  • http://example.com → https://example.com
  • https://www.example.com → https://example.com

In this scenario only https://example.com has been configued to send the Strict-Transport-Security header.

What will happen?

  1. http://example.com responds with a 301 redirect to https://example.com which sets the HSTS headers. The HSTS policy will apply only to example.com.
  2. All subsequent requests or links to http://example.com will be translated to https://example.com
  3. Requests to http://www.example.com will not be translated to https://www.example.com because www.example.com doesn't have a HSTS policy. The server will respond with an insecure 301 redirect to https://example.com every time.

How do we mitigate this?

We should redirect HTTP requests to their HTTPS equivalent before any canonicalization steps (adding or removing "www"), and send the HSTS header from any domain or subdomain that is meant to be only accessible over HTTPS:

The server side redirect scheme becomes:

  • http://example.com → https://example.com*
  • http://www.example.com → https://www.example.com* → https://example.com*
  • https://www.example.com → https://example.com*

The asterisk (*) denotes which hosts sends the HSTS header.

Now we've secured both the users who access your site through example.com and those who access your site through www.example.com; both domains have the HSTS policy applied.

But we haven't secured the users who first access http://example.com, and later access http://www.example.com; the redirect from http://www.example.com to https://www.example.com is insecure because the HSTS policy has not yet been set for www.example.com for these users.

To set the HSTS policy for both domains we can use the includeSubDomains token in the HSTS header:

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

includeSubDomains specifies that this HSTS policy also applies to any subdomain of the HSTS issuing host's domain, i.e. the domain and all of its subdomains. However, it may not be desirable to use HSTS on all subdomains, especially if some subdomains are supposed to be accessible with plain HTTP. There are other implications as well, some of which are described in the RFC.

In addition, if your canonical URL is www.example.com, the includeSubDomains token will not protect example.com as this is not a subdomain of www.example.com. A solution is to make a request from www.example.com to an uncached resource on https://example.com, e.g. a 1px image, and make sure that https://example.com sets the HSTS header. Thus both www.example.com and example.com would have an HSTS policy in effect.

NGINX example implementation

This website will be served over HTTPS. Its canonical domain is example.com without the www prefix.

# HTTP traffic is redirected to the HTTPS equivalent.
# No canonicalization steps are taken yet.
server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;
    return 301 https://$host$request_uri;
}

# HTTPS traffic to the non-canonical domain www.example.com is redirected to
# the canonical domain example.com. HSTS headers are set and ensure that
# subsequent traffic to www.example.com is served over HTTPS.
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    # Your standard SSL configuration has been omitted for brevity ...

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

    server_name www.example.com;

    return 301 https://example.com$request_uri;
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    # Your standard SSL configuration has been omitted for brevity ...

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

    server_name example.com;
}

Testing HSTS policy

If you're running Google Chrome, visit chrome://net-internals/#hsts to review the HSTS policy of different domains.