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:
- The browser tries to load
http://www.facebook.com/
. - Facebook’s server responds: Oh, that’s not the URL you want, try
https://www.facebook.com/
instead. - The browser tries to load
https://www.facebook.com/
. - 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:
- The browser tries to load
http://www.facebook.com/
. - 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”:
- The browser remembers that
www.facebook.com
has HSTS enabled, and automatically replaceshttp://
withhttps://
. - The browser tries to load
https://www.facebook.com/
. - 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:
-
max-age
indicates how long in seconds the browser should remember that this website has HSTS enabled. I suggest 15552000 seconds, which is 180 days. As long as the user visits your website at least once every 180 days, they will remain protected.For testing purposes, it may be helpful to use a small value (e.g. 60 seconds) temporarily in order to test that HSTS is working the way you expect.
-
includeSubDomains
is optional. Suppose thatfoo.example.com
enables HSTS. WithoutincludeSubDomains
, HSTS will apply only tofoo.example.com
, and not toabc.foo.example.com
. IfincludeSubDomains
is specified, HSTS will apply tofoo.example.com
,*.foo.example.com
,*.*.foo.example.com
, etc.A subdomain cannot affect its parent domain. An HSTS header from
foo.example.com
will never affectexample.com
orbar.example.com
, regardless of whetherincludeSubDomains
is specified.
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"