The headers we want
Manipulating HTTP headers is one of the most common things that Fastly customers do. Using the right combination of headers is one of the best things you can do for the security of your site, and also a significant contributor to performance.
Most developers know about and depend on a variety of HTTP headers to make their content work. Those that are best known include Content-Type
and Content-Length
, which are both almost universal. But more recently, headers such as Content-Security-Policy
and Strict-Transport-Security
have started to improve security, and Link rel=preload
headers to improve performance. Few sites use these, despite their wide support across browsers.
In the previous post in this series, we looked at unnecessary headers. This time around, I’m going to help you figure out what headers you should be configuring for your site.
Doing some homework
There are multiple services around the web that will analyse your site and tell you what headers you should add. I looked at securityheaders.io and Observatory by Mozilla to supplement my own research and data drawn from the Fastly network.
The headers you should have on your site
So, what are the key headers you should have in responses from your servers? The bulk are responsible for improving security:
Content-Security-Policy: Acts like a firewall in the browser. If your site is compromised, helps limit the damage by preventing connections to hosts you don’t approve. Essential. If you don’t have one, you should.
Referrer-Policy: Configures the level of detail to include in the Referer
header when navigating away from the page. Helps to prevent data leaking from your site to sites that you link to. Highly recommended.
Strict-Transport-Security: Prevents any attempt to connect to your site over plain HTTP. Helps stop man-in-the-middle attacks, and upgrades your site’s security. Also highly recommended.
X-XSS-Protection: Prevents some forms of cross site scripting attack, by preventing script from executing if any of the markup in the document is also present in the same form in the request. Basically if you load a page with url /index.html?foo=<script>alert(‘foo’);</script>
and the browser sees <script>alert(‘foo’);</script>
in the page source, the script is blocked. These days this is largely made redundant by CSP.
X-Content-Type-Options: Set this to nosniff
to prevent browsers allowing content that looks like JavaScript to execute even if it doesn’t have the right content-type. Prevents mime confusion attacks, and more recently is being used by Chrome to enable site isolation. It’s getting less necessary over time, due to better default behaviours, but this is currently still a best practice.
CORS: Cross-Origin Resource Sharing headers allow your URL to be loaded by script operating on another origin. This one's optional. These headers are permissive, not restrictive, so not having them at all gives you the highest level of security.
Others are for performance:
Timing-Allow-Origin: Allows monitoring tools access to timing data for the request. It's good to throw this in on pretty much anything, as it will allow you to get much richer data from services like Google Analytics or Speedcurve.
Link rel=preload: Tells the browser of critical resources that it should get downloading even though it hasn’t encountered the need for them yet. Use this for fonts and important CSS (and read on for how we’re going to make this technique even more effective in future).
Server-Timing: Provides server-side timing information, which allows navigation and resource timing APIs to be supplemented with more granular information about individual tasks that make up the server side processing (eg. ‘how long did we spend in MySQL’). Great for monitoring performance data, in conjunction with RUM beacon tools.
Let’s look at a couple of these in more detail.
Content Security Policy: keep a lid on it
Although Content-Security-Policy
is one of the most important headers to include in your responses, it’s also one of the most verbose. The largest CSP header I found in HTTPArchive was 10KB in size. Ten kilobytes. For a single header value. Worse, while response bodies can be streamed, response headers are buffered by most servers and proxies, and only passed on when they are complete. HTTP/2 header compression helps a bit by remembering them between requests, but that doesn’t mean a 10KB header value is OK.
Also, by potentially filling up the entirety of the first packet of your response, you might force the browser to make two round trips to the server to get any content at all. So as well as trying to remove unnecessary headers, try to keep the values of the headers that you do include as short as possible.
Referrer-Policy
Since the dawn of time (which is approximately the late nineties, in the web world), browsers have been sending (and mis-spelling) the Referer
header. For much of its life, it’s been one of the most important ways to track user journeys from one page to the next in analytics tools, and also understand the origin of incoming traffic. However, that last part comes with significant privacy concerns. If I click a link in an email, and I’m using a web based email client, the site I end up on might reasonably be able to guess my email domain, from the domain name of the referring site. Worse, knowledge of the full URL you came from, including the query arguments, might reveal the terms you used for your last search, or personal data like your email address.
You can choose a number of strategies from the available Referrer-Policy options, but my usual go-to is “origin-when-cross-origin” which will include the Referer
header in all requests that would normally have one, but will truncate the value to just the origin (domain) if the link goes from one origin to another. Links within your own site will include the full referrer.
Measuring Server-Timing at the edge
There are lots of nice things about Server-Timing
, and one of them is that you can add multiple instances to a response, and they should all be aggregated by the browser or RUM tool. That means if your request passes through multiple stages of server processing -- as it does when you have Fastly in front of your site -- each stage can add its own timing metrics and they don’t conflict. Here’s how to add Fastly metrics in a server timing header using VCL in your Fastly service configuration:
add resp.http.Server-Timing = fastly_info.state {", fastly;desc="Edge time";dur="} time.elapsed.msec;
This number will include the time we spend waiting on your backend so in cases where we retrieve content from origin, all your server timing metrics should be smaller than the Fastly number. However, in cases where we serve the document from Fastly cache, you’ll see your original timing metrics from when the page was generated originally, but the Fastly number will confirm that the overall time cost is actually very small.
Server timing metrics are available through the performance object in JavaScript and rendered in the Network panel of Chrome devtools:
Currently, the visualisation of these metrics is rudimentary. But there’s a lot of work going into this and the UI will likely improve dramatically in the future.
Adding all the good headers
Fastly is a good place to add all the security and perf headers. Below is what it looks like when we put all the headers together. Add this code in the deliver stage of the request flow, changing the values to those that are appropriate for your site (though take care not to copy/paste without checking that the values are suitable for your site):
In the next post in this series, we’ll take a look at some of the more exotic headers that are starting to be standardised and appear in browsers.