Signed HTTP Exchanges with nginx: A Setup Guide

Signed HTTP Exchanges (SXG) let you sign your site's responses so that content distributors can serve them while preserving your origin as the content creator. Chromium-based browsers including Chrome, Samsung Internet, and Microsoft Edge support SXG; browser support details are tracked in the Origin-Signed HTTP Exchanges feature entry.

Before You Begin

You will need full control over your domain's DNS entries, an HTTPS server capable of generating and serving SXG, and a dedicated certificate. The SXG certificate is distinct from your regular TLS key and certificate—you cannot reuse them.

This guide assumes an OpenSSL 1.1.1 environment (written against Ubuntu 18.04 LTS on amd64), sudo access, and nginx as the HTTP server. Certificate issuance relies on DigiCert, currently the only provider supporting the required SXG extensions. Example commands use website.test as the placeholder domain.

Step 1: Obtain an SXG-Ready Certificate

Generating SXG requires a TLS certificate carrying the CanSignHttpExchanges extension and a specific key type. DigiCert offers such certificates. Create a CSR first:

openssl ecparam -genkey -name prime256v1 -out mySxg.key
openssl req -new -key mySxg.key -nodes -out mySxg.csr -subj "/O=Test/C=US/CN=website.test"

The result is a CSR file in this form:

-----BEGIN CERTIFICATE REQUEST-----
MIHuMIGVAgEAMDMxDTALBgNVBAoMBFRlc3QxCzAJBgNVBAYTAlVTMRUwEwYDVQQD
DAx3ZWJzaXRlLnRlc3QwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAS7IVaeMvid
S5UO7BspzSe5eqT5Qk6X6dCggUiV/vyqQaFDjA/ALyTofgXpbCaksorPaDhdA+f9
APdHWkTbbdv1oAAwCgYIKoZIzj0EAwIDSAAwRQIhAIb7n7Kcc6Y6pU3vFr8SDNkB
kEadlVKNA24SVZ/hn3fjAiAS2tWXhYdJX6xjf2+DL/smB36MKbXg7VWy0K1tWmFi
Sg==
-----END CERTIFICATE REQUEST-----

Confirm two conditions when ordering the certificate: the validity period must not exceed 90 days, and the Include the CanSignHttpExchanges extension in the certificate checkbox (under Additional Certificate Options) must be enabled. Browsers and distributors will reject SXG from certificates that fail either condition. The instructions below assume your delivered certificate is named mySxg.pem.

The Include the CanSignHttpExchanges extension in the certificate checkbox.

Step 2: Install the SXG Generator

The SXG format is too complex to generate manually. Your options are the Go-based gen-signedexchange tool or Google's C library, libsxg. This guide uses libsxg.

Option A: Debian Package

If your system supports .deb packages and the libssl-dev version matches, install directly:

sudo apt install -y libssl-dev
wget https://github.com/google/libsxg/releases/download/v0.2/libsxg0_0.2-1_amd64.deb
wget https://github.com/google/libsxg/releases/download/v0.2/libsxg-dev_0.2-1_amd64.deb
sudo dpkg -i libsxg0_0.2-1_amd64.deb
sudo dpkg -i libsxg-dev_0.2-1_amd64.deb

Option B: Build from Source

For non-Debian environments, build manually. You'll need git, cmake, openssl, and gcc:

git clone https://github.com/google/libsxg
mkdir libsxg/build
cd libsxg/build
cmake .. -DRUN_TEST=false -DCMAKE_BUILD_TYPE=Release
make
sudo make install

Step 3: Add the nginx Module

The SXG module for nginx enables on-the-fly SXG generation, avoiding the need to pre-generate static SXG files.

Option A: Debian Package

Install the prebuilt module on Debian-based systems:

sudo apt install -y nginx=1.15.9-0
wget https://github.com/google/nginx-sxg-module/releases/download/v0.1/libnginx-mod-http-sxg-filter_1.15.9-0ubuntu1.1_amd64.deb
sudo dpkg -i libnginx-mod-http-sxg-filter_1.15.9-0ubuntu1.1_amd64.deb

Option B: Build from Source

Building the module requires the nginx source. Fetch the tarball and build the dynamic module alongside nginx:

