Deployment guide

Sozu reverse proxy 2.3.3

Overview

Sozu is an HTTP and HTTPS reverse proxy and load balancer written in Rust. You reconfigure it at runtime: add, change and remove clusters, frontends, backends and certificates through its command socket, without restarting the proxy or dropping connections. It speaks HTTP/1.1 and HTTP/2, terminates TLS with rustls and load-balances across backends with health checks.

This guide covers launching and verifying Sozu 2.3.3 on Ubuntu 24.04 from SecureAnyCloud on Amazon EC2. It is one version of the SecureAnyCloud 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.

SecureAnyCloud 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 SecureAnyCloud patch. Upstream Sozu presents a certificate compiled into the program, whose private key is public, to any client that asks 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. The source, patch, lockfile, toolchain and binary checksum are recorded in /usr/share/doc/sozu/BUILD-PROVENANCE.txt.

What you get:

  • 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. It serves a SecureAnyCloud landing page and a /sac-echo page that shows the forwarding headers a backend receives, so you can see the proxy working before you put your own servers behind it
  • 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, x86_64 architecture and an 8 GiB gp3 root volume
  • 24/7 SecureAnyCloud support

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 that first boot read from the instance metadata. These are 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". Any X-Forwarded-For, X-Forwarded-Proto, X-Forwarded-Port or X-Forwarded-Host header that 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). The proxy can never fetch instance credentials, even if a backend address points at the metadata service. Nothing broader is blocked, so 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. Sozu allows TLS 1.2 and 1.3 only, through rustls (AEAD ciphers only), with 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, 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 instead of falling 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.
  • Audited dependencies. There are no published security advisories for Sozu. SecureAnyCloud 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). 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, and details are in /usr/share/doc/sozu/CARGO-AUDIT.txt. SecureAnyCloud ships an updated version of this listing when Sozu publishes a new release.

The default certificate is a SecureAnyCloud 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. This pair is generated on your instance at first boot (CN=default.sozu.invalid) and differs 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

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

The recommended instance type is m5.large. Sozu is light, so size the instance for your traffic.

Connecting to your instance

The version is Sozu Reverse Proxy 2.3.3 on Ubuntu 24.04. The operating system is Ubuntu 24.04 LTS, and the SSH user is ubuntu.

1

Launch the instance

Option A: AWS Marketplace console

  • Open the SecureAnyCloud Hardened WireGuard VPN & VPN, Proxy and Remote Access AMIs listing on AWS Marketplace. Choose View purchase options, then Subscribe. One subscription covers every version in the listing.
  • When the subscription is active, choose Launch your software.
  • 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.
  • Pick an instance type (m5.large recommended) and your key pair.
  • Review the security group. The listing's default group allows SSH (22), HTTP (80, redirect only) and HTTPS (443). Restrict SSH to your own IP address.
  • Choose your VPC and a public subnet (or attach an Elastic IP later). Leave the metadata defaults, because the image already requires IMDSv2. Then choose Launch.

Option B: AWS CLI

Run this command 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. Replace 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.

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 connection details are in /root/sozu-info.txt, which only root can read. 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 (sac 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 /sac-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://secureanycloud.com/stacks/sozu-reverse-proxy-2-3-3/
# Bundle hub: https://secureanycloud.com/stacks/sozu-reverse-proxy-2-3-3/

SOZU_HOST is the instance's public DNS name. Use it in your browser instead of the IP address, 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 the certificate.

3

Confirm Sozu is running

Check that Sozu, the demo backend and first boot are active. Sozu listens on ports 80 and 443 on every interface, the demo backend listens 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

Run the sozu command with sudo to see the proxy's runtime state. Its first two lines report where the CLI itself logs. status lists the workers, and 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 │ ├──────────────────┼─────────────────────┼─────────────────────┼─────────────────────┼────────────────┤ │ sac-landing │ 3747573465220922778 │ 3747573465220922778 │ 3747573465220922778 │ │ └──────────────────┴─────────────────────┴─────────────────────┴─────────────────────┴────────────────┘
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.
  • The instance metadata service is unreachable from inside Sozu.

Run the self-test:

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
5

Send traffic through Sozu

From your workstation, open https://<SOZU_HOST>/ in a browser, or use curl. Accept the self-signed certificate after you compare its fingerprint with TLS_CERT_SHA256.

The landing page comes back over HTTP/2. 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, run the same checks. They cover 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 - sac</title> 301 https://ec2-44-210-92-136.compute-1.amazonaws.com/docs 404

The demo backend's /sac-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/sac-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
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 sac-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.

The following commands add a cluster for app.example.com on port 80 that forwards to a backend. The example uses the demo backend so that it works on its own, so use your server's address instead. The commands check the file, apply it and send a request through the new cluster:

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/sac-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 that Sozu already has. After you delete or edit an existing block, or change a listener, run sudo systemctl restart sozu. Connections are refused for a second or two during the restart.

For HTTPS on your own domain, give the frontend a certificate. Sozu picks the certificate 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, so put permanent ones in config.toml. sudo sozu --help lists every command.

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. The command refuses an IP address on its own, because TLS needs a name. An IP address listed after a name redirects port 80 to that name.

To use a certificate from a public CA, copy the key and full chain to /etc/sozu/tls/sac.key and /etc/sozu/tls/sac.crt (root:sozu, 0640 for the key). You can obtain the certificate with an ACME client on your workstation or another host. 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 }.

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 nobody can use Sozu as an open forward proxy. systemd denies the metadata addresses to every Sozu process:

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

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

9

The per-instance default certificate and the fail-closed start

Sozu starts only when this instance has its own certificates. The following points describe how, and how to check it.

  • sozu-firstboot.service runs once. It reads the instance's names from IMDSv2, mints the TLS key and certificate for those names, and mints the default key and certificate for any other name (sozu-set-host --new-default). It then 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/sac/data/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.

Check the fingerprint:

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 = sac 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 the default certificate. The patched sozu start loads the default pair before anything else and exits if either file is missing or invalid. In that case 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. Sozu 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.

The /usr/share/doc/sozu/ directory holds the licence, the source offer, the SecureAnyCloud 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-sac-default-cert.patch binary sha256 34135b7f014fce5752cbd1d536b4926413198aa2eb63c8e3e87d70c8bc5a8a5f patch sozu-2.3.3-sac-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)
10

Logs and updates

Sozu logs to the systemd journal. It writes 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. SecureAnyCloud 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.

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, and 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 SecureAnyCloud modification, the default-certificate patch shown below.

The 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. It is built with the exact Cargo.lock in /usr/share/doc/sozu/, which is 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-sac-default-cert.patch, Cargo.lock, BUILD-PROVENANCE.txt).

To rebuild it, unpack both crates side by side, run patch -p1 < sozu-2.3.3-sac-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 touches three 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" + +# sac 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 +// sac 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) }); + +/// sac 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 {

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

Deploy on AWS

Launch Sozu Reverse Proxy 2.3.3 on Ubuntu 24.04 on AWS with 24/7 support from SecureAnyCloud.

View on Marketplace

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on AWS