Skip to content

Reverse proxy

Conatus listens on plain HTTP inside its container. To reach it from a domain you terminate TLS at a proxy and forward to the app’s port.

This is the step people miss. Conatus does not guess its own public address. Sign-in callbacks and generated links both come from configuration:

AUTH_URL=https://tasks.example.com
PUBLIC_BASE_URL=https://tasks.example.com

Both must be the external HTTPS origin, with no trailing slash. If you set only one, you get a working page whose login redirects land on the wrong host.

Restart after changing them:

docker compose up -d

Once a proxy is in front, the app should not also be reachable directly. By default, Compose publishes it on 0.0.0.0:

CONATUS_BIND_ADDRESS=127.0.0.1
CONATUS_PORT=4399

If the proxy runs in Docker on the same network, drop the published port entirely and let the proxy reach app:3000 over the Compose network instead.

Caddy gets you certificates with no extra configuration.

tasks.example.com {
reverse_proxy 127.0.0.1:4399
}

If Caddy runs as a Compose service on the same network:

tasks.example.com {
reverse_proxy app:3000
}
server {
listen 443 ssl http2;
server_name tasks.example.com;
ssl_certificate /etc/letsencrypt/live/tasks.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/tasks.example.com/privkey.pem;
# Attachments are uploaded through the app.
client_max_body_size 50m;
location / {
proxy_pass http://127.0.0.1:4399;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
server {
listen 80;
server_name tasks.example.com;
return 301 https://$host$request_uri;
}

client_max_body_size matters: nginx’s 1 MB default rejects most file attachments with a 413 before the app ever sees them.

If you run the optional MCP sidecar, give it its own hostname rather than a path on the app’s. A typical pair:

  • https://tasks.example.com → the web app and /api/v1
  • https://mcp.example.com/mcp → the MCP sidecar on port 3001

MCP_PUBLIC_URL must be that exact external URL, ending in /mcp. See MCP server.

curl -I https://tasks.example.com

Then sign out and sign in again through the domain. If you land back on localhost after login, AUTH_URL is still wrong.