Skip to main content
Omniflow does not require, bundle, or automatically start a reverse proxy. Operators may choose Caddy, Traefik, Nginx, HAProxy, or a managed ingress. The proxy must provide TLS, preserve request IDs, enforce body limits, and route API, web, Telegram webhook, and provider webhook traffic correctly. Customer and admin routes are served by the same web container. Configure trusted proxy headers explicitly; never trust arbitrary X-Forwarded-For input.

Maintained examples

Both are kept working and both are validated in CI. The Traefik example runs as a compose overlay:
The overlay also stops publishing the API and web ports on the host, since the proxy fronts them.

Hosts

BOT_WEBHOOK_DOMAIN may be the same host as the API. Keeping it separate is tidier but not required.

/v1 must be served on the web host

Both panels call the API same-origin. The shared browser client keeps a base URL of "" (packages/api-client/src/fetcher.ts) and nothing calls setBaseUrl, so a request from the panel goes to the web host and expects the API to answer it. Both examples therefore route /v1 on WEB_DOMAIN to the API container, with the same body limit and the same block on the operational endpoints as the public API host. This is not a convenience. It is what keeps the session cookie first-party, which is what lets it stay SameSite=Lax and HttpOnly with no CORS anywhere in the stack. Serving the panel API cross-origin instead would require SameSite=None, a Secure cookie sent on cross-site requests, and an allow-list of origins with credentials — three moving parts protecting an operator session, in place of one.
A proxy that serves WEB_DOMAIN without routing /v1 produces a panel that looks entirely healthy and cannot do anything. Pages render, an anonymous visitor is still redirected to sign in, and the sign-in form reports “Sign-in failed. Try again.” — because the request reached Next.js, which has no /v1 route, rather than the API. If you write your own proxy configuration, this is the rule to carry across.
API_DOMAIN is still worth publishing: provider webhooks, the /v1/admin bearer surface, and the Telegram webhook are called by systems that are not the browser. A single-host installation that serves only WEB_DOMAIN works for both panels, but has nowhere to point a payment provider.

Do not publish the operational endpoints

/metrics and /readyz describe installation internals: dependency health, queue depth, payment classifications. Both examples return 404 for them on the public API host. Scrape them over the internal network instead, or put them behind your own authentication. /livez is safe to expose — it reports only that the process is running.

Body limits

Do not rewrite request bodies

Provider webhook signatures are computed over the exact bytes the provider sent. A proxy that re-encodes, pretty-prints, or normalises a JSON body invalidates every signature, and Omniflow will correctly reject the result as unverified. If webhooks start failing verification after a proxy change, this is the first thing to check.

Access logs

An access log records request paths, and a subscription link is a request path. The Traefik example turns access logging off by default for that reason. If you need it, apply a path filter you have reviewed.