How to get and serve SXG files using nginx, and the challenges of subresource prefetching.


![Hiroki Kumazaki](https://web.dev/images/authors/kumagi.jpg) Hiroki Kumazaki [X](https://twitter.com/kumagi) [GitHub](https://github.com/kumagi)

<br />

As a [Signed HTTP Exchanges (SXG)](https://developer.chrome.com/blog/signed-exchanges) distributor, you can deliver SXG files on behalf of the original content creators. Web browsers that support SXG will display such SXG files as if they were delivered from the original content creators. This enables you to implement cross-site preloading without violating privacy. This guide shows you how to distribute SXG properly.

## Cross-browser support

Chrome is currently the only browser that supports SXG. See the Consensus \& Standardization section of [Origin-Signed HTTP Exchanges](https://www.chromestatus.com/feature/5745285984681984) for more up-to-date information.

## Get SXG files

Specify in your `Accept` request header that you want the server to return an SXG file along with the request:

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

This guide assumes that you put your SXG files in `/var/www/sxg`.

## Serve a simple SXG file

Attach the following headers to distribute a single SXG file:

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

Configure `nginx`:

    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;
        }
        ...

Load the new configuration into `nginx`:

    sudo systemctl restart nginx.service

`nginx` will start serving SXG files.
When Chrome accesses your server, the address of the original content publisher will appear in the bar!

## Prefetch subresources

Most web pages consist of multiple subresources, such as CSS, JavaScript, fonts, and images.
The content of SXG cannot be changed without the content creator's private key.
This causes problems when the browser tries to resolve subresources.

For example, suppose `index.html.sxg` from `https://website.test/index.html` has a link to `https://website.test/app.js`. When a user's browser receives the SXG file from `https://distributor.test/example.com/index.html.sxg`, it will find the link to `https://website.test/app.js`.
The browser can fetch `https://website.test/app.js` directly on actual access, but it should not be done in the preload phase to preserve privacy.
If the resource was fetched during the preload phase, it would be possible for the content creator (`website.test`) to be able to detect which content distributor (`distributor.test`) is requesting the resource.

![The link to app.js in distributor.test/index.html.sxg points to website.test/app.js.](https://web.dev/static/articles/how-to-distribute-signed-http-exchanges/image/the-link-appjs-distrib-412bcf45b2ff8.png)

If the distributor wants to serve `app.js.sxg` from their own service and tries to modify `https://website.test/app.js` to be the distributor's version of that subresource (such as `https://distributor.test/website.test/app.js.sxg`), it will cause a signature mismatch and make the SXG invalid.

![An attempt to link the reference to app.js in distributor.test/index.html.sxg to distributor.test/app.js causes a signature mismatch.](https://web.dev/static/articles/how-to-distribute-signed-http-exchanges/image/an-attempt-link-referen-84afe29e1070f.png)

To solve this problem, there's an experimental SXG subresource prefetching feature in Chrome now.
You can enable it at: `about://flags/#enable-sxg-subresource-prefetching`.
To use subresource prefetching the following conditions must be met:

- The publisher must embed a response header entry in SXG, such as: `link: <https://website.test/app.js>;rel="preload";as="script",<https://website.test/app.js>;rel="allowed-alt-sxg";header-integrity="sha256-h6GuCtTXe2nITIHHpJM+xCxcKrYDpOFcIXjihE4asxk="`. This specifies the subresource that can be substituted with the SXG's specific integrity hash.
- The distributor must attach a response header when serving the SXG, such as: `link: <https://distributor.test/website.test/app.js.sxg>;rel="alternate";type="application/signed-exchange;v=b3";anchor="https://website.test/app.js"`. This specifies the path of `app.js` and corresponds to the subresource.

![anchor](https://web.dev/static/articles/how-to-distribute-signed-http-exchanges/image/anchor-c9f5e4dee9467.png)

The first one is relatively easy because [`nginx-sxg-module`](https://github.com/google/nginx-sxg-module) can calculate integrity hashes and embed them into link headers from upstream responses. But the second one is more difficult because the content distributor must be aware of the specified subresources in the SXG.

If there are no subresources other than `https://website.test/app.js`, then all you need to append in your nginx config is:

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

But such cases are rare because typical websites consist of a lot of subresources. Additionally, the distributor must attach the proper anchor link header when serving an SXG file. Currently, there is no easy way to resolve this issue, so stay tuned for updates!

## Send feedback

Chromium engineers are keen to hear your feedback on distributing SXG at [webpackage-dev@chromium.org](mailto:webpackage-dev@chromium.org).
You can also join [the spec discussion](https://github.com/WICG/webpackage/issues), or [report a bug](https://issues.chromium.org/issues/new?noWizard=true&template=0&component=1456695) to the team.
Your feedback will greatly help the standardization process and also help address implementation issues.
Thank you!