git clone https://github.com/google/nginx-sxg-module
wget https://nginx.org/download/nginx-1.17.5.tar.gz
tar xvf nginx-1.17.5.tar.gz
cd nginx-1.17.5
./configure --prefix=/opt/nginx --add-dynamic-module=../nginx-sxg-module --without-http_rewrite_module --with-http_ssl_module
make
sudo make install

nginx's configuration is highly flexible; install it anywhere and specify the corresponding module, config, log, and pidfile paths. This guide uses /opt/nginx as the installation root.

Step 4: Configure the Plugin

If You Installed the Debian Module

SXG delivery mandates HTTPS. You will need two distinct certificate/key pairs: one for standard SSL/TLS and the SXG-specific one from Step 1. For a setup where SSL keys live in /path/to/ssl/ and SXG keys in /path/to/sxg/, your /etc/nginx/nginx.conf should resemble:

user www-data;
include /etc/nginx/modules-enabled/*.conf;

events {
     worker_connections 768;
}

http {
    include       mime.types;
    default_type  application/octet-stream;
    add_header  X-Content-Type-Options nosniff;

    server {
        listen 443 ssl;
        ssl_certificate     /path/to/ssl/fullchain.pem;
        ssl_certificate_key /path/to/ssl/privkey.pem;
        server_name  website.test;

        sxg on;
        sxg_certificate     /path/to/sxg/mySxg.pem;
        sxg_certificate_key /path/to/sxg/mySxg.key;
        sxg_cert_url        https://website.test/certs/cert.cbor;
        sxg_validity_url    https://website.test/validity/resource.msg;
        sxg_cert_path       /certs/cert.cbor;

        root /var/www/html;
    }
}

Two directives warrant attention:

  • sxg_cert_url is required—browsers use it to locate the certificate chain, which bundles the certificate and OCSP stapling information in CBOR format. The cert.cbor file can live on any HTTPS-capable CDN or static file server; it need not share your origin.
  • sxg_validitiy_url is intended to serve SXG-signature-header updates, saving bandwidth by avoiding full SXG re-downloads when content hasn't changed. This feature isn't implemented yet.

Start nginx to begin serving SXG:

sudo systemctl start nginx.service
curl -H"Accept: application/signed-exchange;v=b3" https://website.test/ > index.html.sxg
cat index.html.sxg
sxg1-b3...https://website.test/...(omit)

If You Built the Module from Source

For an nginx installation under /opt/nginx, configure it as follows:

load_module "/opt/nginx/modules/ngx_http_sxg_filter_module.so";

events {
    worker_connections 768;
}

http {
    include       mime.types;
    default_type  application/octet-stream;
    add_header X-Content-Type-Options nosniff;

    server {
        listen 443 ssl;
        ssl_certificate     /path/to/ssl/fullchain.pem;
        ssl_certificate_key /path/to/ssl/privkey.pem;
        server_name  example.com;

        sxg on;
        sxg_certificate     /path/to/sxg/mySxg.pem;
        sxg_certificate_key /path/to/sxg/mySxg.key;
        sxg_cert_url        https://website.test/certs/cert.cbor;
        sxg_validity_url    https://website.test/validity/resource.msg;
        sxg_cert_path       /certs/cert.cbor;

        root /opt/nginx/html;
    }
}

Launch nginx. Your SXG content is now live:

cd /opt/nginx/sbin
sudo ./nginx
curl -H "Accept: application/signed-exchange;v=b3" https://website.test/ > index.html.sxg
less index.html.sxg
sxg1-b3...https://website.test/...(omit)

Step 5: Enable Dynamic Application Backends

The previous configuration serves static files from the document root, but nginx's upstream directives let you apply SXG to arbitrary web application backends—Ruby on Rails, Django, Express, and others—as long as nginx fronts the HTTP(S) traffic:

upstream app {
    server 127.0.0.1:8080;
}

server {
    location / {
        proxy_pass http://app;
    }
}

Step 6: Verify Your SXG Output

Use the dump-signedexchange tool to check served SXGs for correctness. Run it and inspect the output for errors, headers, and body content:

go get -u github.com/WICG/webpackage/go/signedexchange/cmd/dump-signedexchange
export PATH=$PATH:~/go/bin
dump-signedexchange -verify -uri https://website.test/ | less