Sr
Networking AWS

Sozu Reverse Proxy 2.3.3 on AWS User Guide

| Product: Sozu Reverse Proxy 2.3.3 on Ubuntu 24.04 on AWS

Overview

This guide covers launching and verifying Sozu 2.3.3 on Ubuntu 24.04 from cloudimg on Amazon EC2. It is one version of the cloudimg Hardened WireGuard VPN & VPN, Proxy and Remote Access AMIs listing on AWS Marketplace; you pick it as the software version when you subscribe and launch. The other VPN, proxy and remote access servers in the listing are described on the listing's guide hub.

Sozu is an HTTP and HTTPS reverse proxy and load balancer written in Rust. Its defining feature is that it is reconfigured at runtime: you add, change and remove clusters, frontends, backends and certificates through its command socket without restarting the proxy or dropping connections, and its main process can even upgrade its own workers in place. It speaks HTTP/1.1 and HTTP/2, terminates TLS with rustls and load-balances across backends with health checks.

On this image Sozu serves HTTPS on port 443 with a certificate generated on your instance's first boot, and port 80 only redirects to HTTPS. Out of the box it proxies to a small demo backend on 127.0.0.1:8000 that serves a cloudimg landing page and a /cloudimg-echo page showing the forwarding headers a backend receives, so you can see the proxy working before you put your own servers behind it.

cloudimg builds Sozu 2.3.3 from the official crates.io release with cargo install --locked on a separate build machine, so no compiler, Rust toolchain or build cache is on this image. The build carries one small cloudimg patch: upstream Sozu presents a certificate compiled into the program, whose private key is public, to any client asking for a name you have not configured; this image uses a certificate generated on your instance instead (see Step 9). Two dependencies are raised to their fixed releases, so the dependency audit is clean. The build is reproducible (two independent builds gave the same binary), and the source, patch, lockfile, toolchain and binary checksum are recorded in /usr/share/doc/sozu/BUILD-PROVENANCE.txt.

What is on the image:

  • Sozu 2.3.3 (sozu.service) as /usr/local/bin/sozu, configured in /etc/sozu/config.toml, with Sozu's default features (rustls with the ring crypto provider, jemalloc)
  • HTTPS on 443 (TLS 1.2 and 1.3, HTTP/2) with a self-signed certificate generated on your instance's first boot; port 80 only redirects to HTTPS
  • A second certificate, also generated on your instance, for TLS clients that ask for a name you have not configured; no private key is built into the Sozu program
  • The sozu command-line tool, which talks to the proxy over a local UNIX socket only
  • A demo backend (sozu-backend.service) on 127.0.0.1:8000
  • Helpers: sozu-selftest, and sozu-set-host (your own DNS names and a new certificate)
  • No metadata access: Sozu cannot reach the instance metadata service

Default user: ubuntu · Architecture: x86_64 · Root volume: 8 GiB gp3

