Programster's Blog

Tutorials focusing on Linux, programming, and open-source

Website Security Headers

When developing a website, there are a number of headers one can send that can increase the security of your website by preventing things like the site being embedded in an iframe of someone else's site, or from running inline-injected JavaScript code. Below is a list of headers that link to the section of this page that outlines what they are responsible for and how to set them.

  1. Strict-Transport-Security (HSTS)
  2. Content Security Policy (CSP)
  3. X-Frame Options
  4. X-Content-Type-Options - Disable Content Type Sniffing

Headers

Strict-Transport-Security (HSTS)

Setting this header tells the browser to only load content from the website using HTTPS connections. Most websites will automatically detect if the user is on an HTTP connection and redirect them, but this prevents the browser using a plain HTTP connection in the first place. This also also helps with websites that are behind a proxy that terminates the secure connection. This is because at that point, the website's backend can't tell if the user was using an HTTPS connection or not, except perhaps by checking the X-Forwarded-Proto which could not be set. Using this option is safer and requires less effort by the developer (don't have to write code to perform checks).

Strict-Transport-Security: max-age=<expire-time-in-seconds>

If you wish to include all your subdomains, then use the following:

Strict-Transport-Security: max-age=<expire-time-in-seconds>; includeSubDomains

Preload

Google mainta a service at hstspreload.org which registers sites as using HSTS. This is to help prevent users having to visit your site for the first time to get the header to know that it is an HSTS enabled website. If one adds preload to this header, then this instructs Chrome to register this website with that service so that chrome will know that the website is HSTS enabled before the user's browser has even visited the site once (because some other user once visited the site and had the site registered).

This is not part of the official spec, but is widely/commonly used.

If using preload, the max-age needs to be at least one year.

Strict-Transport-Security: max-age=<expire-time-in-seconds>; preload

If using preload, the max-age needs to be at least one year.

Nginx - "Always"

You may see nginx examples like so:

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

... and wonder what the always part is for. This is to tell Nginx to always add this header, even for the internally generated error pages and is not part of the header name/value pair that gets sent to the browser.

Firefox Incorrectly Shows As Disabled

After having implemented this and checking the headers came back in responses, I found that Firefox's security tab kept showing HSTS as disabled. I wasn't the only one to find this [1] [2] so for now I'm putting it down as a bug in Firefox.

More Info

More info on HSTS.

Content Security Policy (CSP)

Specifies any number of directives that control locations from which certain resource types may be loaded, where a directive consists of a directive name (list of directives), and a value.

When specifying multiple directives, use a ; between them.

Content-Security-Policy: default-src 'self'; script-src https://example.com; base-uri 'none'

The use of single quotes around 'self' and 'none' are required.

The following policy would prevent your website being loaded in an iframe from another site.

Content-Security-Policy: frame-ancestors 'none'

Upgrade Insecure Requests Policy

To force the browser to "convert" any linked resources on the same domain to using HTTPS instead of HTTP, one can simply add upgrade-insecure-requests to the CSP like so:

Content-Security-Policy: upgrade-insecure-requests; frame-ancestors 'none'

This policy is different from the others in that one just declare the policy name, which doesn't need a value. This can be particularly useful as a quick fix to prevent "mixed content" errors because a site's code may not be aware that it is behind a TLS terminating reverse proxy etc.

PHP Middleware

For those using PHP with PSR-15 Middleware, you can do the following to make life easier, allowing you to easily add/edit polices without having to manually manipulate a massive string.

<?php

use \Psr\Http\Message\ServerRequestInterface;
use \Psr\Http\Server\MiddlewareInterface;
use \Psr\Http\Server\RequestHandlerInterface;
use \Psr\Http\Message\ResponseInterface;

class MiddlewareContentSecurityPolicy implements MiddlewareInterface
{
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        $policies = [
            "default-src 'self'",     // set default for sources to self.
            "frame-ancestors 'none'", // prevent site loading inside iframe
            "base-uri 'none'",        // don't allow setting the base URI in the HTML.
        ];

        return $handler->handle($request)->withHeader('Content-Security-Policy', implode("; ", $policies));
    }
}

More Info

More info.

X-Frame Options

Adding this header prevents your website being loaded in another site's iframe.

X-Frame-Options: deny

This would allow your website to be used in an iframe of the same domain. E.g you wish to load content from yourself on a page.

X-Frame-Options: sameorigin

This would allow your website to be loaded in an iframe, on a specific site:

X-Frame-Options: allow-from https://some.domain.com/

More info This header is being phased out in favor of the Content-Security-Policy.

X-Content-Type-Options - Disable Content Type Sniffing

Use the following header stops "content sniffing", which tells browsers to not analyze the byte streams of content coming from a website in order to try and determine the mime-type automatically. This means that they will rely on you having set the correct mime type on content.

X-Content-Type-Options: nosniff

References

Last updated: 8th March 2025
First published: 7th March 2025

This blog is created by Stuart Page

I'm a freelance web developer and technology consultant based in Surrey, UK, with over 10 years experience in web development, DevOps, Linux Administration, and IT solutions.

Need support with your infrastructure or web services?

Get in touch