To use a proxy in Puppeteer, pass Chrome a --proxy-server launch argument when you start the browser. That routes every page through the proxy. Authenticated proxies need one extra step — page.authenticate() — and rotating per page means using separate browser contexts. Those three pieces cover almost every real setup.
The basic setup: a launch argument
Puppeteer controls headless Chrome, and Chrome takes the proxy as a command-line flag. Pass it in the args array at launch:
const browser = await puppeteer.launch({
headless: true,
args: ["--proxy-server=http://pr.quanticdata.io:7777"],
});
const page = await browser.newPage();
await page.goto("https://ipinfo.io/ip");
console.log(await page.evaluate(() => document.body.innerText));Every request from this browser now exits through the proxy. Note the proxy is set on the browser, not the page — all pages in this browser share it. That is fine for a single-IP session; for per-page rotation, see below.
Authenticated proxies
Here is the gotcha that fills Stack Overflow threads: Chrome's --proxy-server flag does not accept user:pass@ in the URL. Credentials must be supplied separately, per page, with page.authenticate() before you navigate:
const browser = await puppeteer.launch({
args: ["--proxy-server=http://pr.quanticdata.io:7777"],
});
const page = await browser.newPage();
await page.authenticate({ username: "USER", password: "PASS" });
await page.goto("https://example.com");Forgetting page.authenticate() is the number-one reason a paid proxy "doesn't work" in Puppeteer — you get a blank page or a 407 because the proxy rejected the unauthenticated request. Call it on every page that uses the proxy, before goto.
Testing the proxy before the real run
Always confirm the proxy is actually routing before you point Puppeteer at your real target — a misconfigured proxy that silently falls back to your server's IP is how a scraper gets the whole box banned on the first run. Navigate to an IP-echo endpoint and read the result: it should show the proxy's exit IP, not your machine's. On a rotating endpoint, load it twice and you should see two different IPs, confirming rotation is live. This ten-line check runs in seconds and saves the class of bug where everything looks fine locally but every request has been hitting the target from one bare, un-proxied address.
Rotating proxies per page
Because the proxy is set at launch, rotating a different IP per page means either relaunching the browser (heavy) or using separate browser contexts — but Chrome applies the launch proxy to all contexts, so true per-context proxies need a proxy-per-launch or an endpoint that rotates for you. The clean answer is the latter: point the single launch proxy at a rotating endpoint, and the provider hands out a fresh IP per request automatically — no relaunching, no context juggling. That removes the whole problem: one launch arg, and every navigation gets a new exit IP. For sites that need the same IP across a multi-step flow, use a sticky-session endpoint instead.
Proxy plus stealth: the real-world combination
A proxy fixes the IP, but headless Chrome still leaks automation signals that detectors read. For defended targets, pair the proxy with puppeteer-extra-plugin-stealth to patch those tells, and use residential IPs so the exit looks like a real user rather than a datacenter range. The combination — residential proxy + stealth + human-like pacing — is what actually survives serious anti-bot systems; the proxy alone or stealth alone gets flagged. It's the same recipe we detail for Playwright stealth, and it applies identically to Puppeteer.
Common mistakes
| Symptom | Cause | Fix |
|---|---|---|
| Blank page / 407 with paid proxy | Credentials in the --proxy-server URL | Use page.authenticate(), not user:pass@ |
| Proxy ignored | Flag on the page or set after launch | It must be a launch arg on the browser |
| Still blocked despite proxy | Headless automation tells | Add the stealth plugin + residential IPs |
| All pages share one IP | Proxy is browser-wide by design | Use a rotating endpoint for per-request IPs |
When Puppeteer plus proxy is more than you need
Running Puppeteer means operating a headless browser fleet plus the proxy and stealth layers — real infrastructure that needs maintaining as detectors evolve. If your goal is the data rather than browser control, a scraping API runs the browser, residential proxies and anti-block server-side and returns clean Markdown or JSON: you send a URL, you get data, with no launch args, no page.authenticate(), no stealth plugin to keep current. Keep Puppeteer for the interactive and logged-in flows where you need to drive the browser directly; hand the high-volume, gets-blocked scraping to an API built to win that fight. We walk through the tradeoff in our Playwright scraping guide, and it holds for Puppeteer just the same.