Environment variables
Environment variables, defaults, accepted values, and runtime limits.
The starter template is .env.example. Empty environment values are removed at process start. Docker -e FOO= therefore uses the unset default. Values below are runtime defaults; the template can supply a different starter value.
JWT_SECRET is required on a fresh data dir. DurinDoor does not mint DATA_DIR/jwt-secret. An existing file from an older install is reused with a warning. Generate a value with openssl rand -hex 32.
Tables list each variable's default and effect. For example, FIRECRAWL_TIMEOUT_MS defaults to 15000, while .env.example supplies 30000.
Production secrets
| Variable | Default | Purpose |
|---|---|---|
JWT_SECRET | none | Signs dashboard session tokens. Env wins. Legacy DATA_DIR/jwt-secret is accepted with a warning. Missing both throws. Documented example values such as replace-me are ignored and count as unset. |
INITIAL_PASSWORD | 123456 | Dashboard password when none is stored. Remote login with the built-in default, or with a documented example such as change-me or replace-me, is refused. |
API_KEY_SECRET | minted at DATA_DIR/api-key-secret when DATA_DIR is set | Secret that signs API key checksums. Set it when minted keys must survive recreate. |
MACHINE_ID_SALT | endpoint-proxy-salt | Salt for the machine id embedded in generated API keys. |
AUTH_COOKIE_SECURE | false | true forces secure dashboard cookies. Otherwise https on BASE_URL / NEXT_PUBLIC_BASE_URL or the request URL decides. |
Native default DATA_DIR remains ~/.9router on macOS/Linux and %APPDATA%\9router on Windows.
Listen address and URLs
| Variable | Default | Purpose |
|---|---|---|
PORT | 20128 | Listen port for the gateway and dashboard. |
HOSTNAME | 127.0.0.1 from custom-server.js when unset | Bind address. CLI default bind is 0.0.0.0. Docker Compose sets 0.0.0.0 inside the container. |
HOST | 127.0.0.1 | Host used when deriving the provider-plugin manifest URL. |
DASHBOARD_PORT | falls back to PORT then 20128 | Alternate port for the manifest URL. |
API_PORT | falls back to PORT then 20128 | Alternate port for the manifest URL. |
NODE_ENV | unset (treated as not production / not development unless set) | production for production starts. Gates shutdown, intercept-path overrides, and similar. |
NINEROUTER_MAX_OLD_SPACE_SIZE | 6144 MB | CLI child heap cap. Positive integer MB, or 0 to leave sizing to Node. An existing --max-old-space-size in NODE_OPTIONS suppresses the default. |
BASE_URL | unset | Server-side origin for callbacks, secure-cookie inference, and the sidecar manifest URL. |
NEXT_PUBLIC_BASE_URL | unset | Browser-visible origin. Same fallback chain as BASE_URL for cookies and the manifest. Also the OAuth redirect_uri for a non-loopback install, so the consent flow lands on the public origin instead of window.location.origin behind a proxy that rewrites Host. |
CLOUD_URL | unset | Optional server-side origin for a separately configured remote endpoint. No hosted default. |
NEXT_PUBLIC_CLOUD_URL | unset | Browser-visible remote endpoint used by selected CLI-tool helpers. |
TRUST_PROXY | unset (false) | true plus the custom-server peer token lets login rate limiting read X-Forwarded-For. |
SHUTDOWN_SECRET | unset | Bearer secret for /api/shutdown. Production always 403s that route. |
next.config.mjs rewrites /v1/:path* to /api/v1/:path*.
Data directory and postgres
| Variable | Default | Purpose |
|---|---|---|
DATA_DIR | ~/.9router or %APPDATA%\9router | Persistent root for SQLite, auth secrets, logs, intercept files, tunnels, Headroom venv, and backups. Created with permissions 0700 when set. Unix paths on Windows are ignored. |
DURINDOOR_PG_URL | unset | A nonempty libpq URL selects explicit PostgreSQL startup and wins over the dashboard secret. Packaged startup treats an exact empty value as unset. |
DURINDOOR_DATABASE_ENGINE | unset | Set to postgres for PostgreSQL-only startup, which fails closed even if the URL is missing or empty. Without either selector, the saved engine controls legacy cutover behavior. |
DURINDOOR_PG_SSLMODE | unset (prefer in Compose) | Appended to the URL when it has no sslmode= already. |
DURINDOOR_PG_PREFERRED_VERSION | 18 in Compose | Operator cap on the target major version (16, 17, 18). Compose only. |
Rate-limit backoff
Positive decimal integers only. A resolved maximum below the base resets the whole schedule to defaults. Maximum delay is capped at 7 days.
| Variable | Default | Purpose |
|---|---|---|
BACKOFF_BASE_MS | 2000 | Initial account/model lock delay after rate-limit fallback. |
BACKOFF_MAX_MS | 300000 | Maximum exponential lock delay. |
BACKOFF_MAX_LEVEL | 15 | Maximum exponential backoff level. |
Logs and request details
ENABLE_REQUEST_LOGS writes metadata-only files under logs/ in the routing core. It never enables or vetoes database persistence. OBSERVABILITY_ENABLED overrides the dashboard enableObservability setting when set to true or false; empty defers to the dashboard.
| Variable | Default | Purpose |
|---|---|---|
ENABLE_REQUEST_LOGS | false (must be the string true) | Diagnostic request files under cwd/logs. No payloads, stream chunks, stacks, or URL query/fragment. |
OBSERVABILITY_ENABLED | unset | true enables Usage → Details persistence; false vetoes it. |
OBSERVABILITY_MAX_RECORDS | 200 | Cap on retained request-detail rows when settings omit it. |
OBSERVABILITY_BATCH_SIZE | 20 | Flush batch size. |
OBSERVABILITY_FLUSH_INTERVAL_MS | 5000 | Flush interval. |
LOG_LEVEL | INFO | DEBUG, INFO, WARN, ERROR. |
LOG_USAGE_VERBOSE | unset | 1 prints extra usage-tracking debug. |
STRIP_REASONING_CONTENT | unset | 1 / true / on / yes strips non-streaming reasoning_content when the message also has content. Header x-9router-reasoning: off does the same per request. |
DURINDOOR_REASONING_MIN_BUDGET | unset (disabled) | Opt-in floor for thinking-model output budgets: a caller max_tokens in [256, floor) on a thinking-capable model is raised to the floor (clamped to the model's known output cap) so reasoning tokens cannot consume the whole budget. Unset = the existing buffer heuristic runs unchanged. |
X-DurinDoor-Effort: auto (header, not env) | n/a | Adaptive thinking effort: when a request carries no reasoning field of any shape (reasoning_effort, reasoning, thinking), the gateway resolves auto to low/medium/high from deterministic request-shape signals scoped to the current turn (last-user-message length, context size, prior tool results, tool-loop depth). Scoped to requests dispatched in the OpenAI Chat Completions shape; a no-op elsewhere. |
Treat request-log metadata as sensitive operational data. Keep opt-in diagnostics off unless needed.
Outbound proxy
Dashboard proxy settings write NINE_ROUTER_PROXY_MANAGED plus the matching URL and no-proxy vars. Process-level HTTP_PROXY / HTTPS_PROXY / ALL_PROXY / NO_PROXY (and lowercase twins) are also read. Loopback targets (localhost, *.localhost, 0.0.0.0, 127.0.0.0/8, ::, ::1, and IPv4-mapped loopback) never use an outbound proxy.
| Variable | Default | Purpose |
|---|---|---|
HTTP_PROXY / http_proxy | unset | Proxy URL for http upstreams. |
HTTPS_PROXY / https_proxy | unset | Proxy URL for https upstreams. |
ALL_PROXY / all_proxy | unset | Fallback proxy for all protocols. |
NO_PROXY / no_proxy | unset | Comma-separated hosts that bypass the proxy. |
NINE_ROUTER_PROXY_URL | unset | Managed proxy URL written by dashboard settings. |
NINE_ROUTER_NO_PROXY | unset | Managed no-proxy list. |
NINE_ROUTER_PROXY_MANAGED | unset | 1 when dashboard owns the proxy env. Disabled settings clear only managed vars. |
OUTBOUND_SSRF_GUARD_ENABLED | unset (guard on) | false / 0 / no / off allows private provider URLs (legacy opt-out). |
OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS | unset | 1 / true / yes / on allows every private URL. |
OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS | local allowed | Blank defaults to allowing LAN/localhost probes. Metadata endpoints stay blocked while a guard mode is active. |
PROXY_CONNECT_TIMEOUT_MS | 90000 | Connect timeout for proxied fetches. |
PROXY_HEADERS_TIMEOUT_MS | 300000 | Headers timeout for proxied fetches. |
Prefer dashboard proxy settings for day-to-day use. Use process-level vars for container egress.
Timeouts and stream bounds
Most timeout variables parse an integer greater than zero. Invalid or nonpositive values use the default. SSE_KEEPALIVE_MS and STREAM_EARLY_EOF_PEEK_MS also accept 0 to turn the feature off.
| Variable | Default | Purpose |
|---|---|---|
GITHUB_CREDIT_USAGE_CACHE_TTL_MS | 30000 | Reuse a GitHub Copilot credit reading for this duration before checking the local credit cutoff again. |
SSE_KEEPALIVE_MS | 10000 | Claude SSE ping interval during upstream silence. 0 disables. |
STREAM_STALL_TIMEOUT_MS | 360000 | Inter-chunk stall timeout once tokens flow. |
STREAM_FIRST_CHUNK_TIMEOUT_MS | 200000 | Time-to-first-token timeout. |
STREAM_EARLY_EOF_PEEK_MS | 10000 | How long a direct stream is held for its first content frame. A stream that ends or errors empty inside it retries once on a sibling connection. 0 disables. |
OLLAMA_LOCAL_CONNECT_TIMEOUT_MS | 120000 | Connect timeout for Ollama Local. Raise it for slow 70B loads. |
COMBO_MODEL_TIMEOUT_MS | 0 (off) | Per-member combo timeout. 0 uses fetch connect timeout. |
FETCH_CONNECT_TIMEOUT_MS | 60000 | Abort if upstream headers never arrive. |
PROVIDER_BODY_TIMEOUT_MS | 120000 | Bound for a successful non-streaming provider body. |
RESPONSE_BODY_TIMEOUT_MS | 120000 | Bound for forced stream-to-json conversion. |
MAX_PROVIDER_BODY_BYTES | 8388608 | Max provider body bytes. |
MAX_RESPONSES_OUTPUT_ITEMS | 1024 | Cap on Responses output items. |
CODEX_SSE_PEEK_TIMEOUT_MS | 30000 (capped at 5 min) | Bound for the Codex SSE peek. |
GEMINI_NATIVE_TTS_FETCH_TIMEOUT_MS | 45000 | Gemini native TTS header timeout. |
CONCURRENCY_GATE_TIMEOUT_MS | 120000 | Wait for a provider concurrency slot. |
TRAE_STREAM_TIMEOUT_MS | 300000 | Trae stream timeout. |
VIDEO_FETCH_TIMEOUT_MS | 120000 | Video fetch timeout. |
CODEX_REFRESH_SPACING_MS | 2000 | Gap between sibling OAuth refreshes. 0 opts out. |
BG_REFRESH_DELAY_MS | 1500 | Background token refresh delay (normal). |
BG_REFRESH_GOOGLE_DELAY_MS | 12000 | Background refresh delay for Google-family tokens. |
DISABLE_BACKGROUND_TOKEN_REFRESH | unset | Truthy disables background refresh. |
ONBOARD_MAX_ATTEMPTS | 2 | Gemini/Antigravity project-id onboard attempts. |
ONBOARD_RETRY_DELAY_MS | 12000 | Delay between onboard attempts. |
EAGER_PROJECT_ID_REFRESH | unset | true refreshes Google project ids eagerly. |
Web fetch and search
| Variable | Default | Purpose |
|---|---|---|
FIRECRAWL_BASE_URL | https://api.firecrawl.dev (hosted); http://127.0.0.1:3002 for firecrawl_custom if env is unset | Firecrawl-compatible base for /v1/web/fetch. |
FIRECRAWL_API_KEY | unset | Firecrawl key. Leave empty for a self-hosted instance that does not need one. |
FIRECRAWL_TIMEOUT_MS | 15000 | Request timeout. .env.example writes 30000. |
FIRECRAWL_DEFAULT_FORMAT | markdown | Default extraction format. |
SEARXNG_URL | http://localhost:8888/search | Built-in unauthenticated SearXNG web-search endpoint. |
CLINE_LIVE_CATALOG | unset (off) | true fetches Cline's live OAuth model catalog. Default stays the registry. |
OAuth client overrides
Most OAuth providers use built-in client metadata. Set these only when you run your own OAuth app.
| Variable | Default | Purpose |
|---|---|---|
MIMO_LOGIN_PROXY | unset | HTTP proxy for non-CN Xiaomi MiMo browser login. Without it, login probes local HTTP proxies; CN login stays direct. |
GEMINI_OAUTH_CLIENT_ID | empty | Gemini CLI OAuth client id. |
GEMINI_OAUTH_CLIENT_SECRET | empty | Gemini CLI OAuth client secret. |
GEMINI_CLIENT_ID | unset | Alternate Gemini client id used by model-list helpers. |
GEMINI_CLIENT_SECRET | unset | Alternate Gemini client secret for those helpers. |
ANTIGRAVITY_OAUTH_CLIENT_ID | empty | Antigravity OAuth client id. |
ANTIGRAVITY_OAUTH_CLIENT_SECRET | empty | Antigravity OAuth client secret. |
AGY_CLIENT_ID | unset | Antigravity client id used by model-list helpers. |
AGY_CLIENT_SECRET | unset | Antigravity client secret for those helpers. |
CLAUDE_CODE_CLIENT_VERSION | 2.1.282 | Claude Code version sent in the Claude User-Agent and billing header. Ignored unless it is a short header-safe token ([A-Za-z0-9][A-Za-z0-9._-]{0,31}). Read at startup. |
KIMI_CODING_OAUTH_CLIENT_ID | registry clientId | Kimi Coding device-code client id. |
GHE_COPILOT_OAUTH_CLIENT_ID | registry clientId | GitHub Enterprise Copilot device-code client id, for a GHES instance with its own OAuth app. |
GITLAB_BASE_URL | https://gitlab.com | GitLab origin. |
GITLAB_DUO_BASE_URL | https://gitlab.com | GitLab Duo origin (preferred over GITLAB_BASE_URL). |
GITLAB_OAUTH_CLIENT_ID | registry | GitLab OAuth client id. |
GITLAB_OAUTH_CLIENT_SECRET | registry | GitLab OAuth client secret. |
GITLAB_DUO_OAUTH_CLIENT_ID | registry | GitLab Duo OAuth client id. |
GITLAB_DUO_OAUTH_CLIENT_SECRET | registry | GitLab Duo OAuth client secret. |
MCP gateway, tunnels, Headroom
MCP OAuth needs a public https URL. If the dashboard is reached through a tunnel, set that origin here.
| Variable | Default | Purpose |
|---|---|---|
MCP_GATEWAY_OAUTH_PUBLIC_URL | unset | Public origin for MCP Gateway OAuth callbacks. Wins, then tunnel status, then the request. |
OAUTH_PUBLIC_BASE_URL | unset | Older public OAuth origin fallback. |
TUNNEL_WORKER_URL | https://abc-tunnel.us | Cloudflare tunnel worker endpoint. |
TUNNEL_TRANSPORT_PROTOCOL | http2 | Quick tunnel protocol: http2, quic, auto. |
CLOUDFLARED_PROTOCOL | http2 | Alternate name for the same protocol. |
HEADROOM_URL | http://localhost:8787 | External Headroom proxy URL. |
HEADROOM_API_KEY | unset | Optional key forwarded to the Headroom proxy route. |
Local intercept helper
The intercept helper is optional and local. Several of these are set by the manager for the child process. Do not set the nonce/gate vars by hand.
| Variable | Default | Purpose |
|---|---|---|
MITM_ROUTER_BASE | http://localhost:20128 | Router base used by the intercept helper. |
MITM_SERVER_PATH | auto-detected src/mitm/server.js | Override the intercept server entrypoint. Ignored in production unless the path is inside the installed tree. |
MITM_LISTEN_PORT | 8443 | Intercept listen port. Must be 1025-65535 (Windows allows 1-65535). |
DEBUG_MITM | unset | Truthy enables verbose intercept handler logs. |
ROUTER_API_KEY | unset | Optional key the intercept helper sends to the local router. |
MITM_GLOBAL_STATE_DIR | unset | Non-production only: shared intercept state dir. |
MITM_CA_PREPARED | set to 1 by the manager | Child marker that the CA is ready. |
MITM_INSTANCE_NONCE | set by the manager | Launch-gate nonce. |
MITM_LAUNCH_GATE_FILE | set by the manager | Launch-gate file path. |
MITM_MANAGER_PID | set by the manager | Parent pid for the launch gate. |
ChatGPT Web
See ChatGPT Web for the transports these tune.
| Variable | Default | Purpose |
|---|---|---|
DURINDOOR_BROWSER_POOL | on | off, 0 or false disables the shared browser pool; ChatGPT Web auto then uses HTTP. |
DURINDOOR_BROWSER_AUTO_INSTALL | off | 1 runs playwright install chromium the first time no browser is found. |
DURINDOOR_CHATGPT_WEB_HEADLESS | off | 1 runs the ChatGPT Web browser headless instead of headed off-screen. |
CHATGPT_WEB_CHROME_PATH | unset (CHROME_PATH, then standard install paths) | Chrome or Chromium binary for the browser transport. The Docker image sets /usr/bin/chromium-browser. |
OBSCURA_BIN, OBSCURA_CDP_ENDPOINT, OBSCURA_PORT | unset | Optional Obscura engine for headless pool contexts. |
DURINDOOR_CHATGPT_TLS_TIMEOUT_MS | 60000 | HTTP transport request timeout. |
DURINDOOR_CHATGPT_TLS_GRACE_MS | 10000 | Grace period before a hung tls-client call is abandoned. |
DURINDOOR_CHATGPT_STREAM_FIRST_BYTE_TIMEOUT_MS | 30000 | HTTP transport wait for the first streamed byte. |
DURINDOOR_CGPT_WEB_PRO_TIMEOUT_MS | 1200000 | How long the HTTP transport polls for a Pro answer. |
DURINDOOR_CGPT_WEB_PRO_POLL_INTERVAL_MS | 4000 | Pro answer poll interval. |
DURINDOOR_CGPT_WEB_IMAGE_TIMEOUT_MS | 180000 | Wait for an asynchronous generated image. |
DURINDOOR_CGPT_WEB_IMAGE_CACHE_MAX_MB | 10 | In-memory cache for generated images served at /v1/chatgpt-web/image/<id>. |
DURINDOOR_PUBLIC_BASE_URL | unset (then BASE_URL, NEXT_PUBLIC_BASE_URL, request headers, localhost:$PORT) | Public origin used in generated image links. |
DURINDOOR_XVFB | on in Docker | 0 stops the image entrypoint from starting Xvfb. |
Optional routing classifier
| Variable | Default | Purpose |
|---|---|---|
TYPESAFE_API_KEY | unset | Enable the TypeSafe Jev complexity classifier for smart chat combos. Failures fall back to the local heuristic. |
TYPESAFE_API_BASE | https://api.typesafe.ai | Classifier service origin; distinct from the direct System One provider connection. |
Provider debug, sidecars, Azure
| Variable | Default | Purpose |
|---|---|---|
VALIDATE_OUTBOUND | true | false disables outbound payload validation. Keys are still stripped. |
ENABLE_TRANSLATOR | unset (false) | true enables the dashboard translator feature path. |
CLIPROXYAPI_HOST | 127.0.0.1 | CLIProxyAPI sidecar host when settings have no URL. |
CLIPROXYAPI_PORT | 8317 | CLIProxyAPI sidecar port. |
OMNIROUTE_PROVIDER_MANIFEST_URL | derived from BASE_URL or http://127.0.0.1:20128/api/v1/provider-plugin-manifest | Public provider manifest URL advertised to sidecars. |
OMNIROUTE_PUBLIC_PROTOCOL | http | Protocol used when deriving that URL from host and port. |
CURSOR_STREAM_DEBUG | unset | 1 logs Cursor executor stream debug. |
CURSOR_PROTOBUF_DEBUG | unset | 1 logs Cursor protobuf debug. |
CLAUDE_DISABLE_TOOL_NAME_CLOAK | unset | true disables Claude Code tool-name cloaking. |
AZURE_ENDPOINT | https://api.openai.com | Azure OpenAI endpoint when the connection has none. |
AZURE_API_VERSION | 2024-10-01-preview | Azure API version fallback. |
AZURE_DEPLOYMENT | gpt-4 | Azure deployment fallback. |
AZURE_ORGANIZATION | unset | Azure OpenAI-Organization header. |
OPENAI_API_KEY | unset | Azure executor api-key fallback when the connection has none. |
WINDSURF_API_KEY | unset | Devin CLI executor key fallback. |
Provider-level CLIProxyAPI routing lives in the upstreamProxyConfig settings map (enabled, mode = native / cliproxyapi / fallback, cliproxyapiModelMapping). A connection can set providerSpecificData.cliproxyapiMode = "claude-native" to route only that connection.
CLI binaries and local tools
| Variable | Default | Purpose |
|---|---|---|
AUGGIE_BIN / CLI_AUGGIE_BIN | unset | Path to the Auggie binary. |
CLI_DEVIN_BIN | unset | Path to the Devin CLI binary. |
GROK_HOME | ~/.grok | Grok CLI home used by dashboard Grok Build settings. |
TRAY_MODE | unset | 1 marks a tray-launched process (set by the CLI). |
DISPLAY | unset | Linux tray needs an X display. Without it tray init returns null and the server still runs. |
CODESPACES | unset | Codespaces detection for printed URLs. |
GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN | unset | Codespaces forwarded-port domain. |
Build, packaging, updater
| Variable | Default | Purpose |
|---|---|---|
NEXT_DIST_DIR | .next | Next.js output directory. |
NEXT_TRACING_ROOT_MODE | project root | workspace traces from the parent of the project (CLI packaging). |
NINEROUTER_PROXY_CLIENT_MAX_BODY_SIZE | 128mb | Next.js proxy client body-size limit for large LLM requests. |
NEXT_PHASE | set by Next | phase-production-build / phase-export / phase-static skip app bootstrap. |
NEXT_RUNTIME | set by Next | nodejs selects the Node instrumentation file. |
DURINDOOR_BUILD | unset | 1 also skips bootstrap during packaging. |
DURINDOOR_WORKER_NONCE | set by custom-server | 48-hex worker identity header. Do not set by hand. |
NINEROUTER_PEER_TOKEN | minted by custom-server | Proves x-9r-real-ip came from the wrapper. Do not set by hand. |
Updater internals. Packaged updater only; they can change without notice.
| Variable | Default | Purpose |
|---|---|---|
UPDATER_PKG_NAME | 9router | npm package the updater installs. |
UPDATER_PORT | 20129 | Updater progress port. |
UPDATER_APP_PORT | 20128 | App port the updater waits on. |
UPDATER_TAIL_LINES | 8 | Log tail lines. |
UPDATER_RETRIES | 3 | Install retries. |
UPDATER_RETRY_DELAY_MS | 5000 | Delay between retries. |
UPDATER_LINGER_MS | 30000 | Linger after finish. |
UPDATER_WAIT_MIN_MS | 3000 | Minimum wait for the app port. |
UPDATER_WAIT_MAX_MS | 15000 | Maximum wait for the app port. |
UPDATER_WAIT_CHECK_MS | 500 | Port poll interval. |
UPDATER_SCRIPT_PATH | derived | Path to the updater script. |
UPDATER_RELAUNCH | unset | Relaunch switch. |
UPDATER_RELAUNCH_CMD | unset | Relaunch command. |
UPDATER_RELAUNCH_ARGS | unset | Relaunch args. |
Host process
Read for paths and temp dirs. Operators do not normally set these.
| Variable | Default | Purpose |
|---|---|---|
HOME | OS home | Headroom python / detect paths. |
USERPROFILE | Windows home | Headroom python on Windows. |
APPDATA | Windows roaming | Default DATA_DIR parent; updater and intercept paths. |
LOCALAPPDATA | Windows local | Cursor/Auggie/Devin local stores; Headroom detect. |
XDG_CONFIG_HOME | unset | jcode settings path on Linux. |
PATH | OS path | Headroom, Tailscale, CLI-tool binary lookup. |
TMPDIR / TMP / TEMP | OS temp | Intercept helper temp files. |
SystemRoot | C:\Windows | Windows hosts-file path for intercept-host cleanup. |