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.
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_urlis required—browsers use it to locate the certificate chain, which bundles the certificate and OCSP stapling information in CBOR format. Thecert.cborfile can live on any HTTPS-capable CDN or static file server; it need not share your origin.sxg_validitiy_urlis 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



