| title | Reverse proxy (HTTPS) |
|---|---|
| category | Getting started |
| order | 25 |
| description | Terminate TLS in front of Terrence with Caddy or nginx so terraform login, callbacks, and webhooks work. |
Terrence serves plain HTTP (default port 3000) and never terminates TLS itself. For anything beyond a local quickstart, put a reverse proxy in front of it. You need this because:
terraform loginrequires the instance to be reachable over HTTPS.- Login callbacks, webhook deliveries, registry URLs, and signed URLs are built from the instance base URL.
- Secure cookies are only set on HTTPS origins.
Set the public URL of the instance. It is authoritative for every generated link (login callbacks, webhooks, registry hostname, signed URLs):
PUBLIC_URL=https://terraform.example.comWithout PUBLIC_URL, Terrence derives the base URL from Host (or the connection address as a last resort). X-Forwarded-Host/X-Forwarded-Proto are honored only when the socket peer matches TERRENCE_TRUSTED_PROXY_CIDRS (or the trusted-client-ip-cidrs general setting). That fallback is best-effort: proxy deployments should always set PUBLIC_URL.
Tell Terrence which proxies to trust so forwarded client addresses are honored for audit records and rate limits. The socket peer must be in one of these CIDRs before any forwarded header is read:
TERRENCE_TRUSTED_PROXY_CIDRS=127.0.0.1/32,10.0.0.0/8The same trust can be managed at runtime under Site Admin settings (general keys trusted-client-ip-cidrs and trusted-client-ip-headers), which take precedence over the environment variable. With no trusted proxy configured, the socket peer address is authoritative and forwarded headers are ignored.
Caddy terminates TLS automatically (Let's Encrypt) with a three-line file:
terraform.example.com {
reverse_proxy 127.0.0.1:3000
}Caddy sets X-Forwarded-Host and X-Forwarded-Proto for you. Keep PUBLIC_URL=https://terraform.example.com and add the proxy host to TERRENCE_TRUSTED_PROXY_CIDRS when Caddy runs on another machine.
Terminate TLS with certbot (or your own certificates) and forward the host and scheme explicitly:
server {
listen 443 ssl;
server_name terraform.example.com;
ssl_certificate /etc/letsencrypt/live/terraform.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/terraform.example.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# State uploads reach 100 MiB: raise the 1 MB default body cap.
client_max_body_size 120m;
# Runs stream over long-lived connections: do not cut idle reads.
proxy_read_timeout 1h;
}
}
server {
listen 80;
server_name terraform.example.com;
return 301 https://$host$request_uri;
}As with Caddy, keep PUBLIC_URL set to the public origin and list the nginx host in TERRENCE_TRUSTED_PROXY_CIDRS when it is not localhost.
Terrence requires deployment at the root of a domain or subdomain (e.g. https://terraform.example.com). Subpath deployment (e.g. https://example.com/terrence/) is deliberately not supported: Terraform/OpenTofu CLI service discovery protocols (/.well-known/terraform.json) and standard OAuth callback specifications expect root-level resolution.
When terminating TLS with an internal or organizational certificate authority:
- Ensure client machines have the organizational root CA installed in their local system trust store.
- For Terraform and OpenTofu CLI runs on machines where the CLI uses its own trust store, configure
SSL_CERT_FILE=/path/to/ca-bundle.crtorCURL_CA_BUNDLE=/path/to/ca-bundle.crt. - For containerized or agent executions, mount the CA bundle into
/etc/ssl/certs/to prevent certificate verification errors during CLI init or discovery.
- Open
https://terraform.example.comand sign in. - Run
terraform login terraform.example.comfrom your machine. The browser flow opens against the public URL and the CLI stores the token. - If login still fails, check
terraform logintroubleshooting: instance reachable over HTTPS,PUBLIC_URLmatches the origin, and the proxy forwardsHost/X-Forwarded-Proto.