Puppeteer Proxy Setup: Auth, Rotation, Verification

How to run Puppeteer through a proxy: launch flags, authentication, per-context IPs, rotation with session IDs, and the failures that break it.

VoidMob Team
8 min read

You pass a proxy to Puppeteer with the Chrome flag --proxy-server at launch, then supply credentials per page with page.authenticate(). The proxy applies to the entire browser process, not to a single page, so rotation means either a new browser (or browser context) per identity, or a gateway that changes the exit IP based on the credentials you send. Everything else on this page is the detail that makes that hold up in production.

Basic setup

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch({
  headless: 'new',
  args: [
    '--proxy-server=http://proxy.voidmob.com:10092',
    '--proxy-bypass-list=<-loopback>',
  ],
});

const page = await browser.newPage();
await page.authenticate({ username: 'PROXY_USER', password: 'PROXY_PASS' });

await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
console.log(await page.content());
await browser.close();

Three things in that snippet matter more than they look:

  • Credentials never go in the flag. --proxy-server=http://user:pass@host:port is silently ignored by Chrome. Authentication happens over the HTTP 407 challenge, which is what page.authenticate() answers.
  • page.authenticate() is per page. Call it on every page and popup you open, before the first navigation, or the request fails with a blank auth prompt.
  • --proxy-bypass-list=<-loopback> forces localhost through the proxy too. Leave it out if you have a local test server; add it when you need every single request, including local ones, to exit through the proxy.

SOCKS5 in Puppeteer: the credential trap

Chrome accepts --proxy-server=socks5://host:port, but it cannot authenticate to a SOCKS5 proxy. page.authenticate() only answers HTTP proxy challenges. So SOCKS5 works only with IP whitelisting, or by running a local authenticating relay (the usual pattern is proxy-chain, which spawns an anonymous local proxy that forwards to your upstream with credentials attached) and pointing Chrome at http://127.0.0.1:<localPort>.

If you have both options, use the HTTP endpoint for Puppeteer and keep SOCKS5 for tools that support authenticated SOCKS natively.

One IP per browser, and how to get around it

--proxy-server is process-wide. Two pages in the same browser share one exit IP, which is correct for a session on one account and wrong for parallel scraping across identities. You have three ways out:

Browser context with its own proxy

Recent Puppeteer versions let you attach a proxy to an isolated context:

const context = await browser.createBrowserContext({
  proxyServer: 'http://proxy.voidmob.com:10092',
});
const page = await context.newPage();
await page.authenticate({ username: 'PROXY_USER', password: 'PROXY_PASS' });

Contexts also isolate cookies and storage, so this is the cleanest way to run several identities in one process. Verify it on your installed version before you build on it; older releases ignore the option.

One browser per identity

Heavier on RAM (budget roughly 100 to 200 MB per Chrome instance), but it is the only option that fully separates the browser process, and it is trivially parallelizable with a worker pool.

Same credentials, different session ID

If your provider controls the exit IP from the username, you do not need a new browser at all for rotation between runs. On VoidMob's shared pool you chain underscore parameters onto the username: USER_c_US_s_run1_ttl_30m pins every request tagged with session run1 to one US device for 30 minutes. Change run1 and the gateway hands you a different device; drop _s_ entirely and you get a new IP on every request. Country, city, subdivision, ISP and ASN targeting ride in the same string, which is what you want when the target ranks or prices content by network as well as region (ASN vs geo targeting covers when that distinction changes results).

Rotate or stick? Pick by workload

WorkloadRotation modeHow to set it
Public listing pages, no loginPer requestOmit the session parameter, launch one browser per worker
Logged-in session, forms, cartSticky for the sessionOne session ID per browser context, release when done
Long automation run (30 to 120 min)Sticky, then rotateNew session ID per run, TTL matched to the run
Several accounts in parallelOne IP per accountOne browser or context plus one session ID per account

