Skip to content

TLS and hosted deployment

Trimus's defaults target local, single-operator use: plain http://localhost, optional encryption, insecure cookies. Running Trimus as a shared, centrally-hosted service needs a stricter posture. This page covers the TRIMUS_MODE=hosted switch, TLS termination, and the TRIMUS_PUBLIC_URL contract.

Hosted mode (TRIMUS_MODE)

Mode Behavior
local (default) Current permissive behavior. Encryption optional, HTTP OK, insecure cookies. Nothing changes for existing installs.
hosted Enforces production-grade invariants at startup. The server refuses to boot (with an aggregated, actionable error) until they're met.

In hosted mode, config.Load() fails fast unless:

  • TRIMUS_ENCRYPTION_KEY is set — refusing to run with plaintext secrets at rest. Generate with openssl rand -base64 32. (Provider keys, OAuth tokens, and signed-trigger secrets are encrypted at rest via the KEK/DEK vault; see Secrets and encryption. Existing plaintext rows are migrated with trimus-cli encrypt-providers.)
  • TRIMUS_PUBLIC_URL is https://… — browsers drop Secure cookies over plain HTTP and workers/webhooks must reach a stable HTTPS endpoint. The http://localhost fallback is rejected.

When those pass, hosted mode also forces TRIMUS_SECURE_COOKIES=true.

Example failure:

TRIMUS_MODE=hosted: 2 invariant(s) not met:
  - TRIMUS_ENCRYPTION_KEY is required (generate: `openssl rand -base64 32`) — refusing to run with plaintext secrets
  - TRIMUS_PUBLIC_URL must be https:// (got "http://localhost:3100") — set a real HTTPS public URL or terminate TLS upstream

Test locally with TRIMUS_MODE=local, then flip to hosted when you deploy.

TLS

You have two options.

Run Trimus on plain HTTP behind nginx, Caddy, or a cloud load balancer. This is the most common and flexible topology (centralised certs, HTTP/2, OCSP stapling, etc.).

When you do this, set TRIMUS_TRUSTED_PROXIES to the proxy's CIDR so X-Forwarded-For is honoured (SEC-014), and make sure the proxy forwards X-Forwarded-Proto: https so Trimus knows the original scheme.

nginx

server {
    listen 443 ssl;
    server_name trimus.example.com;
    ssl_certificate     /etc/letsencrypt/live/trimus.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/trimus.example.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:3100;
        proxy_set_header Host              $host;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Caddy

trimus.example.com {
    reverse_proxy 127.0.0.1:3100
}

Cloud ALB / LB: terminate TLS at the LB, forward to the Trimus target group on :3100, and wire the health check to /readyz (readiness) and /healthz (liveness) — see Monitoring.

Option 2 — Built-in TLS

For simple single-node deployments without a separate proxy, point Trimus at a cert/key pair:

TRIMUS_TLS_CERT=/etc/tls/fullchain.pem
TRIMUS_TLS_KEY=/etc/tls/privkey.pem

Both must be set together (one without the other is a boot error). When set, the server listens with ListenAndServeTLS. Graceful shutdown works the same.

TRIMUS_PUBLIC_URL contract

TRIMUS_PUBLIC_URL is the externally-reachable base URL agents, workers, webhooks, Slack/Telegram links, and A2A federation use to reach this server. It must be stable and reachable from those clients.

  • It is validated for shape at startup (must be http(s)://host[:port]).
  • In hosted mode it must be https://.
  • Changing it requires re-registering workers and updating integration configs.
  • Set TRIMUS_SELFTEST_PUBLIC_URL=true to have the server background-probe <TRIMUS_PUBLIC_URL>/healthz after boot and WARN if it's unreachable (a quick way to catch DNS/TLS/proxy misconfiguration). The probe never blocks startup.

Health endpoints (for load balancers)

Path Use
/healthz, /healthz/live Liveness — always 200 if the process is up.
/readyz, /healthz/ready Readiness — checks DB and dependencies.

See Monitoring for details.

Worker co-location and proxies

When workers run on the same network as the server, add the server host to TRIMUS_NO_PROXY (or NO_PROXY) so worker→server traffic stays direct while LLM/API egress still routes through any configured proxy. See the proxy section of Environment.