Domains, HTTPS, and OAuth
The official application image serves both the web UI and /api from a single process, so the reverse proxy forwards the whole request to one app. No separate frontend API address is needed, but domain and HTTPS deployments must still configure the browser-origin allowlist below.
When using a domain or HTTPS reverse proxy, set ALLOWED_ORIGINS=https://chat.example.com. Without it, cookie-authenticated requests may return cross-site request blocked. Same-origin IP/HTTP testing may leave it unset.
Three Ways to Access
| Method | When to use | Security boundary |
|---|---|---|
| Server IP + HTTP | Short initial acceptance testing | Only on controlled networks; should not carry real accounts or OAuth |
| Local reverse proxy | Running Caddy, Nginx, or Traefik on one server | The application listens only on the loopback address; the proxy owns 80/443 and terminates TLS |
| Cloud load balancer/CDN | The cloud platform already provides an HTTPS entry point | The load balancer terminates TLS, and the backend is only exposed to the load balancer or the private network |
In every case, never expose PostgreSQL, Redis, Qdrant, or sandbox through the proxy. They should stay only on the Docker internal network or a private network. If you front Aivory with Cloudflare (proxied DNS or a Tunnel), see the Cloudflare Proxy guide for SSE streaming, upload-size, and origin-TLS specifics.
Same-Host Reverse Proxy
The default Compose maps the app as 80:8787. If Caddy, Nginx, or Traefik also runs on the same host, first change app.ports in the Compose file you use so it listens only on localhost:
ports:
- "127.0.0.1:8787:8787"
After recreating the app, the proxy can connect to 127.0.0.1:8787. With Caddy as an example, the site configuration can stay very simple:
aivory.example.com {
reverse_proxy 127.0.0.1:8787
}
Point DNS at the server, allow only ports 80 and 443 through the firewall, then verify:
curl -fsS http://127.0.0.1:8787/api/health
curl -fsS https://aivory.example.com/api/health
Once the certificate has been issued, the second command should return {"ok":true}. The reverse proxy must forward a proper Host header; mainstream proxies do this by default.
Allowed Browser Origins
For every domain or HTTPS reverse-proxy deployment, fill in the exact browser origin as scheme://host[:port]:
ALLOWED_ORIGINS=https://chat.example.com,https://admin.example.com:8443
Include the scheme and non-default port, but no path or trailing slash. Do not use broad wildcards. Restart app after changing the value.
OAuth Callbacks: Single Domain
When creating a GitHub, Google, Apple, OAuth2, or OIDC identity provider in the admin console, first make the public HTTPS domain available. Copy the callback URL shown in the console verbatim into the provider's console; the scheme, domain, port, and path must all match exactly.
For a single-domain deployment, you usually do not set OAUTH_CALLBACK_BASE_URL. The application constructs the callback URL from the public address the user initiated login on. The identity provider's Client ID, Client Secret, and callback URL must come from the same provider application configuration.
OAuth Callbacks: Multiple Domains
Some providers allow only one callback URL. If Aivory is accessed through multiple domains at the same time, pick one fixed HTTPS primary domain as the OAuth callback domain and set in the environment:
OAUTH_CALLBACK_BASE_URL=https://aivory.example.com
OAUTH_RETURN_ORIGINS=https://aivory.example.com,https://team.example.com
OAUTH_CALLBACK_BASE_URL has no trailing / and determines the fixed callback origin sent to the provider. OAUTH_RETURN_ORIGINS is the exact list of origins allowed to return after login completes, and is the security boundary against open redirects; do not put wildcards, uncontrolled preview domains, or URLs with paths in it. Each provider console should register only the callback URL for the canonical domain.
After changing these variables, restart app and complete one login test through each allowed domain.
GitHub Shows token_exchange_failed
If the account can already be linked but login fails, or the logs show context deadline exceeded or github exchange failed, this usually means the application server cannot reach GitHub's token endpoint within the time limit — not that the user's GitHub identity is broken. Check in order:
- Whether the server's DNS, IPv4/IPv6 routing, egress firewall, and proxy allow HTTPS requests to
github.com:443. - Whether the callback URL in the provider console matches the public HTTPS domain exactly.
- Whether
OAUTH_CALLBACK_BASE_URL, the Client ID, and the Client Secret belong to the same GitHub OAuth App. - Whether the server time is correct; significant clock skew makes OAuth state, cookies, or signature validation fail.
- If an enterprise proxy is required, set a standard HTTP(S) proxy in the application's runtime environment and confirm it is not intercepting GitHub's token requests.
Do not send OAuth Client Secrets, one-time callback parameters, session tokens, or the full .env file to issues, chat logs, or browser consoles.