What Is HSTS and How Do You Set It Up?

Locked and unlocked icons
Shutterstock/Pavel Ignatov

HTTPS is very secure, but it has one flaw: It’s not on by default. An attacker in the middle could hijack a user’s connection before you can tell them to use HTTPS. HSTS solves this issue, and enables HTTPS site-wide.

Having SSL encryption in the first place is a prerequisite for HSTS, because otherwise enabling HSTS will just make your site inaccessible. You can read our guide on setting up free SSL certificates with LetsEncrypt to enable HTTPS across your website.

How Does HSTS Work?

HSTS stands for HTTP Strict Transport Security, and governs how a user’s browser should connect to your website.

Here’s how the connection to your site usually works. A user wants to connect to your website, and pokes your server with a request to connect. Your server does the responsible thing and sends a 301 Moved Permanently response to the browser, telling it that the HTTP address it requested needs to be redirected to HTTPS. The user continues on as normal, browsing securely.

However, an attacker with control over the connection (as is the case with man-in-the-middle attacks) could easily block the 301 response and take control of that user’s browsing session. This is a major issue, as it defeats the purpose of encrypting the site in the first place.

With HSTS enabled, the server sends the same 301 Moved Permanently response, but also sends a header saying, “Hey, I don’t support HTTP. Don’t even try making more HTTP requests, because I’m going to redirect all of them.” The browser gets the message, and will redirect itself to HTTPS before sending anything. This makes sure your site is fully HTTPS, by default, all the time.

HSTS Preloading

However, standard HSTS has one major flaw: The very first connection a user makes is still insecure. If a user has used your site before, the browser will respect the HSTS request in the future. But the initial HSTS response is insecure, so if a user is browsing in a coffee shop and opens your site for the first time (or, for the first time on a mobile device), their connection can still be hijacked.

HSTS preloading is an initiative from the Chromium project to solve this issue. The Chromium Project maintains a list of websites that are HSTS enabled all the time. This list is built into most major browsers, and the browser checks against it before making requests to new sites.

If you’re on the list, even if a user has never interacted with your site before, the user will act like they’ve already seen your HSTS header and never communicate with HTTP. This makes the connection entirely secure from the start.

Enabling HSTS and Joining the Preload List

HSTS can be turned on with a simple header, which is added to all responses your server sends:

Strict-Transport-Security: max-age=300; includeSubDomains; preload

You can include this in your webserver’s configuration file. For example, in Nginx, you can set the header by including an add_header line in your server block:

add_header Strict-Transport-Security 'max-age=300; includeSubDomains; preload; always;'

And for Apache, the command is similar, using the Header always set line:

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

However, there are a few more steps to ensure everything works correctly, and to be eligible for preloading.

First, make sure that you are redirecting all HTTP requests to HTTPS. On Nginx, you can do this by listening to all port 80 requests (HTTP) and sending a 301 request with the URL changed to the HTTPS equivalent:

server {
  listen 80;
  return 301 https://$host$request_uri;

To qualify for preloading, you must also make sure that all of your subdomains are covered under your SSL certificate, and that you’re serving them over HTTPS. You can do this with a wildcard certificate, which you can get for free from LetsEncrypt. If you’re not preloading, this isn’t necessary but is still advisable.

You can check if HSTS is working correctly by loading your site with the header enabled, then going to chrome://net-internals/#hsts and entering your site name into the “Query HSTS/PKP domain” search tool. If your site displays output like this, HSTS is enabled.

HSTS is enabled if you site displays this output

Additionally, you should check if the strict-transport-security header is included in your site’s response headers, which you can do from the Network tab in the Chrome development console:

Check if the strict-transport-security header is included in your site's response headers

Once you’ve done all of that, you should test that everything works, and that nothing has broken when you turned on HSTS. If there are no issues, you can head over to the preloading submission page, enter in your domain name, and submit your website.

Problems with HSTS and HSTS Preloading

With HSTS, your site is now forced to used HTTPS for everything. This includes every subdomain, even internal tools. Each subdomain you have must have a valid SSL certificate and be secured with HTTPS, or it will be inaccessible for the duration of the HSTS header (which can be up to two years). If you have a wildcard certificate, you can solve some of these issues, but you need to do testing before enabling it for a long time period.

The main issue with HSTS preloading is that it’s very permanent. The minimum max-age is one year, and once your site is put on the list, you can’t leave the list without going through a lengthy removal process requiring each user to perform a browser update to apply the changes.

You can look at this meta-buglist of removal requests to see what the major problems are in practice. Uber had problems with subdomains. Third- and higher-level subdomains may not be supported on normal wildcard certifications. One website from Sweden even reports significantly lower ad revenue, as the local advertisers there do not load their resources over HTTPS, and HSTS blocks every non-secure HTTP request made while the user is connected to your website.

The best way to avoid these problems is to roll out HSTS in stages before making the permanent switch to being preloaded. The Chromium Project recommends testing in intervals by setting the max-age value first to five minutes to test that it works:

max-age=300; includeSubDomains

Then to a week for a longer test:

max-age=604800; includeSubDomains

Then for a month, until you’re certain there are no issues.:

max-age=2592000; includeSubDomains

If something goes wrong and you set a really long max-age property, you can clear the local flag from Chrome’s net-internals page.

Once you’re sure nothing will go wrong with having only HTTPS on all the time, you can set your max-age to two years, add the preload directive, and submit your site for preloading.