Configuration Reference
browser-gateway is configured via a gateway.yml file, environment variables, or both.
browser-gateway is configured via a gateway.yml file, environment variables, or both.
Config File Location
The gateway looks for configuration in this order:
--config <path>CLI flagBG_CONFIG_PATHenvironment variable$BG_DATA_DIR/gateway.yml(default writable location,/datain Docker,~/.browser-gatewayoutside)./gateway.ymlin the current directory (legacy)./gateway.yamlin the current directory (legacy)
If no file is found on first boot, the gateway seeds $BG_DATA_DIR/gateway.yml with a minimal default (version: 1 + empty providers). The dashboard's config editor reads and writes the same path.
Full Config Reference
version: 1
gateway:
port: 9500 # Server port (default: 9500)
defaultStrategy: priority-chain # How the gateway chooses which provider to use
# Options: priority-chain, round-robin, least-connections,
# latency-optimized, weighted
connectionTimeout: 10000 # Max ms to wait when connecting to a provider (default: 10000)
# Tip: increase to 20000-30000 for cloud providers that
# launch browsers on-demand (cold start can take 10-15s)
healthCheckInterval: 30000 # How often to check if providers are alive (default: 30000)
shutdownDrainMs: 30000 # When stopping the gateway, how long to wait for active
# sessions to finish before force-closing (default: 30000)
cooldown:
defaultMs: 30000 # How long to skip a failing provider (default: 30000)
failureThreshold: 0.5 # Cooldown when failure rate exceeds this (default: 0.5 = 50%)
minRequestVolume: 3 # Min connection attempts before evaluating (default: 3)
sessions:
idleTimeoutMs: 300000 # Close sessions with no activity after this (default: 300000 = 5 min)
reconnectTimeoutMs: 300000 # How long a disconnected session stays available for reconnection (default: 300000 = 5 min)
queue:
maxSize: 50 # Max requests waiting when all providers are busy (default: 50)
timeoutMs: 30000 # How long a request waits in queue before giving up (default: 30000)
providers:
provider-name: # Your name for this provider (any string)
url: wss://provider.com?token=xxx # WebSocket URL including any auth params
headers: # Optional. Custom headers sent on the outbound WS upgrade
Authorization: Bearer ${API_KEY} # Needed for providers that require an Authorization header
limits:
maxConcurrent: 10 # Max simultaneous connections (optional, no limit if omitted)
priority: 1 # Lower number = tried first (default: 1)
weight: 1 # For weighted strategy: higher = more traffic (default: 1)
profile: my-profile-slug # Optional. Only sessions using this profile come here (see Profiles)
enabled: true # Optional. false keeps the provider configured but never routes to it
# Optional: get notified when things happen
webhooks:
- url: https://hooks.slack.com/services/xxx # Where to send notifications
events: [provider.cooldown, queue.timeout] # Which events (omit for all events)
dashboard:
enabled: true # Enable web dashboard at /web (default: true)
logging:
level: info # debug | info | warn | error (default: info)
# Saved browser profiles (see Profiles)
profiles:
enabled: false # Off by default. Restart after changing
filesystem:
path: ./profiles # Resolved under $BG_DATA_DIR
encryption:
keyEnv: BG_ENCRYPTION_KEY # Env var that holds the key (else $BG_DATA_DIR/.encryption-key)
lockTtlMs: 300000 # How long a write-back lock is held before it expires (default: 5 min)
cdpTimeoutMs: 10000 # Max ms to load a profile into the browser at connect
commitTimeoutMs: 4000 # Max ms to capture and save the profile at disconnect
# Session recording storage (recording itself is per session: ?session_record=true)
replay:
retentionDays: 7 # Delete recordings older than this. 0 = keep forever
maxBytesPerSession: 524288000 # 500 MB cap per session
filesystem:
path: ./replays # Resolved under $BG_DATA_DIR
capture:
format: jpeg # jpeg or png
quality: 70 # 1-100, jpeg only
everyNthFrame: 1 # 1 = every visual change
# Browser sessions used by the one-shot REST endpoints (see Session Pool below)
pool:
minSessions: 0
maxSessions: 5
# One-shot REST endpoints (/v1/screenshot, /v1/content, /v1/scrape)
rest:
allowedPrivateHosts: [] # Hostnames these endpoints may reach even though they
# point at a private address (default: none)Providers
URL
The URL is the WebSocket endpoint of your browser provider. It includes everything the provider needs for authentication - tokens, API keys, session IDs.
providers:
# Cloud provider with token auth
cloud-provider:
url: wss://provider.example.com?token=${PROVIDER_TOKEN}
# Self-hosted Playwright server
my-playwright:
url: ws://playwright-host:3000
# Raw Chrome with remote debugging
my-chrome:
url: http://chrome-host:9222 # Auto-discovers WebSocket endpoint
# Any WebSocket endpoint
my-custom:
url: ws://custom-service:8080/browserLimits
limits:
maxConcurrent: 10 # Max simultaneous connections to this providerWhen a provider reaches maxConcurrent, the gateway skips it and tries the next one. If not set, there is no connection limit.
Priority
priority: 1 # Lower = higher priority. Tried first in priority-chain strategy.Providers with the same priority are tried in config order.
Weight
weight: 3 # For weighted strategy only. Higher = more traffic. Default: 1.Weights only matter when using the weighted strategy. A provider with weight: 3 gets 3x more traffic than one with weight: 1. See Load Balancing for detailed examples.
Enabled
enabled: false # Keep the provider configured but never route to it. Default: on.A disabled provider is skipped by every strategy, is not health-checked, and a request pinned to it is rejected as if it did not exist. See Turning A Provider Off.
Custom headers
providers:
cloud-with-bearer:
url: wss://provider.example.com
headers:
Authorization: Bearer ${API_KEY}
X-Client: browser-gatewayArbitrary key/value headers sent on the outbound WebSocket upgrade to the provider. Needed for providers that require an Authorization: Bearer header instead of a URL query token, or that expect any other custom handshake header.
Merge order for outbound headers:
- Router userinfo (
https://user:pass@host) resolves toAuthorization: Basic ... - Explicit
headers:values override any client-supplied header of the same name
Limits: 20 headers max, key up to 80 chars, value up to 4 KB.
Pin to a profile
providers:
provider-team-acme:
url: wss://provider.example.com?token=${TOKEN_A}
profile: team-acmeSends only ?profile=team-acme connections to this provider. Optional: a provider with no profile setting takes every session and can save any profile. See Profiles.
Multi-profile
multiProfile: true is accepted for older configs and means the same as leaving profile unset: the provider takes every session and can save any profile.
One-Shot REST Endpoints
POST /v1/screenshot, POST /v1/content and POST /v1/scrape take a url and load
it in a browser for you. Because the gateway is the one fetching the page, the url
is restricted:
- It must start with
http://orhttps://. Other schemes, includingfile:, are refused. - Its host must be a public address. Addresses that only mean something inside your
own network are refused:
localhostand127.0.0.1, the10.x,172.16-31.xand192.168.xranges, the169.254.xrange used for cloud metadata services, and the.localand.internalname suffixes. - Hostnames are looked up, and every address they resolve to is checked the same way. A public name that points at a private address is refused too.
A refused request comes back as HTTP 400 with the host named in the message.
To screenshot something on your own network, such as a staging site, list its hostname:
rest:
allowedPrivateHosts:
- staging.internal
- 127.0.0.1Matching is on the hostname exactly as written in the request, lowercased. A listed hostname skips both the literal-address check and the lookup check. The list is empty by default, so nothing private is reachable until you add it.
Deep Dives
Each major feature has its own guide with real-world examples:
- Load Balancing - All 5 strategies explained with when to use each
- Failover - How automatic failover works, cooldown system
- Request Queue - What happens when all providers are busy
- Webhooks - Get notifications when a provider is put on cooldown, a queued request times out, or the gateway shuts down
- Session Lifecycle - What happens during connect and disconnect
- Graceful Shutdown - How the gateway shuts down cleanly
Environment Variable Interpolation
Use ${ENV_VAR} syntax in any string value. The gateway resolves these at startup.
providers:
production:
url: wss://service.com?token=${API_TOKEN}Supports defaults with ${VAR:-default}:
gateway:
port: ${PORT:-9500}Secrets should ALWAYS use env vars. Never put actual tokens in the config file.
Environment Variables (No Config File)
These environment variables can be set alongside or instead of a config file:
| Variable | Description | Default |
|---|---|---|
BG_TOKEN | Auth token (if set, all connections require it) | None (no auth) |
BG_ENCRYPTION_KEY | Profile encryption key (auto-generated under $BG_DATA_DIR/.encryption-key if unset) | Auto |
BG_DATA_DIR | Where the gateway stores config, profiles, and the encryption key | /data (Docker), ~/.browser-gateway (otherwise) |
BG_CONFIG_PATH | Path to gateway.yml | $BG_DATA_DIR/gateway.yml |
BG_ALLOWED_ORIGINS | Cross-origin allowlist (comma-separated) | Same-origin only |
BG_PUBLIC_URL | The public base URL the gateway gives to agents (for example in GET /mcp/setup.md). Set it when the gateway sits behind a proxy or domain, e.g. https://gateway.example.com. | Built from the bind interface and port, e.g. http://localhost:9500 |
BG_TRUST_PROXY | Set to 1 only behind a reverse proxy that overwrites X-Forwarded-Host. The gateway then uses that header, not Host, when checking a browser request's Origin and the allowed hosts on /mcp. | Off (Host is used) |
BG_ALLOWED_HOSTS | Extra Host header values accepted on /mcp when BG_TOKEN is unset (comma-separated) | Loopback only |
PORT | Server port (12-factor convention used by Railway, Render, Fly, Heroku) | 9500 |
HOST | Bind interface. Setting it always wins over the default below. | 0.0.0.0 when BG_TOKEN is set, 127.0.0.1 when it is not |
LOG_LEVEL | debug / info / warn / error (overrides gateway.yml) | info |
HTTP_PROXY, HTTPS_PROXY, NO_PROXY | Outbound proxy (honored by Node's built-in fetch) | None |
TZ | Timezone for log timestamps | System default |
Which interface the gateway listens on
An interface is the network address the gateway answers on. 0.0.0.0 means every
address the machine has, so other machines on the network can reach it.
127.0.0.1 (also called loopback) means only programs on the same machine can.
If BG_TOKEN is not set, the /v1/* routes have no authentication, so the
gateway binds to 127.0.0.1 and stays private to the machine it runs on. Set
BG_TOKEN and it binds to 0.0.0.0 instead. Setting HOST yourself always wins,
so HOST=0.0.0.0 still opens an unauthenticated gateway if that is what you want.
If the gateway used to be reachable from another machine and now is not, set
BG_TOKEN (recommended) or set HOST=0.0.0.0 explicitly.
.env File
The gateway automatically loads a .env file from the current directory. Put secrets here instead of passing them inline:
# .env
BG_TOKEN=my-secret-tokenThe .env file is loaded on startup. Environment variables set externally (e.g., via Docker -e flags) take precedence over .env values.
Multiple Providers Example
version: 1
providers:
# Cloud provider - use first
cloud-primary:
url: wss://provider.example.com?token=${PRIMARY_TOKEN}
limits:
maxConcurrent: 3
priority: 1
# Self-hosted servers - overflow
playwright-1:
url: ws://playwright-server-1:3000
limits:
maxConcurrent: 10
priority: 2
playwright-2:
url: ws://playwright-server-2:3000
limits:
maxConcurrent: 10
priority: 2
# Second cloud provider - last resort
cloud-backup:
url: wss://backup-provider.example.com?key=${BACKUP_KEY}
limits:
maxConcurrent: 20
priority: 3This setup: use the primary cloud provider first (3 concurrent), overflow to self-hosted Playwright servers (20 concurrent across 2 servers), fall back to the backup provider if everything else is full.
Session Pool
The session pool manages browser connections for the REST API endpoints (/v1/screenshot, /v1/content, /v1/scrape). Instead of opening a new browser connection for every request, the pool keeps a small number of long-lived connections and creates lightweight pages (tabs) within them.
pool:
minSessions: 0 # Minimum browser sessions to keep alive
maxSessions: 5 # Maximum browser connections from pool
maxPagesPerSession: 10 # Pages per browser before creating another
retireAfterPages: 100 # Recycle browser after this many total pages
retireAfterMs: 3600000 # Recycle after 1 hour max
idleTimeoutMs: 300000 # Close idle browsers after 5 minutes
pageTimeoutMs: 30000 # Connect timeout for pool browsers| Setting | Default | What it does |
|---|---|---|
| minSessions | 0 | How many browser sessions to keep alive even when idle. 0 means scale to zero, no browser running until the first REST request. Set to 1 to eliminate cold start latency. |
| maxSessions | 5 | Hard cap on total browser connections. 5 sessions with 10 pages each = 50 concurrent REST operations. |
| maxPagesPerSession | 10 | When a browser session has this many active pages, the next request creates a new session. Higher values use less connections but put more load on each browser. |
| retireAfterPages | 100 | After serving this many total pages, the session is marked for retirement. It finishes active pages, then closes. This prevents Chrome memory leaks from accumulating over time. |
| retireAfterMs | 3600000 | Maximum lifetime of a browser session (1 hour). Even if the page count hasn't been reached, long-running sessions are recycled. |
| idleTimeoutMs | 300000 | Close idle sessions (no active pages) after this many milliseconds. Only applies to sessions above minSessions, the pool never closes below the minimum. |
| pageTimeoutMs | 30000 | How long the pool waits to connect a browser through the gateway. A page held longer than twice this value is force-released. |
When to adjust these settings:
- High throughput, beefy providers: Increase
maxPagesPerSessionto 20 andmaxSessionsto 10 - Memory-constrained providers: Lower
maxPagesPerSessionto 5, lowerretireAfterPagesto 50 - Zero idle cost: Keep
minSessions: 0(default): browsers only run when requests are active - Zero cold start: Set
minSessions: 1, one browser stays warm at all times