Skip to main content
This page covers Docker Compose deployments. On Kubernetes and AWS Fargate, terminate TLS at your ingress controller or load balancer instead.

Inbound HTTPS with Caddy

Tracecat ships with Caddy as a reverse proxy. Caddy automatically provisions and renews TLS certificates from Let’s Encrypt when configured with a domain name.
1

Update environment variables

In your .env file, set BASE_DOMAIN to your domain and switch every public URL and the allowed origins to HTTPS:
BASE_DOMAIN=tracecat.example.com
PUBLIC_APP_URL=https://tracecat.example.com
PUBLIC_API_URL=https://tracecat.example.com/api
TRACECAT__ALLOW_ORIGINS=https://tracecat.example.com
Mixing HTTP and HTTPS across these values causes CORS failures. Keep the scheme consistent everywhere.
2

Expose ports 80 and 443

Update the caddy service ports in your docker-compose.yml:
services:
  caddy:
    ports:
      - "80:80"
      - "443:443"
Port 80 must remain open for the ACME HTTP-01 challenge. Port 443 serves HTTPS traffic.
3

Restart the stack

docker compose up -d
Caddy will automatically obtain and renew certificates on startup.

Bring your own certificate

If your domain is internal or cannot use Let’s Encrypt, supply your own certificate and private key with Caddy’s tls directive. Mount the files into the caddy service and reference them in your Caddyfile:
{$BASE_DOMAIN} {
  tls /etc/caddy/certs/tls.crt /etc/caddy/certs/tls.key
  # ... existing reverse_proxy configuration ...
}

Cloud VMs

If you deploy into a cloud VM (for example an EC2 instance), we recommend placing a managed load balancer such as an AWS Application Load Balancer in front of Caddy. Terminate TLS at the load balancer with an ACM certificate and forward traffic to Caddy, so certificates and public HTTPS are handled by managed infrastructure instead of the VM. With TLS terminated at the load balancer, keep Caddy in HTTP-only mode. Do not set BASE_DOMAIN to a bare domain as in the Let’s Encrypt setup above — that enables Caddy’s automatic HTTPS, which redirects the load balancer’s forwarded HTTP requests back to HTTPS and creates a redirect loop. Keep the default port-only BASE_DOMAIN and set only the public URLs to HTTPS:
BASE_DOMAIN=:80
PUBLIC_APP_URL=https://tracecat.example.com
PUBLIC_API_URL=https://tracecat.example.com/api
TRACECAT__ALLOW_ORIGINS=https://tracecat.example.com
Point the load balancer’s HTTPS listener at Caddy’s HTTP port (80), and redirect HTTP to HTTPS at the load balancer.

Browsers force HTTPS

If the browser fails with ERR_CONNECTION_REFUSED while curl over HTTP works, your deployment is serving plain HTTP but the browser is trying to reach it over HTTPS. This is expected browser behavior, not a Tracecat or Caddy error: modern browsers automatically upgrade requests to a public hostname to HTTPS (through HSTS, the “Always use secure connections” setting, or a prior HTTPS visit). Because the default stack exposes only port 80 with no TLS, the HTTPS request is refused. The Caddy logs confirm HTTP-only mode:
server is listening only on the HTTP port, so no automatic HTTPS will be applied to this server
Serving a public deployment over plain HTTP is not supported. Configure inbound HTTPS instead.

Trust an internal CA for outbound connections

Tracecat containers do not inherit the host’s trust store. If Tracecat calls services with certificates signed by an internal CA — a custom LLM provider, an internal Jira, an on-prem API — those connections fail TLS verification even when curl on the host succeeds.

Per-action: CA certificate secret

For HTTP actions, create a workspace secret named ca_cert with the key CA_CERTIFICATE set to your CA certificate as PEM text. core.http picks it up automatically and verifies servers against it without any deployment changes. HTTP actions also accept verify_ssl: false, but only use this as a temporary workaround — it disables certificate verification entirely. See HTTP actions.

Deployment-wide: inject the CA into service trust stores

To make every service trust your CA — including custom LLM providers and Python integrations — append it to the certifi bundle with a one-shot init container.
1

Add your CA certificates

Create a certs/ directory next to your docker-compose.yml and place your CA certificates in it as .crt files in PEM format (not DER). Include the full chain: root and any intermediate CAs.
2

Create a Compose override

Create docker-compose.override.yml next to your docker-compose.yml:
x-custom-ca: &custom-ca
  environment:
    SSL_CERT_FILE: /custom-ca/bundle.crt
    REQUESTS_CA_BUNDLE: /custom-ca/bundle.crt
    CURL_CA_BUNDLE: /custom-ca/bundle.crt
  volumes:
    - custom-ca:/custom-ca:ro
  depends_on:
    cert-init:
      condition: service_completed_successfully

services:
  cert-init:
    image: ghcr.io/tracecathq/tracecat:${TRACECAT__IMAGE_TAG:-1.0.0-beta.50}
    restart: "no"
    user: root
    volumes:
      - ./certs:/certs:ro
      - custom-ca:/custom-ca
    command:
      - python
      - -c
      - |
        import certifi, shutil
        from pathlib import Path
        shutil.copy(certifi.where(), '/custom-ca/bundle.crt')
        with open('/custom-ca/bundle.crt', 'a') as bundle:
            for cert in sorted(Path('/certs').rglob('*.crt')):
                bundle.write(cert.read_text())
                print(f'Appended {cert}')

  api: *custom-ca
  executor: *custom-ca
  agent-executor: *custom-ca
  agent-worker: *custom-ca
  litellm: *custom-ca

volumes:
  custom-ca:
The cert-init service copies the default certifi bundle into a shared volume and appends your CA certificates. Docker Compose merges the override with each service’s existing configuration. Apply the same *custom-ca block to any other service that makes outbound HTTPS calls.
3

Restart the stack

docker compose up -d
Compose picks up docker-compose.override.yml automatically. Check the init container output with docker compose logs cert-init.
The injected bundle does not reach actions running inside the nsjail sandbox (TRACECAT__EXECUTOR_SANDBOX_ENABLED=true). The sandbox strips these environment variables and does not mount /custom-ca. For sandboxed HTTP actions, use the per-action ca_cert secret instead.