Delivering Signed HTTP Exchanges with nginx

Signed HTTP Exchanges (SXG) let you serve content on behalf of the original publisher while browsers render it as if it came directly from that publisher. This enables cross-site preloading without leaking the user's referrer or exposing the distribution path to the origin server. Below is how to fetch, serve and prefetch SXG files with nginx.

SXG browser support

Chrome is the only browser that currently supports SXG. For the latest status, check the Consensus & Standardization section of the Origin-Signed HTTP Exchanges feature entry on Chromestatus.

Fetching SXG files

Request an SXG file by sending the appropriate Accept header:

Accept: application/signed-exchange;v=b3,*/*;q=0.8

This guide assumes the SXG files are stored in /var/www/sxg.

Serving a simple SXG file

To distribute a single SXG file, attach the required headers:

Content-Type: application/signed-exchange;v=v3
X-Content-Type-Options: nosniff

Configure nginx accordingly:

http {
    ...
    types {
        application/signed-exchange;v=b3  sxg;
    }
    add_header X-Content-Type-Options nosniff;

    location / {
        more_set_headers "Content-Type: application/signed-exchange;v=b3";
        alias /var/www/sxg/;
        try_files $uri.sxg $uri =404;
        autoindex off;
    }
    ...

Reload the nginx configuration:

sudo systemctl restart nginx.service

Once live, Chrome displays the original content publisher's address in the URL bar when accessing your server.

Prefetching subresources

Most pages rely on multiple subresources: CSS, JavaScript, fonts, and images. Because an SXG cannot be modified without the content creator's private key, resolving subresources can cause issues.

Consider index.html.sxg from https://website.test/index.html that links to https://website.test/app.js. When the browser receives the SXG from https://distributor.test/example.com/index.html.sxg, it finds the link to https://website.test/app.js. The browser could fetch that resource directly during the preload phase, but doing so would reveal the distributor's identity to the content creator—violating privacy.

The link to app.js in distributor.test/index.html.sxg points to website.test/app.js.

If the distributor wants to serve an SXG version of the subresource—app.js.sxg—from its own infrastructure, rewriting the URL to something like https://distributor.test/website.test/app.js.sxg breaks the signature, invalidating the SXG.

An attempt to link the reference to app.js in distributor.test/index.html.sxg to distributor.test/app.js causes a signature mismatch.

Fortunately, an experimental subresource prefetching feature is available in Chrome. Enable it at about://flags/#enable-sxg-subresource-prefetching. Prefetching requires meeting two conditions:

  • The publisher must embed a response header entry in the SXG that lists the substitutable subresource with its integrity hash, e.g. link: <https://website.test/app.js>;rel="preload";as="script",<https://website.test/app.js>;rel="allowed-alt-sxg";header-integrity="sha256-h6GuCtTXe2nITIHHpJM+xCxcKrYDpOFcIXjihE4asxk="
  • The distributor must attach a response header when serving the SXG that provides the alternate SXG subresource path, e.g. link: <https://distributor.test/website.test/app.js.sxg>;rel="alternate";type="application/signed-exchange;v=b3";anchor="https://website.test/app.js"

anchor

The first condition is straightforward because nginx-sxg-module can calculate integrity hashes from upstream responses and embed them into link headers automatically. The second condition is harder, as the distributor must know every subresource a given SXG references.

For an SXG with a single subresource, https://website.test/app.js, the nginx config only needs:

add_header link <https://distributor.test/website.test/app.js.sxg>;rel="alter...

Real-world sites reference many subresources, making manual config impractical. There is currently no simple way to attach proper anchor link headers for an arbitrary SXG file, so expect updates as tooling improves.

Providing feedback

Chromium engineers welcome feedback on SXG distribution at [email protected]. You can also join the spec discussion on the WICG repository, or file a bug with the Chromium team using the relevant component.