James Galley

Web application developer

PHP, Yii, MySQL, Node.js - JavaScript, HTML, CSS & SASS - Linux, AWS & serverless

Strict-Transport-Security HTTP header

Abbreviated as HSTS, the Strict-Transport-Security HTTP header is used to improve the security of a browser connection to a website by enforcing the use of the encrypted HTTPS protocol.

The headers tells browsers that a particular website should only be accessed via HTTPS and to redirect any HTTP requests to HTTPS.

If you can ensure that your website will always present a valid TLS (SSL) certificate and be accesible over HTTPS, then it's advisable to make use of this header.

The use of the Strict-Transport-Security HTTP header is better than a 301 redirect from HTTP to HTTPS because the initial HTTP request is still vulnerable to a man-in-the-middle attack and a malicous actor intercepting the traffic could redirect the browser to an insecure or fake destination.

Directives

max-age=31536000

The max-age directive tells the browser how long to remember that a site must only be accessed via HTTPS. The duration is in seconds, and the recommended values are at least one year (31536000).

includeSubDomains

Optional. If the includeSubDomains directive is included, the HSTS rule applies to all subdomains of the domain.

preload

Optional and currently an unofficial directive. You can include this directive if you wish to include your website in Google's HSTS preload list, although you'll also need to use a max-age of 2 years (63072000) and use includeSubDomains

Strict-Transport-Security on localhost

When developing locally it's uncommon to use HTTPS, except in specific circumstances. In this case you won't have a TLS (SSL) certificate provisioned on your local server and you'll be making all network requests over HTTP.

So in this scenario it wouldn't be helpful for your browser to force the connection onto HTTPS - you'd just see a browser error.

Fortunately the HSTS header is ignored by browsers until a site is first accessed over HTTPS and with a valid certificate, so if you're working locally and you never access the site using HTTPS, you'll be ok.

You can also use conditional logic in your website or webserver code to remove the header in different environments.

How to set Strict-Transport-Security header in Apache

This example taken from the useful h5bp/server-configs-apache library (this version from v6.0.0, 5th December 2022):

<IfModule mod_headers.c>
    Header always set Strict-Transport-Security "max-age=16070400; includeSubDomains" "expr=%{HTTPS} == 'on'"
</IfModule>

How to set X-Frame-Options in PHP

The header can be set using the PHP header method:

<?php
header('Strict-Transport-Security: max-age=31536000; includeSubDomains');
?>

How to set X-Frame-Options in Yii 2

For Yii2, headers can be added site-wide by modifying the response object in the app config :

'components' => [
    'response' => [
        'on beforeSend' => function($event) {
            $event->sender->headers->add('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
        },
    ],

Note that Apache/.htaccess settings of a header will overwrite any value set in PHP.

How to set Strict-Transport-Security in Remix.run

In the Remix JavaScript framework response headers are set in the handleRequest function of the server entry script, typically app/entry.server.jsx.

import { RemixServer } from "@remix-run/react";
import { renderToString } from "react-dom/server";

export default function handleRequest(
  request,
  responseStatusCode,
  responseHeaders,
  remixContext
) {
  let markup = renderToString(
    <RemixServer context={remixContext} url={request.url} />
  );
  responseHeaders.set("Content-Type", "text/html");

  // Set X-Frame-Options Header
  responseHeaders.set("Strict-Transport-Security", "max-age=31536000; includeSubDomains");

  return new Response("<!DOCTYPE html>" + markup, {
    status: responseStatusCode,
    headers: responseHeaders,
  });
}

See more information on Remix entry scripts here: https://remix.run/docs/en/v1/guides/migrating-react-router-app#creating-server-and-browser-entrypoints

How to set Strict-Transport-Security header in Node.js and Lambda@Edge / Cloudfront

For static websites or apps served via AWS CloudFront and/or with a Lambda@Edge function, the Strict-Transport-Security HTTP header can be set using the Node.js runtime, as follows:

Lambda@Edge (origin response event)

'use strict';
exports.handler = (event, context, callback) => {

    const response = event.Records[0].cf.response;
    const headers = response.headers;

    headers['strict-transport-security'] = [
        {
            key: 'Strict-Transport-Security', value: 'max-age=31536000; includeSubdomains'
        }
    ];

    callback(null, response);
};

Cloudfront function

function handler(event) {

    var response = event.response;
    var headers = response.headers;

    headers['strict-transport-security'] = { value: 'max-age=31536000; includeSubdomains'};

    return response;
}