Security posture

  • No default credential and no shared secret. Sozu has no login of its own. It is administered only with the sozu command over SSH: the command socket /run/sozu/sozu.sock is a UNIX socket (mode 0600, owner sozu, in a 0750 directory), never a network port, so nobody can reconfigure the proxy over the network, and a non-root user on the instance cannot use it either. The only secrets on the instance are the two TLS private keys (the certificate for the instance's names and the default certificate for other names), which first boot generates for this instance only. No key is baked into the image, and the Sozu program itself contains none.
  • Only your names are served. Sozu answers only the names first boot read from the instance metadata: the public and private DNS names over HTTPS and on port 80, and the public and private IPv4 addresses on port 80, which redirect to the DNS name. A request for any other host name gets Sozu's 404 page and never reaches a backend. On 443 the Host header must match the TLS server name (Sozu's strict_sni_binding).
  • Client addresses come from the connection, never from headers. Both listeners use forwarded_headers = "rfc7239", so any X-Forwarded-For, X-Forwarded-Proto, X-Forwarded-Port or X-Forwarded-Host a client sends is removed before the request reaches a backend, and X-Real-IP is always replaced with the address Sozu saw on the connection. Sozu appends its own element to the standard Forwarded header; take the client address from X-Real-IP or the last Forwarded element.
  • No forward proxy. Sozu routes only to the backends you configure. A request for another site (GET http://other-host/...) matches no frontend and gets a 404.
  • No SSRF to instance credentials. sozu.service carries an IPAddressDeny rule for the metadata addresses (169.254.169.254, fd00:ec2::254, 168.63.129.16), so the proxy can never fetch instance credentials, even if a backend address points at the metadata service. Nothing broader is blocked: your backends can be anywhere. The demo backend may only talk to the loopback interface. The AMI is registered IMDSv2 only.
  • Least privilege. Sozu's main process and workers run as the unprivileged sozu user with one capability, CAP_NET_BIND_SERVICE (to bind 80 and 443), no new privileges, a read-only file system apart from /run/sozu, a private /tmp and no access to home directories. The sozu user can read its configuration and key but cannot change any file in /etc/sozu.
  • Modern TLS. TLS 1.2 and 1.3 only, through rustls (AEAD ciphers only), HTTP/2 by ALPN. HSTS is available per frontend once you use a CA-signed certificate.
  • Fails closed. Sozu starts only after first boot has written this instance's keys, certificates and names and a ready marker, and every start runs sozu config check first. If the default certificate or its key is missing, sozu start refuses to run rather than fall back to a built-in one. If first boot cannot finish, Sozu stays stopped and first boot tries again on the next boot.
  • Clean build. No compiler, Rust toolchain, protoc or development package is on the image, and every package comes from Ubuntu's main archive. SSH host keys and the machine ID are generated fresh on every instance.

Security notes for Sozu 2.3.3. There are no published security advisories for Sozu. cloudimg audited the exact dependency lockfile the binary is built from (cargo audit). Sozu 2.3.3's own lockfile pins two dependencies with low-impact advisories (ctutils 0.4.3, a constant-time comparison, and rustls-webpki 0.103.15, a certificate distrust update), so this build raises them to their fixed releases within the same version line (ctutils 0.4.7, rustls-webpki 0.103.16). The audit of the shipped lockfile reports no vulnerabilities; details are in /usr/share/doc/sozu/CARGO-AUDIT.txt. cloudimg ships an updated version of this listing when Sozu publishes a new release.

The default certificate (a cloudimg patch). When a TLS client asks for a name that matches none of your certificates, Sozu completes the handshake with a default certificate and then answers 404, so nothing is proxied. Upstream Sozu compiles that default certificate (CN=lolcatho.st) into the program together with its private key, which is public test material and the same in every Sozu build. On this image Sozu reads the default certificate from /etc/sozu/tls/default.crt and /etc/sozu/tls/default.key instead: a pair generated on your instance at first boot (CN=default.sozu.invalid), different on every instance. The Sozu program holds no private key, and if those files are missing Sozu refuses to start instead of falling back to a built-in pair. Never add the default certificate, or this instance's self-signed certificate, to a trust store. A client that sends no server name at all (an https://<ip-address>/ URL) gets no certificate, so always use a DNS name for HTTPS. See Step 9 and /usr/share/doc/sozu/DEFAULT-CERTIFICATE.txt.

Prerequisites

  1. An AWS account and permission to launch EC2 instances
  2. An EC2 key pair in the Region you launch in
  3. A VPC subnet that gives instances a public DNS name (the default VPC does), or your own DNS name for the instance
  4. Optionally, the backend servers you want Sozu to proxy to

Recommended instance type: m5.large. Sozu is light; size the instance for your traffic.

Connecting to your instance

Version Operating system SSH user
Sozu Reverse Proxy 2.3.3 on Ubuntu 24.04 Ubuntu 24.04 LTS ubuntu

Step 1: Launch the instance

Option A: AWS Marketplace console

  1. Open the cloudimg Hardened WireGuard VPN & VPN, Proxy and Remote Access AMIs listing on AWS Marketplace and choose View purchase options, then Subscribe. One subscription covers every version in the listing.
  2. Once the subscription is active, choose Launch your software.
  3. Choose One-click launch from AWS Marketplace (or Launch from EC2 Console for the full launch wizard). In Version, choose Sozu Reverse Proxy 2.3.3 on Ubuntu 24.04, then choose your Region.
  4. Pick an instance type (m5.large recommended) and your key pair.
  5. Security group: the listing's default group allows SSH (22), HTTP (80, redirect only) and HTTPS (443). Restrict SSH to your own IP address.
  6. Choose your VPC and a public subnet (or attach an Elastic IP later), leave the metadata defaults (the image already requires IMDSv2), then choose Launch.

Option B: AWS CLI

Run this on your own workstation. It creates a security group that allows SSH from your management address and the web ports from everyone, then launches the instance. Replace the AMI ID with the one shown for your Region on the Marketplace launch page, and the VPC, subnet, key pair and CIDR range with your own:

SG=$(aws ec2 create-security-group --group-name sozu \
  --description "Sozu reverse proxy" --vpc-id vpc-0123456789abcdef0 \
  --query GroupId --output text)
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol tcp --port 22  --cidr <your-mgmt-cidr>
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol tcp --port 80  --cidr 0.0.0.0/0
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol tcp --port 443 --cidr 0.0.0.0/0
aws ec2 run-instances \
  --image-id ami-0123456789abcdef0 \
  --instance-type m5.large \
  --key-name my-key-pair \
  --security-group-ids "$SG" \
  --subnet-id subnet-0123456789abcdef0 \
  --associate-public-ip-address \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=sozu}]'

