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.
- Strict-Transport-Security (HSTS)
- Content Security Policy (CSP)
- X-Frame Options
- 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
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
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'
'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'
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
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/
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
- Content Security Policy (CSP): Directives, examples, fixes
- sucuri.net - Free website malware and security checker
First published: 7th March 2025