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.

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.

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"

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.



