Skip to main content

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.

Required for domain deployments

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

MethodWhen to useSecurity boundary
Server IP + HTTPShort initial acceptance testingOnly on controlled networks; should not carry real accounts or OAuth
Local reverse proxyRunning Caddy, Nginx, or Traefik on one serverThe application listens only on the loopback address; the proxy owns 80/443 and terminates TLS
Cloud load balancer/CDNThe cloud platform already provides an HTTPS entry pointThe 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:

  1. Whether the server's DNS, IPv4/IPv6 routing, egress firewall, and proxy allow HTTPS requests to github.com:443.
  2. Whether the callback URL in the provider console matches the public HTTPS domain exactly.
  3. Whether OAUTH_CALLBACK_BASE_URL, the Client ID, and the Client Secret belong to the same GitHub OAuth App.
  4. Whether the server time is correct; significant clock skew makes OAuth state, cookies, or signature validation fail.
  5. 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.