Chronicle uses Caddy for automatic HTTPS — for both the advanced backend and the speaker-recognition service — so certificates are obtained and renewed automatically.
Modern browsers require HTTPS for:
- Microphone access over network (not localhost)
- Secure WebSocket connections (WSS)
- Remote access via Tailscale/VPN
- Production deployments
Note: the native mobile app does not require HTTPS — only browsers do.
Both services front their containers with Caddy:
| Service | Config | HTTP → HTTPS ports |
|---|---|---|
| Advanced Backend | backends/advanced/Caddyfile |
80 → 443 |
| Speaker Recognition | extras/speaker-recognition/Caddyfile |
8081 → 8444 |
The wizard records the chosen approach as HTTPS_CERT_MODE in each service's .env:
Caddy obtains and auto-renews the certificate itself — no cert files on disk, no
renewal cron. The Caddyfile has no tls directive; Caddy picks the right source from
the site address:
*.ts.net(Tailscale) → fetched from the localtailscaledat TLS-handshake time. The wizard mounts the hosttailscaled.sockinto the Caddy container via a generateddocker-compose.override.yml. Trusted on all devices in your tailnet.- Real domain → Let's Encrypt via ACME (requires ports 80/443 reachable). Trusted everywhere.
- IP address /
localhost→ Caddy's internal CA (self-signed). Browsers show a warning you can accept; the native app connects fine.
Renewal is automatic (~30 days before expiry), so a Caddy-managed deployment never hits an expired cert.
On Docker Desktop (macOS/Windows) the tailscaled socket isn't reachable from the
Docker VM, so Caddy can't fetch a *.ts.net cert. There the wizard issues the cert on
the host (tailscale cert → certs/server.crt/server.key, mounted into Caddy) and the
Caddyfile includes a tls /certs/... directive. services.py renews it on every
./start.sh / ./restart.sh if it's within 21 days of expiry — no cron needed, since
those boxes restart frequently.
When you run ./wizard.sh, the wizard:
- Asks if you want to enable HTTPS
- Prompts for your Tailscale name, domain, or IP
- Picks
caddyorstaticmode automatically (based on the address and whether thetailscaledsocket is available) - Generates the Caddyfile (and, for Caddy-managed Tailscale, the socket-mount override)
- Updates CORS settings for HTTPS origins
No manual certificate generation required.
For *.ts.net and real domains the certificate is publicly trusted — no warning. For an
IP or localhost address Caddy serves a self-signed internal-CA cert, so browsers warn:
- Click "Advanced"
- Click "Proceed … (unsafe)"
- Microphone access will now work
HTTPS not working:
- Check the Caddy containers are running:
docker compose ps(look forcaddy) - Confirm the served cert:
echo | openssl s_client -connect localhost:443 -servername <your-name> 2>/dev/null | openssl x509 -noout -issuer -enddate - For Caddy-managed Tailscale, confirm the socket is mounted:
docker inspect <caddy-container> --format '{{range .Mounts}}{{.Destination}} {{end}}'should list/var/run/tailscale/tailscaled.sock - Check you're using
https://nothttp://
Tailscale cert won't issue (500 ... failed to create DNS record):
- Ensure MagicDNS and HTTPS Certificates are enabled at https://login.tailscale.com/admin/dns
- These 500s are usually transient on Tailscale's side; retry after a short wait
Microphone not accessible:
- Ensure you're accessing via HTTPS (not HTTP)
- Accept the browser certificate warning (IP/localhost only)
- From a remote device use the Tailscale name/IP, not
localhost