Enabling HTTP/2 on GOV.UK

By admin
Note: This is an RFC I wrote to persuade the GOV.UK Senior Technology team to enable HTTP/2 on GOV.UK. It was originally posted on the GOV.UK RFCs GitHub repo. It can be viewed here. I’m posting it to my blog so as to own my own work and hopefully give it more visibility.

The Technology in Government
ORG

blog post I wrote on the work can be seen here.

Summary

Back in

November 2018
DATE

we trialed the use of HTTP/2 on GOV.UK. According to quite a few sources, enabling HTTP/2 should improve web performance for users by introducing technology like multiplexed streams, HPACK header compression and stream prioritisation. Unfortunately it turned out that from our synthetic web performance testing it actually slowed the site down in many instances.

We tested

5
CARDINAL

different page types, on multiple devices and connection speeds and examined the following performance metrics to come up with a result:


First
ORDINAL

visual change

Visually complete

95%
PERCENT

Last visual change

Speed index

Load time (fully loaded)

And for

Lighthouse
ORG

reports these metrics were examined:

First Contentful Paint

First Meaningful Paint

Speed Index


First
ORDINAL

CPU Idle

Time to Interactive

The RFC below discusses the problems with our current setup and suggests possible solutions.

Problems

1 – Sub Resource Integrity (SRI)

On GOV.UK we are using

Subresource Integrity
ORG

for all our CSS and

JavaScript
ORG

assets coming from the assets domain. The SRI specification requires that the

crossorigin
ORG

attribute is set to anonymous to be used with SRI resources for security reasons. This is because of data leakage from credentialed TCP connections. This requirement is forcing the browser to open a

second
ORDINAL

TCP connection in ‘anonymous mode’ so it can download the CSS / JS from the assets domain. In doing so this is adding

100
CARDINAL

’s of milliseconds of delay to the page rendering timeline. This occurs even when using the preconnect resource hint, a browser feature intended to help fix this issue.

This performance issue is occurring in both HTTP/1.1 and HTTP/2 as seen in the WebPageTest connection view waterfalls below:

HTTP/1.1

Here we can see

13
CARDINAL

TCP connections being opened (

6
CARDINAL

‘credentialed’, 6 ‘anonymous’,

1 third
CARDINAL

-party to

Google Analytics
ORG

). If we weren’t using SRI, we could reduce this requirement down to

8
CARDINAL

(6 ‘credentialed’, 1 ‘anonymous’ for fonts,

1 third
CARDINAL

-party to

Google Analytics
ORG

). This is the

first
ORDINAL

step in improving web performance for GOV.UK users on HTTP/1.1.

HTTP/2

Below you can see the delay in the waterfall while the

2nd
ORDINAL

anonymous TCP connection is established:

And this is what it could look like if we were to remove SRI:

In the example test above on a

Nexus
ORG


5
CARDINAL

device under

3
CARDINAL

G connection speeds, we could bring the request of the CSS & JS files forwards by ~750 ms. This should speed up the whole waterfall and turn the results from the summary list above from red to green in favour of HTTP/2.

This is achieved through the use of HTTP/2 connection coalescing, which can be seen in action on GOV.UK from our trial below:

The coalesced connection is under-utilised if ‘anonymous mode’ is used on our static assets. There’s also an impact from the fact that TCP Slow Start is in action on the delayed anonymous connection, meaning assets will download slower than they could via the single coalesced connection. So we are in the following situation: the connection used to download the HTML isn’t utilised fully (by this point it will already be up to speed), and the anonymous connection downloading the critical

CSS
ORG

has been delayed, so it isn’t up to speed yet. For best performance we should be utilising the connection that has already been established via the HTML download, and use it for other critical page assets (

CSS/JS
LAW

).

2 – Assets served with

Access-Control-Allow-Origin
ORG

: *

My initial thinking was that we could quickly switch the

crossorigin
ORG

attribute from anonymous to use-credentials for our static assets (

CSS/JS
LAW

). Unfortunately on examining the

Fetch
PERSON

