There are two different ways of accessing a website: HTTP and HTTPS. HTTPS is the same as HTTP, except that it uses an encryption technology called TLS (also known as SSL). More and more websites are using HTTPS to keep their user’s browsing history private, to prevent ads or malware from being injected into pages as they are loaded, and to protect confidential data in a user’s account. With the launch of Let’s Encrypt, it’s even free.

Unfortunately, a design flaw in how HTTPS works can still put users at risk, even on websites that use HTTPS on every page. Here’s what happens if I type “www.facebook.com” into the address bar of an old browser without HSTS:

  1. The browser tries to load http://www.facebook.com/.
  2. Facebook’s server responds: Oh, that’s not the URL you want, try https://www.facebook.com/ instead.
  3. The browser tries to load https://www.facebook.com/.
  4. Facebook responds: OK, here’s the page you asked for…

The browser actually performs two requests, and only the second happens over a secure HTTPS connection! Why does this happen? Backwards compatibility. The browser can’t know ahead of time that the server supports HTTPS, so it tries HTTP first, waiting for the server to explicitly tell it “no, use HTTPS instead”.

This is a serious problem. Not only does this waste time and bytes, it makes the website vulnerable to an attack called “SSL stripping”. In an SSL stripping attack, an attacker intercepts and modifies or replaces packets going between the browser and the web server. Here’s how this might look:

  1. The browser tries to load http://www.facebook.com/.
  2. The attacker responds (impersonating Facebook’s server): Sure, here’s a login page that looks exactly like the real Facebook, except that it sends the user name and password to my server (maniacal laughter).

By intercepting the request that happened over insecure HTTP, the attacker can easily impersonate the Facebook server and prevent the browser from ever using HTTPS. Most users won’t even notice that they’re on HTTP instead of HTTPS; they’ll just see “www.facebook.com” and think that everything is fine.

HSTS is the solution to this problem. HSTS lets a website tell the user’s browser to remember the fact that the website can handle HTTPS. Whenever the browser goes to load a page on that site, it will remember the fact that it should use HTTPS. Here’s what happens now when a user clicks on a link to http://www.facebook.com or types in “www.facebook.com”:

  1. The browser remembers that www.facebook.com has HSTS enabled, and automatically replaces http:// with https://.
  2. The browser tries to load https://www.facebook.com/.
  3. Facebook responds over a secure connection: OK, here’s the page you asked for…

Any time that the browser finds a link, <img> tag, <script> tag, or any other situation involving a Facebook URL, it will automatically rewrite http:// to https:// if needed. Once the browser has seen that a website has HSTS enabled, the browser will no longer use insecure HTTP connections with that site, making it impossible to perform an SSL stripping attack.

How do websites enable HSTS?

In order to enable this protection against SSL stripping, the website needs to send a special HTTP header to the browser in response to each request. Once the user’s browser sees this header, it will record the fact that the website has HSTS enabled. The header looks like this:

Strict-Transport-Security: max-age=15552000

or this:

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

The HSTS header must be sent over HTTPS. Browsers will ignore the header if it is sent for an http:// resource.

The header has two parts:

For the best protection, it’s a good idea to use includeSubDomains. That said, make sure that all affected subdomains have HTTPS enabled first. If you enable HSTS on a subdomain that does not support HTTPS, browsers will refuse to use insecure HTTP, and your users will not be able to access that part of your site.

Enabling HSTS in Apache

You may need to enable mod_headers first (run the command a2enmod headers).

In your Apache site configuration, add the following inside each SSL VirtualHost (look for <VirtualHost *:443>):

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

Enabling HSTS in Nginx

In your Nginx site configuration, add the following to each SSL server block:

add_header Strict-Transport-Security "max-age=15552000; includeSubDomains"