10/10 Header report


For our 400th episode milestone we had the pleasure of chatting with Dries! (Link will work 3pm May 22nd) During the episode we were discussing the http header project Dries had put on his site as a fun side project. I jokingly commented on how Dries' site scored only a 9 out of 10, but was quickly humbled when I ran my own site and got a 5/10.

Over the next couple of days I took it as a challenge to get 10/10. Most of the recommendations were related to CSP and HSTS type headers. I had not reviewed them as closely as I would for a client site normally because this site is statically generated and I assumed hosting on Netlify would take care of most common headers. One of the great things about the tool is it will point out your faulty assumptions and give a quick way to validate.

Here is my current report: https://dri.es/headers?url=https%3A%2F%2Fnlighteneddevelopment.com let me know if I drop below a 10/10!

My first step was to install the CSP module. This module is extremely configurable for adding individual CSP directives to your site. I went through the normal process of enabling it, everything breaking on my site then slowly updating the allowlist rules to get my site working again. I then immediately realized this would only be helpful for my local for debugging. My site is static so Drupal is not serving the headers at the host!

I host the site with Netlify and use Cloudflare as a CDN. After some brief internet research I found you can use Netlify's toml file for headers (which I do not use) or you can add a _headers file to the webroot folder. I chose the latter.

Once I got this piece, most headers were easy, you need an opening comment line, then one header per line:

  Content-Security-Policy: default-src 'self' data:; frame-src 'self' youtube.com www.youtube.com; object-src 'none'; script-src-elem 'self'; style-src 'self' data: 'unsafe-inline'; style-src-attr 'self'; frame-ancestors 'self' *.youtube-nocookie.com *.ytimg.com;
  Referrer-Policy: strict-origin-when-cross-origin
  Permissions-Policy: accelerometer=(), camera=(), geolocation=(), gyroscope=(), microphone=(), payment=(), usb=() x-xss-protection: 1; mode=block;
  X-Frame-Options: SAMEORIGIN
  X-Permitted-Cross-Domain-Policies: none
  Cross-Origin-Embedder-Policy: credentialless
  Cross-Origin-Opener-Policy: same-origin
  Cross-Origin-Resource-Policy: same-origin
  Cache-Control: public, max-age=86400, must-revalidate

Key things to remember here: your browser will make all headers lowercase, but you need the actual header names from the spec.
I had to tweak the Cross-Origin-Embedder-Policies to get the youtube embed working. Credentialless is an experimental value and you have to add the credentialless attribute to the appropriate iframe. Dries' header tool actually did not support the credentialless directive when I first set this up but Dries graciously updated his tool!

Also pay close attention to the actual directives, most headers do not need terminating semicolons, just between directives, and of course there are exceptions like the Cache-Control header which uses commas!

There were a couple of trickier headers I could not resolve in Netlify. HSTS headers seem to be ignored by Netlify, and Cloudflare was setting a dynamic cache header that was generating a warning in the tool.

Cloudflare has a toggle for HSTS (currently under SSL/TLS > Edge Certificates). HSTS can make your site unreachable if your certificates are not working so ensure you have those set up properly first. Cloudflare will warn you extensively about this!

The dynamic header was a lot trickier. All of the documentation I found online for how to update this points to the wrong settings page in Cloudflare so it took a bit of experimentation. Also, I am not convinced most sites want to change this setting, so make sure you review your site needs first.

In order to change the dynamic header for Cloudflare I had to create two page rules:

  • nlighteneddevelopment.com/ Cache Level: Cache Everything
  • nlighteneddevelopment.com/* Cache Level: Cache Everything

Cloudflare rules for nlighteneddevelopment.com

All in it took me just a couple of hours of tinkering to get everything working and hit 10/10. Most of that was the normal CSP wrangling to get the minimum settings. I had to experiment a bit with the credentialless option and of course configure Cloudflare too.

I hope this helps you and let me know if you hit 10/10!

Here is my full header report as of 5-10-23

Full header report from dri.es