Docs Casual Sheets

Self-hosting — TLS + custom domain

Let's Encrypt with each proxy, DNS pointers, CASUAL_PUBLIC_ORIGIN.

TLS termination happens at the reverse proxy. Casual Sheets itself listens on plain HTTP inside the container; that’s the standard shape for self-hosted services and keeps cert renewal cleanly isolated from the application lifecycle.

DNS

Point an A (or AAAA) record at your server’s public IP:

sheets.acme.example.   3600    IN  A   203.0.113.42

Or — if you’re under a CDN like Cloudflare with a proxied record — the record can be CNAME to the Cloudflare hostname and Cloudflare terminates the TLS.

Let’s Encrypt

Each proxy has its own automation:

  • Caddy — automatic. First request to the hostname triggers an HTTP-01 challenge; cert provisioned + renewed in the background.
  • Traefikcertificatesresolvers.letsencrypt.acme.* block in the static config (see reverse-proxy.md).
  • nginxcertbot --nginx -d sheets.acme.example. Cron / systemd timer handles renewal.

For all three: TCP/80 must be reachable from the public internet during the challenge (or use DNS-01 if you have API access to your DNS provider — Caddy + Traefik both support most DNS providers via plugins).

CASUAL_PUBLIC_ORIGIN

Once HTTPS is up, set the public origin so the server emits correct redirect URLs + WOPI BaseFileName values:

docker run -e CASUAL_PUBLIC_ORIGIN=https://sheets.acme.example ...

Or in the admin panel → NetworkingPublic origin. The admin write wins over env.

HSTS

Only emit when HTTPS terminates upstream — sending HSTS over HTTP locks users out for the max-age window. Once you’ve verified HTTPS works for two consecutive page loads, set CASUAL_HSTS_MAX_AGE=31536000 (1 year) in the admin panel → NetworkingHSTS max-age.

Most modern proxies will emit HSTS themselves; check whether yours does before setting it here too (duplicate headers are harmless but slightly noisy).

Sub-path vs sub-domain

Two shapes, both supported:

PatternWhenWhat changes
https://sheets.acme.example/Default. Dedicated subdomain.Nothing — defaults work.
https://acme.example/sheets/Sharing a domain with other apps.Set Base path to /sheets in the admin panel. Configure the proxy to forward /sheets/* (and /sheets/yjs) verbatim, without stripping the prefix.

The base-path mode also requires CASUAL_PUBLIC_ORIGIN to include the prefix:

CASUAL_PUBLIC_ORIGIN=https://acme.example/sheets

Verifying the cert chain

openssl s_client -showcerts -servername sheets.acme.example \
  -connect sheets.acme.example:443 < /dev/null \
  | openssl x509 -noout -subject -issuer -dates

Should print notBefore ≤ today ≤ notAfter with at least 30 days runway. All three proxies renew automatically; this is a human-readable sanity check after first provisioning.


Synced from docs/self-hosting/tls.md in schnsrw/sheets. To update: edit upstream and re-run npm run sync-docs.