HOW TO: Set your Security Headers and HSTS in 10 minutes (nginx)

January 14, 2022

If you have a fresh site, odds are running it through securityheaders.com is going to give you some warnings. I won’t go into much detail about what security headers specifically do in this article. The ones that matter are X-XSS-ProtectionX-Content-Type-OptionsX-Frame-Options Referrer-PolicyFeature-PolicyContent-Security-Policy and Strict-Transport-Security.

Long story short, you want to pass them all…

So let’s knock out a bunch of them in one go straight away. Add these to your nginx configuration, or ask your web host to do it, which is going to be the case most of the time:

add_header X-XSS-Protection "1; mode=block";
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin";
add_header Feature-Policy "microphone 'none'; payment 'none'; sync-xhr 'self' https://YOURDOMAIN.COM";

Don’t forget to change https://YOURDOMAIN.COM for your actual domain. Now that takes care of 4 out of 7 security headers. Easy!

We’re going to knock out 2 more security headers at once by using Cloudflare. If you’re not already using Cloudflare – you should get on that – it’s awesome (there’s a non-Cloudflare, nginx method below this bit if you prefer). Sign up for free here.

HSTS preloading

Once you get your DNS routed through there, you’ll have a whole new suite of fancy tools to play with. One such tool is their HTTP Strict Transport Security (HSTS) feature, found about halfway down on the Crypto page:

Click Enable HSTS and you’ll see some important details regarding the implications of disabling HSTS later. Do pay attention to this:

If you turn on HSTS and do not have HTTPS for your website, browsers will not accept the HSTS setting.

If you have HSTS enabled and leave Cloudflare, you need to continue to support HTTPS through a new service provider otherwise your site will become inaccessible to visitors until you support HTTPS again.

If you turn off Cloudflare’s HTTPS while HSTS is enabled, and you don’t have a valid SSL certificate on your origin server, your website will become inaccessible to visitors.

Note: Disabling Cloudflare’s HTTP can be done in several ways: Grey clouding a subdomain in your DNS records, “Pausing” the Cloudflare service, or having a misconfigured custom SSL certificate through your Cloudflare dashboard (e.g., invalid SSL certificates, expired certificates, or mismatched host names).

If you need to disable HTTPS on your domain, you must first disable HSTS in your Cloudflare dashboard and wait for the max-age to lapse to guarantee that every browser is aware of this change before you can disable HTTPS. The average max-age is six months (you can set the max-age in the next step).

If you remove HTTPS before disabling HSTS your website will become inaccessible to visitors for up to the max-age or until you support HTTPS again. Because disabling HTTPS on an HSTS enabled website can have these consequences, we strongly suggest that you have a committed HTTPS service in place before enabling this feature.

HTTP STRICT TRANSPORT SECURITY (HSTS) DISCLAIMER | CLOUDFLARE

You can read what Cloudflare has to say about HSTS in far more depth here.

Switch every option on and set the Max Age Header (max-age) option to 12 months. Cloudflare recommends 6 months, but the minimum required to get the most out of HSTS is 12 months as HSTS Preload requires it, and you’ll be submitting your site there right after this.

All set? Perfect, now head to aforementioned HSTS Preload site to submit your site to the preload index. They require your max-age setting to be 31,536,000 seconds – which coincidentally enough – is 12 months. You’ve already done that in Cloudflare so you’re good to click Submit to the HSTS preload list:

Just for fun, here’s the nginx equivalent for non-Cloudflare users:

add_header Strict-Transport-Security "max-age=31536000 ; includeSubDomains; preload";

While we’re on the topic of security and Cloudflare, check out this article on a security exception you might need to make in your Cloudflare account, assuming you’re using WordPress and Gutenberg.

Last but not least…

Now we have 6 out of 7 security metrics that securityheaders.com check for. The only one left is the Content Security Policy security header. This one is a little trickier than the others because you’ll need to use a rule that includes all the domains you’ll be loading content from aside from your actual domain (like ajax.cloudflare.com, www.google-analytics.com, ajax.googleapis.com, etc…).

You’ll want to add something like the following to your nginx configuration or ask your web host to do it like with the first step:

add_header Content-Security-Policy "img-src * data:; font-src * data:; default-src 'unsafe-inline' YOURDOMAIN.COM ajax.cloudflare.com secure.gravatar.com yoa.st search.google.com fonts.googleapis.com fonts.gstatic.com ajax.googleapis.com www.google-analytics.com ";

Make sure to swap YOURDOMAIN.COM with your actual domain and to add in any other domains that your code calls on pageload like your CDN, external fonts, advertising networks, facebook pixels, etc..

Once all that’s done, run your domain through securityheaders.com one last time to make sure. And that’s all 7!

BONUS ROUND

Add this header to nginx to prevent “MIME Sniffing” in Internet Explorer:

add_header X-Content-Type-Options nosniff;

This prevents Internet Explorer from “sniffing” a response away from the declared content-type as the header instructs the browser not to override the response content type. Using the nosniff header, if the server tells the browser that a resource is text/css, the browser will only render it as text/css.