Integration · AI & workflow tools
How to use a proxy with Stagehand
Stagehand launches a local browser with localBrowser.launch({ proxy: { server } }), but in version 4 it refuses a proxy username or password with "Authenticated local browser proxies are not supported yet". The working route is a small local forwarder: the proxy-chain package opens an unauthenticated port on 127.0.0.1 that passes traffic on to the ARC gateway with your credentials, and Stagehand's browser points at that port.
Reviewed 30 Oct 2026
http://USERNAME _pool-arcfast _country-de:PASSWORD@ proxy.arcproxies.io:8000The short answer
- Stagehand 4:
proxy.usernameandproxy.passwordon a local browser throw an error. Stagehand's own docs list authenticated local proxies as unsupported. - Stagehand 3.7: the same fields were accepted, but in our test Chrome answered the 407 without credentials and the page never loaded.
anonymizeProxy()fromproxy-chainturnshttp://user:pass@gatewayinto a localhttp://127.0.0.1:portwith no credentials; pass that asserver.- One forwarder and one browser per agent task, each with its own
_session-handle, keeps every task on its own exit IP.
Setup
Set up Stagehand with a proxy, step by step
- 01
Install Stagehand and proxy-chain
Stagehand drives a locally installed Chrome.
proxy-chainis a Node library that runs the forwarder inside your own process, so no extra service is needed.shell1 linenpm i @browserbasehq/stagehand proxy-chain zod - 02
Start a forwarder, launch the browser, check the exit IP
Percent-encode the username and password when you build the upstream URL. The forwarder listens on a random local port and adds the credentials on every request it passes on.
Stagehand.create()attaches the AI layer to the browser; pages come frombrowser.context.check-exit.ts20 linesimport { localBrowser, Stagehand } from "@browserbasehq/stagehand";import { anonymizeProxy, closeAnonymizedProxy } from "proxy-chain";const upstream = "http://" + encodeURIComponent("USERNAME_pool-arcfast_country-de_session-k3f9a1x2_sesstime-10") + ":" + encodeURIComponent("PASSWORD") + "@proxy.arcproxies.io:8000";const local = await anonymizeProxy(upstream); // http://127.0.0.1:<port>const browser = await localBrowser.launch({ headless: true, proxy: { server: local } });const stagehand = await Stagehand.create({ browser, model: { modelName: "openai/gpt-5.4-mini", apiKey: process.env.OPENAI_API_KEY },});const [page] = await browser.context.pages();const response = await page.goto("https://api.ipify.org?format=json");console.log(response ? await page.evaluate(() => document.body.innerText) : "no response");await browser.close();await closeAnonymizedProxy(local, true); - 03
One session per agent task
An agent that searches, filters and opens results should keep one IP from start to finish. Wrap the forwarder and the browser in a helper that mints a handle per task and cleans both up afterwards, then run
act(),observe()andextract()inside it.task.ts23 linesimport { randomBytes } from "node:crypto";import { localBrowser, Stagehand } from "@browserbasehq/stagehand";import { anonymizeProxy, closeAnonymizedProxy } from "proxy-chain";const BASE = "USERNAME_pool-arcfast_country-de"; // pool + country; session added per taskexport async function withSession<T>(task: (s: Stagehand) => Promise<T>): Promise<T> { const user = BASE + "_session-" + randomBytes(4).toString("hex") + "_sesstime-15"; const local = await anonymizeProxy( "http://" + encodeURIComponent(user) + ":" + encodeURIComponent("PASSWORD") + "@proxy.arcproxies.io:8000", ); const browser = await localBrowser.launch({ headless: true, proxy: { server: local } }); try { const stagehand = await Stagehand.create({ browser, model: { modelName: "openai/gpt-5.4-mini", apiKey: process.env.OPENAI_API_KEY }, }); return await task(stagehand); } finally { await browser.close(); await closeAnonymizedProxy(local, true); }} - 04
Treat a missing response as a proxy failure
When the gateway rejects the credentials,
page.goto()does not throw: Chrome shows its own error page (ERR_TUNNEL_CONNECTION_FAILED) and the call resolves without a response. Check for that before letting the model act on the page, or it will try to work with an error screen.guard.ts4 linesconst response = await page.goto(url);if (!response || response.status() >= 400) { throw new Error("load failed: " + (response ? response.status() : "proxy or network error"));}
Why Stagehand needs a forwarder for proxy authentication
Chrome's own proxy setting has no place for credentials; a proxy that asks for them sends a 407 challenge, and something has to answer it. Playwright and Puppeteer answer it over the DevTools protocol, which is why their proxy options take a username. Stagehand 4 does not handle that challenge for local browsers and rejects the fields up front.
The forwarder moves the answer out of the browser. Chrome talks to 127.0.0.1 without credentials; the forwarder adds the Proxy-Authorization header on each request to the gateway. HTTPS stays end to end: the forwarder relays the CONNECT tunnel and never sees page content.
Stagehand versions and proxy options compared
| Setup | Proxy with username and password | Result in our test |
|---|---|---|
Stagehand 4, localBrowser.launch({ proxy: { username, password } }) | Rejected | Throws before the browser starts |
Stagehand 3.7, localBrowserLaunchOptions.proxy with credentials | Accepted by the type | Chrome received 407 and sent no credentials |
Stagehand 4 + proxy-chain forwarder | Credentials held by the forwarder | Page loaded through the gateway |
If you run an older Stagehand with the documented username and password fields, test with an IP echo page before trusting it.
Sessions, timing and bandwidth for browser agents
Agents are slow: every act() call waits for a model response, so a five-step task can take minutes. Set _sesstime- longer than the task's worst case, and keep in mind that a sticky exit is held on a best-effort basis; a changed IP mid-task should trigger a retry of the task, not a crash. AI agents covers patterns for this, and the browser-use guide shows the same idea in Python.
A local branded Chrome also makes its own background requests, for updates and Google services, and through a forwarder those count against your balance like page traffic. In our test they started within seconds of launch.
The problem
Common pitfalls
Credentials in the server URL
server: "http://user:pass@host:port" does not help: Chrome ignores credentials embedded in proxy settings. Keep them in the forwarder's upstream URL.
Leaking forwarders
Each anonymizeProxy() call opens a local port. Close it with closeAnonymizedProxy(url, true) in a finally block, or long-running workers accumulate open ports.
Sharing one forwarder across parallel tasks
A forwarder is bound to one upstream username, so all browsers behind it share one session handle and one exit IP. Start one forwarder per task.
Unencoded credentials
A password containing @ or : breaks the upstream URL. Wrap both parts in encodeURIComponent() as in the examples.
FAQ
Questions
Something not covered? Write to contact@arcproxies.io.
Does Stagehand support authenticated proxies?
Not for local browsers in Stagehand 4: setting proxy.username or proxy.password throws "Authenticated local browser proxies are not supported yet". A local forwarder such as proxy-chain's anonymizeProxy() holds the credentials instead, and Stagehand uses the forwarder's credential-free address.
Where do I set a proxy in Stagehand?
In Stagehand 4, pass proxy: { server, bypass } to localBrowser.launch(). In Stagehand 3, the same object went under localBrowserLaunchOptions in the Stagehand constructor.
How do I give each Stagehand agent its own IP?
Start one forwarder per task with a different _session- handle in the ARC username, and launch one browser behind each. The exit IP then stays the same for that task and differs from the others.
Why does my Stagehand page show ERR_TUNNEL_CONNECTION_FAILED?
The proxy refused the tunnel, most often because of a wrong username or password in the forwarder's upstream URL. page.goto() resolves without a response in this case, so check for a missing response before calling act().
Sources & review
Last reviewed by the ARC Proxies team. Prices are read from the live catalog at build time.
- [1]Stagehand: Browser configuration (v4): local proxy options and limitations
- [2]Stagehand: Browser configuration (v3): localBrowserLaunchOptions.proxy
- [3]Apify (GitHub): proxy-chain: anonymizeProxy() and closeAnonymizedProxy()
- [4]Chromium: Proxy support in Chrome: credentials in proxy settings
Next
What people build with it
Integrations
Other ai & workflow tools
Get started
Get credentials and paste them in.
Sign up, buy a gigabyte, generate a username in the dashboard. Code WELCOME15 takes 15% off your first purchase; bandwidth never expires.