You do not need --metadata-options: the AMI already makes IMDSv2 required.

Step 2: Connect and read the connection details

Connect as ubuntu with the key pair you chose:

ssh -i ~/.ssh/my-key-pair.pem ubuntu@<public-ip>

First boot finishes within a minute of launch. It issues the TLS certificate for the instance's DNS names and the default certificate for other names, writes the names Sozu serves into /etc/sozu/config.toml and starts Sozu. The details are in /root/sozu-info.txt, readable only by root. Nothing in it is a secret:

sudo cat /root/sozu-info.txt
# Sozu 2.3.3 - generated on first boot by sozu-firstboot.service. Nothing here is a secret.
# The TLS keys in /etc/sozu/tls were generated on this instance; none was baked into the image, and the sozu binary
# holds no key (cloudimg patch: the default certificate for unknown SNI names is /etc/sozu/tls/default.crt). Sozu has no
# login of its own: the landing page and /cloudimg-echo are public demo content; the command socket is local.

SOZU_HOST=ec2-44-210-92-136.compute-1.amazonaws.com
SOZU_URL=https://ec2-44-210-92-136.compute-1.amazonaws.com/
SOZU_NAMES=ec2-44-210-92-136.compute-1.amazonaws.com ip-172-31-95-247.ec2.internal
SOZU_IPS=44.210.92.136 172.31.95.247
TLS_CERT_SHA256=DB:A5:1F:71:A4:75:15:40:7A:17:E1:AD:6B:DD:00:74:B9:76:0C:52:10:4D:06:D8:A5:B7:4E:1F:2F:7F:CE:C1
TLS_DEFAULT_CERT_SHA256=CD:CF:DD:ED:97:89:35:E9:5F:5A:A0:9C:EB:93:7A:B1:E2:C7:80:A1:C8:CE:1A:3D:76:9E:E2:76:D7:2D:55:D0
SOZU_CONFIG=/etc/sozu/config.toml
SOZU_COMMAND_SOCKET=/run/sozu/sozu.sock

# Open TCP 443 (and 80, which only redirects) in the security group to the clients that should reach Sozu.
# Your backends:          add a [clusters.<name>] block to /etc/sozu/config.toml, then:
# Apply a change:         sudo sozu config check && sudo systemctl reload sozu   (adds new clusters/frontends/backends;
#                         to remove or change existing ones: sudo systemctl restart sozu)
# Runtime state:          sudo sozu status | sudo sozu cluster list | sudo sozu frontend list
# New DNS name:           sudo sozu-set-host <dns-name> ...   (restarts sozu)
# Self-test:              sudo sozu-selftest
# Guide:                  https://www.cloudimg.co.uk/guides/sozu-reverse-proxy-2-3-3-on-ubuntu-24-04-aws/
# Bundle hub:             https://www.cloudimg.co.uk/guides/vpn-proxy-aws/

SOZU_HOST is the instance's public DNS name: use it, not the IP address, in your browser, because Sozu picks the certificate from the name the client asks for. TLS_CERT_SHA256 is the fingerprint of this instance's certificate; compare it with the one your browser shows before you accept it.