Rotating mid-login is the single most common self-inflicted block: the platform sees the IP change between the form POST and the redirect and treats it as a hijacked session. If you are unsure how long to hold a pin, sticky sessions on mobile proxies has the timing rules. The same launch and rotation logic in Playwright is on the Playwright proxy page if you run both frameworks.

Verify the exit before you run volume

Never trust the flag. Assert the IP inside the browser, not from Node, because Node's own fetch does not go through Chrome:

const ip = await page.evaluate(() =>
  fetch('https://api.ipify.org?format=json').then((r) => r.json())
);
console.log(ip);

Run that at the start of the flow and again mid-run. Same address means the pin is holding; a new address means the device dropped and the gateway re-assigned you. For a one-off check of what a given proxy actually exits as (type, carrier, ASN), the Proxy Validator is faster than writing a script, and the IP Blacklist Checker tells you whether the exit is already burned on public lists.

Check what your current exit IP looks like

Failure modes and what causes them

  • ERR_TUNNEL_CONNECTION_FAILED: wrong port, wrong scheme, or credentials rejected. Test the same credentials with curl before blaming Puppeteer.
  • ERR_NO_SUPPORTED_PROXIES: you passed socks5:// with credentials, or a scheme Chrome does not understand.
  • Auth prompt appears in headful mode: page.authenticate() was called after goto, or on a different page object than the one navigating.
  • Requests leak around the proxy: WebRTC can expose the local address independently of the HTTP path. Launch with --force-webrtc-ip-handling-policy=disable_non_proxied_udp and confirm with the WebRTC Leak Test.
  • Page renders but content is wrong: this is usually geo, not the proxy. The IP is in one country while Accept-Language and the browser timezone say another. Set locale and timezone per context to match the exit.
  • Everything works, then blocks after 20 minutes: you are burning IPs faster than the pool can recycle them. Slow concurrency per IP down, not up.

Cost sizing

Puppeteer loads full pages, so bandwidth, not request count, drives your bill. Block images, fonts and media with request interception when you only need HTML, and it typically cuts page weight by more than half:

await page.setRequestInterception(true);
page.on('request', (req) => {
  const block = ['image', 'font', 'media', 'stylesheet'];
  block.includes(req.resourceType()) ? req.abort() : req.continue();
});

Shared pay-per-GB pools start at $3.99 for 1 GB and drop to $2.50 per GB at the 100 GB tier. If your work is one stable identity rather than volume scraping, a dedicated 4G/5G device from $49 per month gives you a single IP you rotate on your own schedule.

1How do I use a proxy in Puppeteer?

Pass --proxy-server=http://host:port in the launch args, then call page.authenticate({ username, password }) on each page before its first navigation. Credentials embedded in the flag URL are ignored by Chrome.

2Can I set a different proxy per page?

Not per page. The proxy is set per browser process, or per isolated browser context on recent Puppeteer versions via createBrowserContext({ proxyServer }). For a different IP per page you need one context or one browser per page, or a gateway that keys the exit IP to the credentials you send.

3Is Puppeteer free to use, and is it a Google project?

Yes to both. Puppeteer is an open-source Node library maintained by the Chrome DevTools team at Google, released under Apache 2.0. There is no license cost; your costs are compute and bandwidth.

4What is Puppeteer used for?

Driving Chrome or Firefox programmatically: rendering and scraping JavaScript-heavy pages, generating PDFs and screenshots, end-to-end testing, and automating multi-step browser flows that a plain HTTP client cannot complete.

5Are proxy servers illegal?

Proxies are ordinary network infrastructure and legal in most jurisdictions. Legality depends on what you do through them: collecting public data or checking region-specific content is routine, while evading a ban, faking engagement metrics, or bypassing identity verification breaks platform terms and can break the law.

Driving Puppeteer from an AI agent instead of a script? VoidMob's MCP server exposes the same exits as agent tools, so the agent can pull or rotate an IP mid-task without a proxy string baked into the launch args.

Run Puppeteer on real 4G/5G exits

Pay-per-GB mobile pools with session control in the username, or dedicated devices you rotate on demand.