Supported Providers
browser-gateway works with any WebSocket endpoint. If your browser provider exposes a WebSocket URL, it works with the gateway.
browser-gateway works with any WebSocket endpoint. If your browser provider exposes a WebSocket URL, it works with the gateway.
How Providers Work
Each provider is a WebSocket URL. The gateway forwards bytes transparently and doesn't parse what's behind it. You configure the URL (with any auth params baked in), set connection limits, and assign a priority.
providers:
my-provider:
url: <websocket-url-with-auth>
limits:
maxConcurrent: <number>
priority: <number>Provider Types
Remote browser services (cloud)
Cloud providers typically give you a WebSocket URL with an API key:
providers:
cloud-provider:
url: wss://provider.example.com?token=${API_TOKEN}
limits:
maxConcurrent: 5
priority: 1browserserve (self-hosted runtime)
browserserve is a self-hosted browser server from the same stack: one container hands out isolated Chrome sessions with a warm pool, host-derived capacity, and an optional profile channel. It is the only self-host option that carries a full profile, including IndexedDB and service workers, across sessions.
docker run -d -p 9222:9222 --shm-size=1g ghcr.io/browser-gateway/browserserve:latestConfig:
providers:
browserserve:
url: ws://your-host:9222
limits:
maxConcurrent: 8
priority: 1The gateway auto-detects a browserserve provider and adopts its advertised capacity when maxConcurrent is left unset. See the browserserve reference for configuration, the profile channel, and isolation tiers.
Self-hosted Playwright
Run your own Playwright server:
npx playwright run-server --port 4000 --host 0.0.0.0Or via Docker:
docker run -d -p 4000:3000 --shm-size=1gb \
mcr.microsoft.com/playwright:v1.50.1-noble \
/bin/sh -c "npx -y playwright@1.50.1 run-server --port 3000 --host 0.0.0.0"Config:
providers:
my-playwright:
url: ws://playwright-host:4000
limits:
maxConcurrent: 10
priority: 1Important: Playwright client and server versions must match exactly.
Raw Chrome (--remote-debugging-port)
Chrome/Chromium with remote debugging enabled:
google-chrome --remote-debugging-port=9222 --headless --no-sandboxGet the WebSocket URL:
curl http://localhost:9222/json/version
# Look for "webSocketDebuggerUrl"Config:
providers:
my-chrome:
url: http://chrome-host:9222 # Auto-discovers the WebSocket endpoint
limits:
maxConcurrent: 1
priority: 1Use http:// for Chrome CDP, the gateway auto-discovers the full WebSocket path via /json/version. You can also use the full ws:// URL if you already have it.
Connection Modes
The gateway is protocol-agnostic - it forwards raw bytes without parsing. However, your client needs to use the correct connection method for the provider type:
| Provider Type | Client Method |
|---|---|
| Playwright run-server | chromium.connect(wsEndpoint) |
| Chrome CDP endpoints | chromium.connectOverCDP(wsEndpoint) |
| Puppeteer (any) | puppeteer.connect({ browserWSEndpoint }) |
Multiple Providers
Mix any number of providers with different priorities:
providers:
primary:
url: wss://provider-a.example.com?key=${KEY_A}
limits:
maxConcurrent: 5
priority: 1
overflow:
url: ws://my-playwright-server:4000
limits:
maxConcurrent: 20
priority: 2
emergency:
url: wss://provider-b.example.com?key=${KEY_B}
limits:
maxConcurrent: 50
priority: 3The gateway tries them in priority order. If the primary is full or down, traffic routes to the next available provider automatically.
Provider Capacity
Every provider has a real concurrency ceiling. Set maxConcurrent to the smaller of what the provider actually handles and what you have paid for.
Browserless (self-hosted or Railway)
The ghcr.io/browserless/chromium container on an 8 GB / 8 vCPU host holds 5 concurrent sessions safely, with 6 as the absolute ceiling. Above 6, incoming sessions serialize inside Browserless's internal multiplexer and clients start hitting timeouts.
We verified this by ramping load until sessions failed. At the failure point, host RAM peaked at 21% and CPU at 6%, so the cliff is not resource-bound. Browserless documentation claims 10 sessions per GB of RAM; the observed number is 0.625 per GB.
providers:
browserless-1:
url: wss://browserless-1.example.com?token=${TOKEN_1}
limits:
maxConcurrent: 5
browserless-2:
url: wss://browserless-2.example.com?token=${TOKEN_2}
limits:
maxConcurrent: 5To serve N concurrent sessions on Browserless, provision ceil(N / 5) instances. If your traffic bursts (many session opens within a few seconds), keep at least 20% headroom below the fleet ceiling. Simultaneous saturation across every slot in the fleet can push individual instances briefly above their per-instance ceiling and trigger the serialization behavior.
Bump TIMEOUT on Browserless to a real value (default is 30000 ms, which kills sessions at 30 seconds regardless of activity):
docker run -e TIMEOUT=3600000 ghcr.io/browserless/chromiumbrowserserve
browserserve's /json/version endpoint advertises the concurrency the host can actually sustain, measured on the host at startup. The gateway reads this on provider add and adopts it when maxConcurrent is left unset.
Other providers
Ask the vendor for the real number, not the marketing number. If in doubt, ramp your own load until sessions start failing and set maxConcurrent to 80% of the last clean level.
Custom Headers
Some providers require an Authorization: Bearer <key> header on the WebSocket upgrade instead of a URL query token. Configure custom headers per provider:
providers:
cloud-bearer:
url: wss://provider.example.com/cdp
headers:
Authorization: Bearer ${API_KEY}
X-Client: browser-gatewayHeaders are sent on the outbound WebSocket handshake to the upstream. Values interpolate from environment variables. Limits: 20 headers max, key up to 80 chars, value up to 4 KB. See Configuration → Custom headers for merge order.
Profiles And Providers
Whether a provider can safely carry a profile depends on how the underlying browser is isolated between sessions. The self-hosted runtime gives every session a fresh isolated Chrome and can serve any profile with no pinning. External CDP providers must pin one profile per slot with profile:. multiProfile: true on external providers is rejected at add-time.
See Profiles → Provider Isolation for the full matrix and the automatic residue detection that catches misconfigured or misbehaving providers.
Tips
- Auth goes in the URL or in headers. Query params (
?token=xxx) work for most providers. Useheaders:forAuthorization: Bearerschemes. Always reference secrets via${ENV_VAR}. - Set realistic limits. Check the provider's actual concurrency limit and set
maxConcurrentaccordingly. The gateway enforces these locally to avoid hitting provider rate limits. - Chrome CDP needs the full path. Chrome's CDP URL includes a UUID that changes on restart. Point at
http://host:9222and the gateway auto-discovers the current WebSocket path via/json/version.
Configuration Reference
browser-gateway is configured via a gateway.yml file, environment variables, or both.
browserserve
browserserve is a self-hosted browser server. One container runs isolated Chrome sessions over CDP, with an optional profile channel to save and restore session state. Run it standalone, or add it to the gateway as a provider.