DOCS · DEPLOYMENT
Deploy Cortex on a company subdomain
This guide deploys one Cortex instance for a team behind a company hostname such as cortex.company.com. It is self-contained: you do not need to install Warden, Trestle or Watchpost to use it. Cortex, Warden, Trestle and Watchpost can be deployed together on sibling subdomains, and every project also works independently. Sharing a parent domain creates no implicit trust or shared authentication — credentials, cookies, databases, service accounts and authorization remain separate per application. Use one distinct hostname per application (for example cortex.company.com rather than company.com/cortex), because independent subdomains simplify proxying, cookie scope, origins, upgrades and operational isolation.
cortex.company.com is the coding agent, warden.company.com the browser IDE and agent, trestle.company.com the backend platform, and watchpost.company.com the monitoring control plane. Deploy only the projects you need.Recommended topology
Internet or company network
|
v
DNS hostname (cortex.company.com)
|
v
Caddy or nginx with HTTPS (TLS termination, hostname routing)
|
v
127.0.0.1:7331
|
v
systemd user service (cortex.service)
Cortex listens on loopback by default. The reverse proxy owns the externally reachable hostname and TLS termination; Cortex itself stays bound to 127.0.0.1 unless your documented security model requires otherwise.
1. Choose the hostname
Pick a stable internal or public name. cortex.company.com is used throughout this guide.
2. Create the DNS record
With a fixed public IP, create an A record (and an AAAA record with the IPv6 address where applicable):
Type: A
Name: cortex
Value: 203.0.113.10
When the hostname should point at another hostname instead, use a CNAME:
Type: CNAME
Name: cortex
Value: apps.company.com
DNS only maps the hostname. It does not select the application port, provide HTTPS, or secure the service — Caddy or nginx performs hostname routing and TLS termination. Private deployments do not require public DNS: split-horizon DNS, internal DNS and VPN-only hostnames are valid and often preferable.
3. Install the Cortex binary
Place the binary at a stable absolute path. A Downloads directory is disposable and will break the unit; install deliberately, for example:
mkdir -p /opt/cortex
install -m 0755 cortex /opt/cortex/cortex
or use the official installer, which installs to ~/.local/bin by default:
curl -fsSL https://cortex-go.github.io/install.sh | sh
4. Install the service unit
Keep the listener on loopback, pin the external origin, and trust only the direct loopback reverse proxy:
cortex service install \
--host 127.0.0.1 --port 7331 \
--root /srv/cortex-workspaces \
--data /var/lib/cortex \
--public-origin https://cortex.company.com \
--trust-proxy
--trust-proxy accepts forwarding headers only from Cortex's direct loopback peer (the proxy on the same host). --public-origin pins the canonical external origin for Host and same-origin checks. Do not enable proxy trust when clients can connect directly to the Cortex HTTP port. The unit records an absolute executable path; moving or deleting the binary breaks the service until you reinstall.
5. Keep the listener on loopback
The --host 127.0.0.1 --port 7331 flags keep Cortex private. Only the reverse proxy, which runs on the same host, reaches it. Browser authentication remains mandatory even behind the proxy: after first-run password setup, every request requires a session cookie, with optional TOTP and Google sign-in.
6. External origin and trusted proxy
These are already set by the flags above. --public-origin makes cookies and same-origin checks use https://cortex.company.com, and --trust-proxy makes the backend honour forwarding headers only from 127.0.0.1. The proxy must replace (not blindly forward) browser-supplied X-Forwarded-* headers.
7. Configure Caddy
cortex.company.com {
reverse_proxy 127.0.0.1:7331
}
Add this when the backend must see the forwarded peer and scheme:
cortex.company.com {
reverse_proxy 127.0.0.1:7331 {
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
}
request_body {
max_size 100MB
}
}
Coding-agent responses stream as Server-Sent Events, so do not buffer them; Caddy proxies streaming responses natively. Caddy's automatic HTTPS requires working public DNS and a reachable ACME challenge path. For private networks, use an internal certificate or your company PKI instead of presenting a development certificate as publicly trusted.
8. Configure nginx as an alternative
server {
listen 80;
server_name cortex.company.com;
return 301 https://cortex.company.com$request_uri;
}
server {
listen 443 ssl http2;
server_name cortex.company.com;
ssl_certificate /etc/letsencrypt/live/cortex.company.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/cortex.company.com/privkey.pem;
client_max_body_size 100m;
location / {
proxy_pass http://127.0.0.1:7331;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
}
Obtain the certificate with Certbot or your company PKI before starting nginx. The proxy deliberately replaces X-Forwarded-* from $remote_addr rather than trusting a client-supplied header. proxy_buffering off keeps the streaming agent output flowing, and the long read timeout accommodates long-running agent sessions. The request body limit covers attached images (Cortex caps each image at 10 MiB and six per run).
9. Obtain and verify HTTPS
curl -I https://cortex.company.com
Confirm the certificate is issued for the hostname and the TLS handshake completes.
10. Verify the public health endpoint
curl -s https://cortex.company.com/api/health
Expect {"ok":true}. /api/health is the public, read-only liveness endpoint; the richer /api/status stays behind browser authentication.
11. Open the application and complete first-run setup
Open https://cortex.company.com, set a password (and optionally TOTP or Google sign-in), then configure a provider and model in Settings. For ChatGPT Plus/Pro or GitHub Copilot, authenticate OpenCode once on the Cortex host (opencode auth login --provider openai or github-copilot) so the service can copy the credential into isolated sessions.
12. Configure lingering for unattended boot
loginctl show-user "$USER" -p Linger
loginctl enable-linger "$USER"
Cortex never enables lingering automatically. Enable it only when the service must run after logout or start before login; it changes what the host runs without a login session.
13. Verify service status and logs
cortex service status
cortex service logs
status reports enabled/running state, PID, version, listen address and a live health check, and exits nonzero when the service is failed or missing.
14. Troubleshoot
- DNS — the hostname resolves but nothing loads: confirm the A/AAAA or CNAME points at the proxy host and that internal DNS (not public DNS) is the right place for VPN-only names.
- TLS — certificate warnings: automatic HTTPS needs public DNS and reachable ACME paths; internal deployments need a company PKI/internal CA.
- Proxy — reverse proxy connects but gets 400/425 or a cookie failure: the proxy must replace forwarding headers, and
--trust-proxymust be enabled only when the proxy is the direct loopback peer. - Streaming — agent output appears only at the end: disable proxy buffering (nginx
proxy_buffering off) so Server-Sent Events stream. - Cookies — login works on
127.0.0.1but not through the proxy: the cookie domain and Secure flag depend on--public-originmatching the hostname and TLS being present.
HOME and GH_CONFIG_DIR when a hosts.yml exists so gh keeps working; the token stays in your keyring and is never copied into session data.