docs · proxies
Bring your own proxy
By default a session leaves the internet from the datacenter IP of the host its worker runs on (Hetzner, in Germany or Finland). Pass an upstream proxy and the browser’s traffic goes through it instead. We do not sell proxy traffic and run no proxy pool of our own.
Passing a proxy
| where | how |
|---|---|
| WebSocket URL or REST query | proxy=<url-encoded proxy URL> (also externalProxyServer= and --proxy-server=) |
| POST /v1/sessions body | "proxyUrl": "http://user:pass@host:port" |
| launch JSON | {"proxy": {"server": "…", "username": "…", "password": "…"}} |
- Schemes:
http,https,socks4,socks4a,socks5,socks5h. A barehost:portin--proxy-servermeans http. - Credentials in the URL work. They are handed to a forwarder inside the worker, never to Chromium’s command line, and never written to our logs; we record only the proxy’s host and port.
- URL-encode the whole proxy URL when it goes into a query string.
- A proxy address inside a private network is refused: it would be a way around the egress firewall, not a proxy.
const proxy = encodeURIComponent('http://user:pass@proxy.example.net:8000');
const browser = await chromium.connectOverCDP(
`wss://gw.flatbrowser.com?token=flat_live_YOUR_KEY&proxy=${proxy}`,
);Sticky sessions
Stickiness — keeping the same exit IP for a whole job — is a feature of your proxy vendor, usually selected by a session token in the proxy username. Flatbrowser passes the URL through unchanged, so sticky sessions work exactly as your vendor documents them. Use one Flatbrowser session per sticky identity: the proxy is fixed when the session starts. Browserless’s proxySticky flag has nothing to switch here and is ignored when you bring a proxy.
// one sticky upstream identity per job: the session id lives in the
// proxy username (the exact syntax is your vendor's — check their docs)
const user = `customer-acme-session-${jobId}`;
const proxy = encodeURIComponent(`http://${user}:${pass}@gate.vendor.example:7000`);
const url = `wss://gw.flatbrowser.com?token=flat_live_YOUR_KEY&proxy=${proxy}` +
'&proxyCountry=de&proxyLocaleMatch=true';Matching locale and timezone to the exit
A German exit IP with an en-US browser on UTC is an odd combination. With your own proxy you can line them up:
| parameter | effect |
|---|---|
proxyCountry=de&proxyLocaleMatch=true | sets a representative locale and timezone for that country (de-DE, Europe/Berlin) |
proxyTimezone=Europe/Vienna | sets the timezone explicitly |
locale=de-AT | sets navigator.language and Accept-Language explicitly |
timezone=Europe/Vienna | sets the browser timezone explicitly |
locale and timezone win over anything derived from proxyCountry, and they work without a proxy too. proxyCountry cannot choose where your proxy exits — your proxy URL decides that.
WebRTC
Every session runs with the WebRTC policy disable_non_proxied_udp by default: WebRTC may not send UDP around a proxy, so a page cannot learn the worker’s own address through a STUN request — this also covers a proxy you set per browser context (Playwright newContext({ proxy }), Puppeteer createBrowserContext({ proxyServer })), which our side cannot see. With a session proxy you cannot turn it off. Without one, you may choose another policy with --webrtc-ip-handling-policy=….
Chromium’s own background traffic (component updates, phishing-detection and translation lookups, sync and the like) is switched off in every session, so your proxy carries only what your pages load.
What is not offered
Your responsibility
You choose and pay the proxy vendor, and the Acceptable Use Policy applies to proxied traffic exactly as to direct traffic. Our abuse blocklist applies as well: blocked hostnames never reach your proxy, they fail to resolve inside the browser.