How to set up HSTS on Netlify

Travis - Sep 2 '23 - - Dev Community

What is HSTS and why should you care?

HSTS helps make your website more secure.

HSTS, stands for “HTTP Strict Transport Security” which adds extra security to HTTPS websites. It protects websites against man-in-the-middle attacks and cookie hijacking.

Note: This is more secure than simply configuring a HTTP to HTTPS (301) redirect on your server, where the initial HTTP connection is still vulnerable to a man-in-the-middle attack.

For a full description, check out Wikipedia's HSTS page.

How to check whether a website is on Google’s HSTS Preload list

You can check if a site is using HSTS or not at Google’s HSTS Preload website.

Or

The presence of the HSTS header can be confirmed by examining the server’s response through an intercepting proxy or by using curl as follows:

$ curl -s -D- https://yourdomain.com | grep -i strict
Enter fullscreen mode Exit fullscreen mode

You should see the result below, if you haven't setup HSTS yet continue reading.

strict-transport-security: max-age=63072000; includeSubDomains; preload

Curl information source: owasp.org

Before adding HSTS to this site, I saw the following results:

Status: travislord.xyz is not preloaded.

Eligibility: In order for travislord.xyz to be eligible for preloading, the errors below must be resolved:

Error: No includeSubDomains directive
The header must contain the includeSubDomains directive.

Error: No preload directive
The header must contain the preload directive.

When I saw that, of course I wanted to add HSTS. Here’s how I did it.

How to make most websites hosted on Netlify eligible for Google’s HSTS preload list

If you don't already have one create a netlify.toml file in your root directory. Now head to the file and add the following lines.

[[headers]]
  for = "/*"
  [headers.values]
    Strict-Transport-Security = "max-age=63072000; includeSubDomains; preload"
Enter fullscreen mode Exit fullscreen mode

Max-age of 63072000 is two years. Google’s minimum is one year, but it appears that Netlify wants two and Netlify includeSubDomains running.

Netlify HSTS preload - Doc

Committed, pushed and time to see if it worked! 🤞

Now head back to Google’s HSTS Preload to check the site again, you should see something similar to the following example. Link

Eligibility: travislord.xyz is eligible for the HSTS preload list.

You should see a “submit” button, followed by the following agreement request:

I am the site owner of travislord.xyz or have their permission to preload HSTS. (If this is not the case, travislord.xyz may be sending the HSTS preload directive by accident. Please contact hstspreload@chromium.org to let us know.)

I understand that preloading moonbooth.com through this form will prevent all subdomains and nested subdomains from being accessed without a valid HTTPS certificate:
*.travislord.xyz
..travislord.xyz …

You should see a “submit” button, followed by the following agreement request:

Success
travislord.xyz is now pending inclusion in the HSTS preload list!

Please make sure that moonbooth.com continues to satisfy all preload requirement, or it will be removed. Please revisit this site over the next few weeks to check on the status of your domain.

Also consider scanning for TLS issues using SSL Labs.

If you check you site again on hsts preload you will now see the message under the 'Check HSTS preload status and eligibility' button.

Status: travislord.xyz is pending submission to the preload list.

Now it's a waiting game, come back in a few weeks and you will see the status like below.

Status: travislord.xyz is currently preloaded

Bonus round

X-Frame-Options

X-Frame-Options = "DENY"
Enter fullscreen mode Exit fullscreen mode

X-XSS-Protection

X-XSS-Protection = "1; mode=block"
Enter fullscreen mode Exit fullscreen mode

Content-Security-Policy

Replace domain.com with the sites domain.

Content-Security-Policy = " font-src cdnjs.cloudflare.com fonts.gstatic.com domain.com"
Enter fullscreen mode Exit fullscreen mode

X-Content-Type-Options

X-Content-Type-Options = "nosniff"
Enter fullscreen mode Exit fullscreen mode

Permissions-Policy

Permissions-Policy= "vibrate=(), geolocation=(), midi=(), notifications=(), push=(), sync-xhr=(), microphone=(), camera=(), magnetometer=(), gyroscope=(), speaker=(), vibrate=(), fullscreen=(), payment=()"
Enter fullscreen mode Exit fullscreen mode

Referrer-Policy

Referrer-Policy = "strict-origin-when-cross-origin"
Enter fullscreen mode Exit fullscreen mode

Cheers for coming this far, take it all 🍻

[[headers]]
  for = "/*"
  [headers.values]
    Strict-Transport-Security = "max-age=63072000; includeSubDomains; preload"
    X-Frame-Options = "DENY"
    X-XSS-Protection = "1; mode=block"
    Content-Security-Policy = " font-src cdnjs.cloudflare.com fonts.gstatic.com yourdomain.com"
    X-Content-Type-Options = "nosniff"
    Permissions-Policy= " midi=(), sync-xhr=(), microphone=(), camera=(), magnetometer=(), gyroscope=(), fullscreen=(), payment=()"
    Referrer-Policy = "strict-origin-when-cross-origin"
Enter fullscreen mode Exit fullscreen mode

To test the extra headers you can head over to securityheaders.com. You should see the same results displayed on the image under the blogs title.

Want to read more...

. . . . . . . . . . . . . . . . . . . . . . . .