Step 3: Confirm Sozu is running

Sozu, the demo backend and first boot are active. Sozu listens on ports 80 and 443 on every interface, the demo backend only on 127.0.0.1, the command socket is a UNIX socket, and nothing listens on UDP:

systemctl is-active sozu.service sozu-backend.service sozu-firstboot.service
sozu --version | head -1
sudo ss -Htlnp '( sport = :80 or sport = :443 or sport = :8000 )' | awk '{print $4}' | sort -u
sudo ss -Hxl | awk '{print $5}' | grep sozu
sudo ss -Hulnp | grep -c -E 'sozu|python3' || true
active
active
active
sozu 2.3.3 ()
0.0.0.0:443
0.0.0.0:80
127.0.0.1:8000
/run/sozu/sozu.sock
0

The sozu command (run with sudo) shows the proxy's runtime state (its first two lines report where the CLI itself logs). status lists the workers; cluster list and frontend list show what is routed:

sudo sozu status
sudo sozu cluster list
Logs will be sent to stdout
Access logs will be sent to None
Success: Successfully collected the status of workers
┌───────────┬──────┬───────────┐
│ worker id │ pid  │ run state │
├───────────┼──────┼───────────┤
│ 0         │ 8409 │ RUNNING   │
├───────────┼──────┼───────────┤
│ 1         │ 8410 │ RUNNING   │
└───────────┴──────┴───────────┘
Logs will be sent to stdout
Access logs will be sent to None
Success: Successfully queried clusters
┌──────────────────┬─────────────────────┬─────────────────────┬─────────────────────┬────────────────┐
│ cluster id       │ worker 0            │ worker 1            │ worker main         │ desynchronized │
├──────────────────┼─────────────────────┼─────────────────────┼─────────────────────┼────────────────┤
│ cloudimg-landing │ 3747573465220922778 │ 3747573465220922778 │ 3747573465220922778 │                │
└──────────────────┴─────────────────────┴─────────────────────┴─────────────────────┴────────────────┘

Step 4: Run the self-test

The shipped self-test checks the whole path from the instance itself: Sozu runs unprivileged, the landing page is served over HTTPS for the instance's name with this instance's certificate, port 80 redirects to HTTPS, a host name the instance does not serve gets no backend, forged client-address headers never reach the backend, the command socket is local and only ports 22, 80 and 443 are open, and the instance metadata service is unreachable from inside Sozu:

sudo sozu-selftest
ok: sozu is active: 3 processes (main + workers), user sozu, CAP_NET_BIND_SERVICE only
ok: https://ec2-44-210-92-136.compute-1.amazonaws.com/ serves the landing page through Sozu with this instance's certificate (DB:A5:1F:71:A4:75:15:40...)
ok: an unknown TLS name gets this instance's own default certificate (CD:CF:DD:ED:97:89:35:E9...), not a built-in one
ok: port 80 redirects to https://ec2-44-210-92-136.compute-1.amazonaws.com/ (IP Hosts too); an unknown Host gets 404 and no backend on 80 and 443
ok: client IP from the socket: X-Real-IP = 172.31.95.247; a forged X-Forwarded-For / X-Real-IP never reaches the backend
ok: the command socket is a local UNIX socket (0600 sozu); off-box TCP is exactly 22, 80, 443; sozu holds only 80 and 443
ok: metadata: 169.254.169.254 is unreachable from inside sozu
SELFTEST_OK

Step 5: Send traffic through Sozu

From your workstation, open https://<SOZU_HOST>/ in a browser (accept the self-signed certificate after comparing its fingerprint with TLS_CERT_SHA256), or use curl. The landing page comes back over HTTP/2, and plain HTTP to the name or to the IP address redirects to the HTTPS name:

curl -sk -o /dev/null -w '%{http_code} %{http_version}\n' https://ec2-44-210-92-136.compute-1.amazonaws.com/
200 2
curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' http://44.210.92.136/
301 https://ec2-44-210-92-136.compute-1.amazonaws.com/

On the instance, the same checks: the landing page over HTTPS, the redirect on port 80, and a 404 with no backend for a name the instance does not serve:

H=$(sudo sed -n 's/^SOZU_HOST=//p' /root/sozu-info.txt)
curl -sk --resolve "$H:443:127.0.0.1" "https://$H/" | grep -o '<title>[^<]*</title>'
curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' -H "Host: $H" http://127.0.0.1/docs
curl -s -o /dev/null -w '%{http_code}\n' -H 'Host: unknown.example' http://127.0.0.1/
<title>Sozu reverse proxy - cloudimg</title>
301 https://ec2-44-210-92-136.compute-1.amazonaws.com/docs
404

The demo backend's /cloudimg-echo page shows the forwarding headers it received. Here the request goes to the instance's private address with forged X-Forwarded-For, X-Real-IP and Forwarded headers: the forged X-Forwarded-For never arrives, X-Real-IP is the real peer address, and Sozu's own element is the last one in Forwarded:

H=$(sudo sed -n 's/^SOZU_HOST=//p' /root/sozu-info.txt); PRIV=$(hostname -I | awk '{print $1}')
curl -sk --resolve "$H:443:$PRIV" -H 'X-Forwarded-For: 203.0.113.66' -H 'X-Real-IP: 203.0.113.67' \
  -H 'Forwarded: for=203.0.113.68' "https://$H/cloudimg-echo"
backend-peer: 127.0.0.1
host: ec2-44-210-92-136.compute-1.amazonaws.com
forwarded: for=203.0.113.68, proto=https;for="172.31.95.247:53314";by=0.0.0.0
x-forwarded-for: (absent)
x-forwarded-proto: (absent)
x-forwarded-port: (absent)
x-forwarded-host: (absent)
x-real-ip: 172.31.95.247

Step 6: Point Sozu at your own backends

/etc/sozu/config.toml holds the global settings, the two listeners and the clusters. Each cluster has frontends (an address plus a host name, optionally a path, a redirect policy or a certificate) and backends (the servers the traffic goes to). The cloudimg-landing cluster sits between two marker lines that sozu-set-host manages; add your own clusters below it. The file ends with a commented example.

