Server settings
The gateway is configured entirely through environment variables (operational settings) and the config file (routing, plugins, tracing). This page is the reference for the environment variables read at startup.
A few variables relax authentication or expose internals and must never be enabled on a network-reachable deployment. They are marked (security-sensitive) below: MASTER_KEY, ALLOW_UNAUTHENTICATED_PROXY, and ENABLE_PPROF. Set GATEWAY_ENV=production (see Production mode) so the two most dangerous misconfigurations โ an unauthenticated proxy and a * CORS origin โ refuse to start instead of shipping silently.
Quick referenceโ
| Variable | Default | Purpose |
|---|---|---|
GATEWAY_CONFIG | (none) | Path to the JSON or YAML config file (format auto-detected) |
PORT | 8080 | HTTP listen port |
GATEWAY_ENV | (none) | Set to production to turn on startup safety checks โ see Production mode |
MASTER_KEY | (none) | (security-sensitive) Bootstrap/break-glass admin + proxy credential; generate with ferrogw init |
ALLOW_UNAUTHENTICATED_PROXY | false | (security-sensitive) Disables /v1 auth โ local dev only |
CORS_ORIGINS | (none) | Comma-separated allowed origins, matched literally (no wildcard) |
TRUSTED_PROXIES | loopback | Comma-separated CIDRs whose X-Forwarded-For/X-Real-IP are honored for client-IP resolution |
RATE_LIMIT_RPS | 20 | Per-IP requests/second; the middleware is on by default โ 0 disables it |
RATE_LIMIT_BURST | 40 | Per-IP burst capacity |
ENABLE_PPROF | false | (security-sensitive) Exposes /debug/pprof profiling endpoints (admin scope required) |
FERRO_MODEL_CATALOG_URL | GitHub releases | Override the model-catalog source (air-gapped / custom pricing) |
FERRO_MODEL_CATALOG_TIMEOUT | 10s | Bounds the startup catalog fetch; 0 skips the remote fetch entirely |
FERRO_MODEL_DISCOVERY_INTERVAL | (off) | Opt-in live /models refresh interval (Go duration, e.g. 6h) |
FERRO_OLLAMA_MODELS | (none) | Comma-separated Ollama model list narrowing /v1/models |
LOG_LEVEL | info | debug, info, warn, error |
LOG_FORMAT | json | json or text (human-readable for dev) |
Core runtimeโ
GATEWAY_CONFIG- path to the JSON or YAML config file (format auto-detected). When unset the gateway boots from environment-derived defaults (a fallback strategy over every provider whose key is present).PORT- HTTP listen port (default8080).LOG_LEVEL- logging level (debug,info,warn,error; defaultinfo).LOG_FORMAT- log output format; setLOG_FORMAT=textfor human-readable development logs.
Credentials and authโ
MASTER_KEY- (security-sensitive) the bootstrap and break-glass admin credential the gateway accepts for both admin (/admin/*) and proxy (/v1/*) requests. Generate one withferrogw init. It has no key-store row, so unlike a stored key it cannot be revoked or expired without restarting the process โ treat it like a root password. If unset, the gateway starts but logs a warning and admin routes have no valid credential.ALLOW_UNAUTHENTICATED_PROXY- (security-sensitive) when set totrue, the/v1/*proxy and inference routes skip authentication entirely. This is a development-only convenience; the gateway logs a warning at startup when it is set, andGATEWAY_ENV=productionrefuses to start with it enabled.
ADMIN_BOOTSTRAP_KEY, ADMIN_BOOTSTRAP_READ_ONLY_KEY, and ADMIN_BOOTSTRAP_ENABLED were removed in v1.4.0 ; MASTER_KEY is the only bootstrap credential now.
Give each operator their own admin-scoped key via POST /admin/keys (or the dashboard) instead of sharing MASTER_KEY day to day โ a shared key can't be revoked for one person without rotating it for everyone, and credential-change records all name the same key. Reserve MASTER_KEY for bootstrap and break-glass recovery. See Auth.
Production modeโ
GATEWAY_ENV=production (case-insensitive) turns on a fixed set of startup checks, reported together so fixing a deployment is one edit rather than one restart per problem:
| Setting | Outcome under GATEWAY_ENV=production |
|---|---|
ALLOW_UNAUTHENTICATED_PROXY=true | Refused โ startup exits; every /v1/* data-plane endpoint would be unauthenticated |
CORS_ORIGINS containing * | Refused โ matched literally, so it denies every cross-origin request while reading as an allow-all |
RATE_LIMIT_RPS=0 | Warned โ startup continues; a normal choice when limits are enforced by an ingress or upstream API gateway |
ENABLE_PPROF=true | Warned โ profiling routes stay behind the admin scope; the risk is leaving them mounted past an incident |
In-memory API_KEY_STORE_BACKEND | Warned โ operator keys, dashboard sessions, and the audit trail are lost on restart, leaving MASTER_KEY as the only way back in |
Outside production (GATEWAY_ENV unset or any other value), all five settings are honored with only a startup warning โ none of them is refused.
CORS and trusted proxiesโ
CORS_ORIGINS- comma-separated list of allowed origins, matched literally against the request'sOriginheader โ there is no wildcard, so aCORS_ORIGINS=*entry allows nothing a browser would ever send (quotes are stripped before the check, soCORS_ORIGINS="*"is caught too). List each origin explicitly. Unset serves no CORS headers. An unlisted origin is denied by the absence ofAccess-Control-Allow-Origin, not by a refused preflight.TRUSTED_PROXIES- comma-separated CIDRs of trusted reverse proxies;X-Forwarded-For/X-Real-IPis honored only from these (default: loopback,127.0.0.0/8and::1/128). An invalid value exits at startup. This matters for the per-IP rate limiter below โ without the real proxy's CIDR listed, every request behind an untrusted proxy resolves to the same client IP and shares one bucket.
Rate limitingโ
Per-IP rate limiting is enabled by default โ it is not opt-in.
RATE_LIMIT_RPS- per-IP requests per second (default20). Set to0to disable the middleware entirely; an invalid or negative value is ignored with a startup warning and the default is used instead. SettingRATE_LIMIT_RPSalone also resets the burst back to the default40โ pair it withRATE_LIMIT_BURSTfor a custom rate/burst combination.RATE_LIMIT_BURST- per-IP burst capacity (default40). The limiter tracks at most 100,000 IPs.
Rejected requests are surfaced on the gateway_rate_limit_rejections_total metric โ see Monitoring.
RATE_LIMIT_RPS=0 disables this per-IP middleware. The rate-limit plugin's requests_per_second: 0 means the opposite โ a rate the gateway cannot serve โ and is rejected at config load; disable that plugin with enabled: false instead. See Rate limiting.
Model catalogโ
FERRO_MODEL_CATALOG_URL- override the model-catalog source URL. The catalog supplies pricing, capabilities, and lifecycle metadata used for cost estimation and/v1/modelsenrichment. By default the gateway fetches the catalog from the public GitHub releases offerro-labs/model-catalog, with an embedded catalog as fallback, so the gateway never fails to start if the source is unreachable. Point this at an internal mirror for air-gapped deployments, or at a custom catalog for enterprise/custom pricing. Any userinfo and query parameters in the URL are stripped from logs, so tokens embedded in the URL are not leaked.FERRO_MODEL_CATALOG_TIMEOUT- Go duration bounding the startup catalog fetch (default10s). The fetch runs before the listener binds, so a blocked-egress deployment waits this long before falling back to the embedded catalog. Set0to skip the remote fetch entirely โ the way to keep a fully air-gapped gateway from spending any startup time on it.FERRO_MODEL_DISCOVERY_INTERVAL- opt-in Go duration (e.g.6h) to live-refresh model lists from provider/modelsendpoints after startup. Unset, unparseable, or under1mleaves discovery disabled.
Each load attempt is recorded on the gateway_catalog_loads_total metric (source, result labels).
Ollama model listโ
FERRO_OLLAMA_MODELS- comma-separated Ollama model list narrowing what/v1/modelsadvertises. Neither variable is required โ Ollama serves whatever the operator pulled onto it, so an unset list lets the provider serve any model.OLLAMA_MODELS- deprecated, read for one more release with a startup warning. This is Ollama's own variable for the models directory, not a model list; a path-shaped value (/โฆ,~โฆ,./โฆ) with no comma is dropped with a warning rather than registered as a bogus model id.FERRO_OLLAMA_MODELSwins when both are set.
Profilingโ
ENABLE_PPROF- (security-sensitive) when set totrue, mounts the Gonet/http/pprofhandlers under/debug/pprof(heap, goroutine, profile, trace, etc.), alongside/debug/vars(expvar). Everything under/debugrequires a bearer token with the admin scope โ a heap or trace profile is a memory image that can contain request bodies and credentials, and expvar publishes the process command line.GATEWAY_ENV=productionallowsENABLE_PPROFbut warns at startup; unmount it once an investigation ends.
Bedrock / AWS credentialsโ
AWS Bedrock is registered automatically when AWS_REGION, AWS_ACCESS_KEY_ID, or AWS_BEARER_TOKEN_BEDROCK is present.
AWS_REGION- AWS region for Bedrock.AWS_ACCESS_KEY_ID- AWS access key (optional โ falls back to the instance role / default credential chain).AWS_SECRET_ACCESS_KEY- AWS secret key.AWS_SESSION_TOKEN- session token for temporary credentials (e.g. STS / assumed-role / SSO sessions). Required alongside the access key and secret when using temporary credentials.AWS_BEARER_TOKEN_BEDROCK- a Bedrock bearer token, used instead of SigV4 keys.
For the full list of provider API-key variables (OpenAI, Anthropic, Gemini, and the rest of the 30 supported providers), see Provider configuration.
Tracing (OTEL_*)โ
The gateway reads exactly two OTEL_* variables โ nothing else:
OTEL_EXPORTER_OTLP_ENDPOINT- OTLP collector base endpoint. Setting it alone turns tracing on and takes precedence over the config file'sobservability.tracing.endpoint. The base getsv1/tracesappended underhttp/protobuf.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT- signal-specific traces endpoint, used verbatim (no path appended). It outranksOTEL_EXPORTER_OTLP_ENDPOINTwhen both are set.
Everything else about tracing โ including the sampler โ is config-only. observability.tracing.sample_ratio builds a ParentBased head sampler (default 1.0, so an inbound sampled trace is always followed); OTEL_TRACES_SAMPLER and OTEL_TRACES_SAMPLER_ARG have no effect on this gateway. OTEL_EXPORTER_OTLP_HEADERS reaches the exporter through the OTel SDK directly, not through the gateway. See the config reference for the full observability.tracing block and Monitoring for metrics and the OTLP exporter setup.
Relatedโ
- Configuration โ routing, plugins, and the
observability.tracingconfig block - Monitoring โ metrics, health checks, and OTLP tracing setup
- Auth โ MASTER_KEY, API keys, and dashboard sessions
- Rate limiting โ the per-IP middleware vs. the
rate-limitplugin - Provider configuration โ per-provider API keys and
<PROVIDER>_BASE_URL