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?
http://example.com
responds with a 301 redirect tohttps://example.com
which sets the HSTS headers. The HSTS policy will apply only toexample.com
.- All subsequent requests or links to
http://example.com
will be translated tohttps://example.com
- Requests to
http://www.example.com
will not be translated tohttps://www.example.com
becausewww.example.com
doesn't have a HSTS policy. The server will respond with an insecure 301 redirect tohttps://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.