This adds a cluster for app.example.com on port 80 that forwards to a backend (here the demo backend, so the example works on its own; use your server's address), checks the file, applies it and sends a request through it:

sudo tee -a /etc/sozu/config.toml >/dev/null <<'TOML'

[clusters.my-app]
protocol = "http"
load_balancing = "ROUND_ROBIN"
frontends = [
    { address = "0.0.0.0:80", hostname = "app.example.com" },
]
backends = [
    { address = "127.0.0.1:8000", backend_id = "my-app-1" },
]
TOML
sudo sozu config check
sudo systemctl reload sozu
curl -s -H 'Host: app.example.com' http://127.0.0.1/cloudimg-echo | head -3
Logs will be sent to stdout
Access logs will be sent to None
Configuration file is valid
backend-peer: 127.0.0.1
host: app.example.com
forwarded: proto=http;for="127.0.0.1:53622";by=0.0.0.0

systemctl reload sozu adds the clusters, frontends and backends that are new in the file without dropping a connection. It does not remove or change ones Sozu already has: after you delete or edit an existing block, or change a listener, run sudo systemctl restart sozu (a second or two of refused connections).

For HTTPS on your own domain, give the frontend a certificate; Sozu picks it by the name the client asks for:

    { address = "0.0.0.0:443", hostname = "app.example.com",
      certificate = "/etc/sozu/tls/app.crt", key = "/etc/sozu/tls/app.key", certificate_chain = "/etc/sozu/tls/app-chain.pem" },

Keep key files root:sozu, mode 0640, in /etc/sozu/tls. Add more backends lines to load-balance; load_balancing can be ROUND_ROBIN, RANDOM, LEAST_LOADED or POWER_OF_TWO.

Runtime changes. The sozu command changes the running proxy without touching the file, for example sudo sozu backend add --id my-app --backend-id my-app-2 --address 10.0.1.11:8080 or sudo sozu backend remove .... Runtime changes last until the next restart; put permanent ones in config.toml. sudo sozu --help lists every command.

Step 7: Use your own domain name and certificate

Point a DNS record (for example proxy.example.com) at the instance's public or Elastic IP address, then give the landing cluster that name. sozu-set-host issues a new self-signed certificate for the names you pass, rewrites the landing frontends between the marker lines and restarts Sozu:

sudo sozu-set-host proxy.example.com
certificate issued for DNS:proxy.example.com
sozu now serves: proxy.example.com
sozu redirects these addresses to https:
SHA-256 fingerprint: ...
default certificate (unknown SNI names) SHA-256 fingerprint: ...

The default certificate is kept; sudo sozu-set-host --new-default also replaces it. Run sudo sozu-set-host with no argument to go back to the instance's own names. An IP address on its own is refused (TLS needs a name); listed after a name, it redirects port 80 to that name.

To use a certificate from a public CA (for example one you obtain with an ACME client on your workstation or another host), copy the key and full chain to /etc/sozu/tls/cloudimg.key and /etc/sozu/tls/cloudimg.crt (root:sozu, 0640 for the key), then run sudo sozu config check && sudo systemctl restart sozu. Once the certificate is CA-signed you can enable HSTS on a frontend with hsts = { enabled = true }.

Step 8: Forward proxy and metadata access are blocked

A request that names another site in its request line matches no frontend and is answered 404, so Sozu cannot be used as an open forward proxy. The metadata addresses are denied to every Sozu process by systemd:

H=$(sudo sed -n 's/^SOZU_HOST=//p' /root/sozu-info.txt)
printf 'GET http://169.254.169.254/latest/meta-data/ HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n' "$H" | timeout 5 nc 127.0.0.1 80 | head -1
systemctl show -p IPAddressDeny sozu.service
HTTP/1.1 404 Not Found
IPAddressDeny=fd00:ec2::254/128 169.254.169.254/32 168.63.129.16/32

cloudimg's build checks also add a cluster whose backend is 169.254.169.254:80 through the command socket: Sozu answers 503 (it cannot connect), while the same cluster with the rule removed reaches the metadata service.

Step 9: The per-instance default certificate and the fail-closed start

  • sozu-firstboot.service runs once: it reads the instance's names from IMDSv2, mints the TLS key and certificate for those names and the default key and certificate for any other name (sozu-set-host --new-default), writes a ready marker, starts the demo backend and Sozu, waits for an HTTPS request to traverse the proxy and writes /root/sozu-info.txt. If any step fails, Sozu stays stopped and first boot runs again on the next boot.
  • sozu.service has ConditionPathExists=/var/lib/cloudimg/sozu-ready and runs sozu config check before every start.
  • The default certificate is this instance's own. A TLS client that asks for a name Sozu does not serve gets the certificate in /etc/sozu/tls/default.crt (generated on this instance, CN=default.sozu.invalid) and then a 404. Its fingerprint is TLS_DEFAULT_CERT_SHA256 in /root/sozu-info.txt:
echo | openssl s_client -connect 127.0.0.1:443 -servername unknown.example 2>/dev/null | openssl x509 -noout -subject -fingerprint -sha256
sudo sed -n 's/^TLS_DEFAULT_CERT_SHA256=//p' /root/sozu-info.txt
sudo stat -c '%n %a %U:%G' /etc/sozu/tls/default.crt /etc/sozu/tls/default.key
subject=CN = default.sozu.invalid, O = cloudimg
sha256 Fingerprint=CD:CF:DD:ED:97:89:35:E9:5F:5A:A0:9C:EB:93:7A:B1:E2:C7:80:A1:C8:CE:1A:3D:76:9E:E2:76:D7:2D:55:D0
CD:CF:DD:ED:97:89:35:E9:5F:5A:A0:9C:EB:93:7A:B1:E2:C7:80:A1:C8:CE:1A:3D:76:9E:E2:76:D7:2D:55:D0
/etc/sozu/tls/default.crt 600 sozu:sozu
/etc/sozu/tls/default.key 600 sozu:sozu
  • Sozu refuses to start without it. The patched sozu start loads the default pair before anything else and exits if either file is missing or invalid, so sozu.service fails with refusing to start Sōzu: no valid default certificate in /etc/sozu/tls/default.crt and /etc/sozu/tls/default.key and nothing listens on 443. It never falls back to a certificate built into the program. To mint a new pair (for example after deleting it), run sudo sozu-set-host --new-default and then sudo systemctl restart sozu.
  • /usr/share/doc/sozu/ holds the licence, the source offer, the cloudimg patch, the build provenance, the exact Cargo.lock, the dependency audit and the note on the default certificate:
ls /usr/share/doc/sozu
grep -E '^(binary sha256|patch|features|toolchain)' /usr/share/doc/sozu/BUILD-PROVENANCE.txt
BUILD-PROVENANCE.txt
CARGO-AUDIT.txt
Cargo.lock
DEFAULT-CERTIFICATE.txt
LICENSE
SOURCE
sozu-2.3.3-cloudimg-default-cert.patch
binary sha256     34135b7f014fce5752cbd1d536b4926413198aa2eb63c8e3e87d70c8bc5a8a5f
patch             sozu-2.3.3-cloudimg-default-cert.patch (sha256 3cdb3619ac376c79affb38c0324d3c740171ad018b7126cf316246868659c353; a copy is next to this file):
features          Sozu's defaults: jemallocator + crypto-ring (no aws-lc-rs, no rustls-openssl, no tui)
toolchain         rustup 1.29.2 (rustup-init sha256 5be191e8c70a5007074254cb74d78dbfdd25cd5a61c27de7b8e5410122ec24f4), rustc 1.99.1 (58cf8f9c3 2026-10-13), cargo 1.99.1 (5f94df478 2026-08-27)

Step 10: Logs and updates

Sozu logs to the systemd journal: one access line per request (cluster, backend, timings, status) and its own events:

sudo journalctl -u sozu -n 3 --no-pager -o cat | cut -c1-120
2026-10-31T16:06:26.087462Z 1793462786087462042 10503 INFO-ACCESS  WRK-01   [01M69BCP16F6E28Z1KSGYFRB9F 01M69BCP16CE
2026-10-31T16:06:26.095633Z 1793462786095633710 10502 INFO-ACCESS  WRK-00   [01M69BCP1FK28JXZTCYEQRF68A 01M69BCP1FDC
2026-10-31T16:06:26.168221Z 1793462786168221268 10503 INFO-ACCESS  WRK-01   [01M69BCP3RDYB8GN2H8MES9ZNR 01M69BCP3RT3

Ubuntu security updates install automatically (unattended-upgrades). Sozu itself is not an apt package; cloudimg publishes a new version of this listing when Sozu releases one. To move to it, launch the new version and copy your /etc/sozu/config.toml and certificates across.

Step 11: Security recommendations

  • Restrict SSH (22) to your management addresses, and 80/443 to the clients that should reach Sozu.
  • Use your own DNS name and a CA-signed certificate in production, then enable HSTS.
  • Point backends at private addresses in your VPC, and let only the Sozu instance reach them (security group to security group).
  • Trust the client address in X-Real-IP (or the last Forwarded element) only from Sozu, never from the open internet.
  • Keep command_socket on its UNIX path; never expose it on a TCP address.
  • Run sudo sozu-selftest after configuration changes.

Support and licensing

Sozu is free software licensed under the GNU Affero General Public License v3 (/usr/share/doc/sozu/LICENSE). This image runs Sozu 2.3.3 with one cloudimg modification, the default-certificate patch below. Its complete corresponding source is the upstream release at github.com/sozu-proxy/sozu/tree/2.3.3 (the crates.io packages sozu 2.3.3 and sozu-lib 2.3.3) plus this patch, built with the exact Cargo.lock in /usr/share/doc/sozu/ (the package's own lock with ctutils 0.4.7 and rustls-webpki 0.103.16). Every instance carries the same files in /usr/share/doc/sozu/ (SOURCE, sozu-2.3.3-cloudimg-default-cert.patch, Cargo.lock, BUILD-PROVENANCE.txt). To rebuild it, unpack both crates side by side, run patch -p1 < sozu-2.3.3-cloudimg-default-cert.patch, copy the Cargo.lock into sozu-2.3.3/ and run cargo install --path sozu-2.3.3 --locked. If you modify Sozu further and let users interact with it over a network, section 13 of the AGPL requires you to offer them your modified source.

The patch (3 files: sozu-lib/src/tls.rs reads the default certificate from files and adds check_default_certificate, sozu/src/main.rs makes sozu start call it first, sozu/Cargo.toml builds against the patched sozu-lib):

diff -ruN a/sozu-2.3.3/Cargo.toml b/sozu-2.3.3/Cargo.toml
--- a/sozu-2.3.3/Cargo.toml
+++ b/sozu-2.3.3/Cargo.toml
@@ -231,3 +231,7 @@

 [target.'cfg(target_os = "linux")'.dependencies.num_cpus]
 version = "^1.17.0"
+
+# cloudimg patch: build against the patched sozu-lib 2.3.3 (default certificate read from files)
+[patch.crates-io.sozu-lib]
+path = "../sozu-lib-2.3.3"
diff -ruN a/sozu-2.3.3/src/main.rs b/sozu-2.3.3/src/main.rs
--- a/sozu-2.3.3/src/main.rs
+++ b/sozu-2.3.3/src/main.rs
@@ -69,6 +69,8 @@
     Harden(util::UtilError),
     #[error("failed to start Sōzu: {0}")]
     StartMain(StartError),
+    #[error("refusing to start Sōzu: {0}")]
+    DefaultCertificate(String),
     #[error("failed to start new worker: {0}")]
     BeginWorker(WorkerError),
     #[error("failed to start new main process: {0}")]
@@ -95,7 +97,9 @@
     register_panic_hook();

     let result = match args.cmd {
-        cli::SubCmd::Start => begin_main_process(&args).map_err(MainError::StartMain),
+        cli::SubCmd::Start => sozu::tls::check_default_certificate()
+            .map_err(MainError::DefaultCertificate)
+            .and_then(|()| begin_main_process(&args).map_err(MainError::StartMain)),
         // this is used only by the CLI when upgrading
         cli::SubCmd::Worker {
             fd: worker_to_main_channel_fd,
diff -ruN a/sozu-lib-2.3.3/src/tls.rs b/sozu-lib-2.3.3/src/tls.rs
--- a/sozu-lib-2.3.3/src/tls.rs
+++ b/sozu-lib-2.3.3/src/tls.rs
@@ -56,12 +56,19 @@
 // -----------------------------------------------------------------------------
 // Default ParsedCertificateAndKey

+// cloudimg patch: the default (unmatched-SNI) certificate and key are read from these files instead of the
+// compiled-in lib/assets pair (whose private key is public and identical in every build). No embedded fallback:
+// if a file is missing or invalid there is no default certificate, and `sozu start` refuses to run (see
+// `check_default_certificate`).
+pub const DEFAULT_CERTIFICATE_PATH: &str = "/etc/sozu/tls/default.crt";
+pub const DEFAULT_KEY_PATH: &str = "/etc/sozu/tls/default.key";
+
 static DEFAULT_CERTIFICATE: LazyLock<Option<Arc<CertifiedKey>>> = LazyLock::new(|| {
     let add = AddCertificate {
         certificate: CertificateAndKey {
-            certificate: include_str!("../assets/certificate.pem").to_string(),
-            certificate_chain: vec![include_str!("../assets/certificate_chain.pem").to_string()],
-            key: include_str!("../assets/key.pem").to_string(),
+            certificate: std::fs::read_to_string(DEFAULT_CERTIFICATE_PATH).ok()?,
+            certificate_chain: vec![],
+            key: std::fs::read_to_string(DEFAULT_KEY_PATH).ok()?,
             versions: vec![],
             names: vec![],
         },
@@ -70,6 +77,16 @@
     };
     build_validated_certificate(&add).ok().map(|c| c.inner)
 });
+
+/// cloudimg patch: load the default certificate now, so that a missing or invalid file stops `sozu start`.
+pub fn check_default_certificate() -> Result<(), String> {
+    match DEFAULT_CERTIFICATE.as_ref() {
+        Some(_) => Ok(()),
+        None => Err(format!(
+            "no valid default certificate in {DEFAULT_CERTIFICATE_PATH} and {DEFAULT_KEY_PATH}"
+        )),
+    }
+}

 #[derive(thiserror::Error, Debug)]
 pub enum CertificateResolverError {

cloudimg provides the hardened image, its first-boot configuration and these guides. For help with this image, contact cloudimg support through the AWS Marketplace listing. Sozu's own documentation is at github.com/sozu-proxy/sozu/tree/2.3.3/doc.