Skip to content

Running Archon behind your own reverse proxy

You can use your own reverse proxy if it satisfies the contract below. Caddy is Archon’s single maintained reference proxy: start with Caddyfile.example and the Cloud deployment guide. Other proxies are community examples, not supported Compose profiles.

Forward to Archon’s configured app port (PORT), preserving the request path. Docker defaults to 3000; local development defaults to 3090 (worktrees may allocate another port). See Port configuration. Serve the UI and API at the same public origin.

Keep the upstream private so clients cannot bypass the proxy. For a proxy on the same host, use HOST=127.0.0.1. A container proxy needs a reachable container interface, but the app’s host port must not be exposed publicly. The base Compose file publishes the app port; follow the GitHub App Docker setup to replace that mapping with a loopback-only mapping.

Route groupProxy behavior
/internal/*Never forward, for any HTTP method. Return a proxy-generated 404 or 403.
GET /api/healthForward without interactive authentication for load-balancer and uptime checks. This is the Docker/production health endpoint.
/webhooks/*Forward without interactive authentication. Preserve the raw body and the provider’s signature, secret, event, and delivery headers.
/api/stream/*Forward with response buffering off and timeouts that allow long-lived SSE connections. Apply the same access boundary as other protected API routes.
/api/auth/*Forward login/session requests and cookies to Archon when using web auth; do not put them behind a proxy login that prevents users reaching Archon’s login.
Other /api/*, UI pages, and assetsForward normally, with the authentication posture described below.

/health is also registered by the server and used as a local-development convenience endpoint. Use /api/health for production checks, matching Docker and the Caddy reference.

In GitHub App mode, POST /internal/git-credential hands out live installation tokens. It is registered only when the GitHub adapter is in App mode. The server refuses a non-loopback bind in App mode unless ARCHON_ALLOW_INTERNAL_ON_PUBLIC_BIND=1 explicitly acknowledges it. That flag does not protect the endpoint. Set it only when the topology requires a non-loopback upstream, the upstream is private, and the proxy already denies /internal/*.

The Caddy reference denies /internal/* with a proxy-generated 404 before its catch-all forwards other paths. Keep that denial in your deployed configuration. Follow Internal endpoint security for the Docker port restrictions and external POST probe, too. A GET returning 404 does not prove that the credential endpoint is blocked.

Archon reads webhook bodies as raw text before verification. Do not parse and reserialize JSON, rewrite the body, or strip verification headers. Proxy login pages and redirects must not intercept webhook deliveries; the configured adapter or webhook source verifies them. Preserve the original /webhooks/... path.

Both the dashboard stream (/api/stream/__dashboard__) and conversation streams (/api/stream/:conversationId) send an initial heartbeat and another every 30 seconds. Disable response buffering and caching for these routes. Set any idle/read timeout in the proxy chain above 30 seconds with margin, for example 60 seconds or longer, and avoid a fixed response-duration limit that cuts off a healthy stream.

The Caddy reference uses flush_interval -1 for /api/stream/*. Verify events arrive as they are emitted, rather than appearing in a batch when the connection closes.

With Web UI login enabled (DATABASE_URL and BETTER_AUTH_SECRET), Better Auth handles sessions and Archon’s server-side gate protects /api/*. The gate is on by default; ARCHON_WEB_AUTH_REQUIRED=false disables it and leaves login as a UI feature only. /api/auth/* and /api/health* are public exceptions. Static UI files can be served without proxy authentication; the API gate is the access boundary.

The proxy does not need its own login when that gate is enabled, but it must strip client-supplied X-Archon-User (or the name configured by ARCHON_WEB_AUTH_HEADER). Archon accepts this trusted identity header as a fallback even with Better Auth enabled; allowing clients to supply it bypasses the API gate. If you deliberately use proxy authentication and identity attribution, strip incoming values and set the header only from the authenticated identity. Keep the upstream reachable solely through that trusted boundary.

Without web auth, or with its API gate disabled, the proxy must provide the access boundary for a network-accessible UI and API. Use the authentication options in the Cloud guide, or your proxy’s equivalent, while keeping health checks and webhooks reachable. A reverse proxy alone does not add authentication.

Archon’s application code does not read X-Forwarded-* headers as an access or identity contract. For Better Auth behind a fixed-origin proxy, set BETTER_AUTH_URL=https://archon.example.com to the public origin instead of relying on the upstream HTTP URL. Preserve session cookies. BETTER_AUTH_TRUSTED_ORIGINS is for additional allowed origins, not a replacement for the public URL. See the configuration reference for signup policy and auth settings.

This community example is not a supported profile. It assumes Traefik v3 with the Docker provider, a websecure HTTPS entry point and TLS certificates already configured, and a shared Docker network named archon-network. It uses Archon’s web auth with the API gate enabled, BETTER_AUTH_URL set to the public origin, and the default identity header. Add these labels to your app service; adjust the host, network, and port to your deployment.

labels:
- "traefik.enable=true"
- "traefik.docker.network=archon-network"
- "traefik.http.routers.archon.rule=Host(`archon.example.com`) && !PathPrefix(`/internal`)"
- "traefik.http.routers.archon.entrypoints=websecure"
- "traefik.http.routers.archon.tls=true"
- "traefik.http.routers.archon.service=archon"
- "traefik.http.routers.archon.middlewares=archon-strip-identity"
- "traefik.http.middlewares.archon-strip-identity.headers.customrequestheaders.X-Archon-User="
- "traefik.http.services.archon.loadbalancer.server.port=3000"
- "traefik.http.services.archon.loadbalancer.healthcheck.path=/api/health"
- "traefik.http.services.archon.loadbalancer.responseforwarding.flushinterval=-1ms"

The router rule excludes the entire /internal prefix. Ensure no other router or fallback forwards it to Archon. The headers middleware removes the identity header, and the service flush setting flushes responses immediately. Do not add a buffering middleware to the stream routes. Keep the HTTPS entry point’s transport.respondingTimeouts.writeTimeout at 0s (no response-duration limit) for long-lived streams, and check any outer load balancer’s idle timeout as well.

Keep the app port private as described above. For GitHub App mode, complete the internal-endpoint setup and POST probe before exposing the deployment. Verify /api/health, a real webhook delivery, an unauthenticated API request (401 with the gate enabled, including when a forged identity header is sent), and a live stream through the public proxy.