specification there’s information in the table (

5th
ORDINAL

row down) that states:

Access-Control-Expose-Headers , Access-Control-Allow-Methods , and

Access-Control
ORG

-Allow-Headers response headers can only use * as value when request’s credentials mode is not “include”.

Basically, the use of the wildcard ( * ) isn’t allowed on a credentialed connection ( use-credentials ). And if it is used, the browser will block any requests and raise an error message the that looks like this:

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at ‘https://assets.example.com/script.js’. (Reason: Credential is not supported if the CORS header ‘Access-Control-Allow-Origin’ is ‘*’).

We are currently serving all our assets from the assets domain with the Access-Control-Allow-Origin: * header, which is blocking us from making this change. The reason why we are serving assets like this is because we are using SRI and they need the

crossorigin
ORG

attribute for it to work.

The

Heroku
WORK_OF_ART

applications (e.g. https://government-frontend.herokuapp.com) that are created are a legitimate reason for using this header when

crossorigin
ORG

is present. If we were to remove the Access-Control-Allow-Origin: * header then the JS and fonts (due to their unique CORS requirements) on the

Heroku
WORK_OF_ART

previews would break. According to the Access-Control-Allow-Origin documentation, there are

only 3
CARDINAL

valid values for this header:

*

<origin>

null

The <origin> value is very important. As this is where we would ideally like to specify: *.gov.uk; *.herokuapp.com . Unfortunately this header only accepts a single origin, and it doesn’t recognise wildcard values. So we are in a situation where we need to use * (for SRI) and a specific origin value (e.g https://government-frontend.herokuapp.com for every unique

Heroku
PRODUCT

app URL, including individual PR’s) at the same time. There’s the possibility that this can be done by adjusting the VCL config on the

CDN
ORG

, but that then opens us out to more potential complications as listed in

@kevindew
LOC

’s comment here. So this could complicate both the

CDN
ORG

configuration and local development if not properly investigated.


3
CARDINAL

– The assets domain

Domain sharding for static assets is an anti-pattern under HTTP/2, and our current HTTP/1.1 setup isn’t optimal for performance either. If our static assets weren’t being served including SRI we would be able to remove the

crossorigin
ORG

and integrity attributes from the <script> and <link> tags. This in turn would allow the browser to correctly use HTTP/2 connection coalescing, which would minimise the impact the assets domain is having on HTTP/2 performance.

Some browser implementations of HTTP/2 connection coalescing are notoriously flaky, so this performance benefit will only be available in some browsers. Users on older versions of

Safari
PERSON

(before

12
CARDINAL

),

IE
ORG

, and Edge 18 and below, all don’t support coalescing, so won’t benefit from this optimisation. But as we’ve seen above, the domain sharding isn’t offering any benefits anyway. So why not remove the need for connection coalescing completely for our static assets and serve them from the origin. All browsers that support HTTP/2 will then receive the same benefits and experience of the browser using a single multiplexed TCP connection.

95.7%
PERCENT

of the

UK
GPE

population use a browser that supports HTTP/2, so most of our users will benefit from this change.

Once completed our connection graph will look similar to this:

The bulk of the page assets will be downloaded on the initial connection to the origin (www.gov.uk), with a secondary anonymous connection only being opened for the fonts. That’s

twelve
CARDINAL

connections under our current HTTP/1.1 implementation, down to just

two
CARDINAL

for HTTP/2 in many cases. So in order to fix our HTTP/1.1 and HTTP/2 performance issues we should serve static assets (

CSS
ORG

,

JavaScript
ORG

, fonts, images) all from the origin (www.gov.uk).

Summary

In order to enable HTTP/2 on the GOV.UK domain (and for it to perform well) we need to all problems listed above:

SRI requires the

crossorigin
ORG

attribute to be set for it to work.

attribute to be set for it to work. The use of crossorigin=’anonymous’ breaks HTTP/1.1 connection reuse and HTTP/2 connection coalescing.

breaks HTTP/1.1 connection reuse and HTTP/2 connection coalescing. Switching to use

crossorigin=’use
GPE

-credentials’ isn’t possible because we serve our assets with

the Access-Control-Allow-Origin
ORG

: * header so that the assets leveraging SRI can be used on other domains (e.g. Heroku).

isn’t possible because we serve our assets with the header so that the assets leveraging SRI can be used on other domains (e.g. Heroku). Even if we serve the static assets from the origin, we will still need the

crossorigin
ORG

attribute because we will still be loading them with SRI enabled.

Therefore my recommendation is to remove the integrity and

crossorigin
ORG

attributes from our static resources (CSS, JS) and work towards serving these assets from the origin domain so we are no longer reliant on an individual browsers implementation of HTTP/2 connection coalescing. NOTE: it is possible to do these streams of work separately.

We should only serve our fonts with the Access-Control-Allow-Origin: * header (this is a requirement when using web fonts). All other assets can have this header removed (as we will have removed the

crossorigin
ORG

attribute). We only need to be serving the following font files:


EOT
CARDINAL

– For Internet Explorer only (6-11)

WOFF – For all modern versions of “evergreen” browsers and IE9-11.

WOFF2 – All modern browsers since

~2016
PERSON

. it’s usage trumps the use of WOFF.


EOT
CARDINAL

files are no longer going to be served to Internet Explorer once GOV.UK has migrated fully to

the Design System
FAC

. So

EOT
CARDINAL

can be removed in the future. There are no plans that I am aware of to create another

webfont
ORG

format, so we won’t be needing to add any more for the foreseeable future.

SRI Note: In serving the static assets from the origin domain, SRI no longer serves any purpose, as it’s a security measure intended to be used on

third
ORDINAL

party resources. As all of our assets will be first party, it really isn’t required and serves only to complicate our setup and impact on performance.

Serving static assets from the origin also clears a path for future web performance enhancements like HTTP/3, QUIC (Quick UDP Internet Connection) and

0-RTT
CARDINAL

. HTTP/3 has no support at the moment as it currently sits behind browser flags. But at some point in the future it will be supported and the asset domain will need to be removed for static assets anyway, to keep up with the latest protocol developments.

NOTE: As mentioned by

@david-ncsc
ORG

in his comment, there’s probably a security benefit to keeping the uploaded files on a separate origin as defence against uploaded malicious files. We should therefore only serve the static assets (

CSS
ORG

,

JavaScript
ORG

, fonts) from the origin. All other assets can stay the same.

Proposal

MUST

Only serve our font assets with

Access-Control-Allow-Origin
ORG

: * header (

WOFF2
PERSON

, WOFF,

EOT
CARDINAL

). – Medium (work required to modify NGINX config here and here)

header (

WOFF2
PERSON

, WOFF,

EOT
CARDINAL

). – Medium (work required to modify NGINX config here and here) Remove the integrity and

crossorigin
ORG

attributes from our static resources (CSS, JS) – Easy (PR’s quickly raised for all apps

CSS/JS
PRODUCT

).

and attributes from our static resources (CSS, JS) – Easy (PR’s quickly raised for all apps

CSS/JS
PRODUCT

). Serve static assets from the origin rather than the assets domain – Medium/Hard (spike already investigated by

@kevindew
LOC

and commented on here).

SHOULD

Review the use of

Access-Control
ORG

-Allow-Methods and Access-Control-Allow-Headers headers as from looking at the CORS documentation they aren’t actually needed. – Easy (Code resides here).

Consequences

Browsers that support HTTP/2 will be allowed to use it to its maximum potential. Browsers that don’t will still use HTTP/1.1, but will now only require

6
CARDINAL

TCP connections rather than

12
CARDINAL

.

Both

H1
CARDINAL

and H2 users should receive improved performance due to improved TCP connection efficiencies.

Under HTTP/2 we should see

only 2
CARDINAL

TCP connections on the WebPageTest connection graph, with the vast majority of assets loading via the same connection as the HTML.

How to enable HTTP/2

Once we have made the changes how do we enable HTTP/2, and what changes need to be made to the applications?