Proxies for Puppeteer route each headless browser session through a different IP, so a target cannot tie all your traffic back to one address and rate-limit or ban it. For most defended sites the right type is a rotating residential proxy, set with Chrome's --proxy-server launch flag and authenticated through Puppeteer's own page.authenticate() method, and this guide walks through the exact setup, how many IPs you actually need, and how to stop Puppeteer from getting blocked.
Puppeteer drives real Chrome over the DevTools Protocol, so it loads full pages, runs JavaScript, and produces a genuine browser fingerprint. That makes it the tool of choice for scraping JavaScript-heavy sites, automated testing, and screenshots, and it also makes the proxy choices different from raw HTTP scraping. A browser pulls dozens of subresources per page, burns real bandwidth, and carries automation tells that a plain request never exposes. All of that shapes which proxy fits and how you wire it in.
Why Puppeteer needs proxies
One Puppeteer instance hammering a site from your own IP works until the target starts counting. A browser session that opens hundreds of pages an hour from a single address does not look like a shopper, it looks like a machine, and the defenses escalate in a predictable order:
- Rate limiting. Too many requests from one IP and you get
429 Too Many Requestsor a deliberate slowdown. - Outright IP blocks. Keep pushing and the address is blocked, sometimes for hours, sometimes permanently.
- Fingerprinting. Serious anti-bot systems profile the browser itself, not just the IP, and serve CAPTCHAs or fake data to anything automated.
Proxies solve the first two by spreading requests across many IPs, so no single address trips a limit. They do nothing for the third on their own, which is the part most Puppeteer tutorials skip and this one will not. There is also the geo case: prices, search results and availability change by country, and a proxy is how you make Puppeteer appear in the market you want to see.
Which proxy type fits Puppeteer
Match the proxy to the target, not to a default. Each type is correct for a specific tier of difficulty, and the best proxies for Puppeteer depend entirely on how hard the site fights back.
Datacenter proxies. IPs from hosting providers: fast, cheap, plentiful. Their weakness is that sites can tell an IP belongs to a datacenter, and a headless browser arriving from a hosting range is one of the more suspicious combinations you can present. Correct for internal testing, undefended targets, and any site that does not scrutinize IP reputation.
Rotating residential proxies. IPs on real home connections, served from a large pool through one gateway. To a website they read as ordinary consumers, so they pass the reputation checks that reject datacenter ranges. This is the default for defended targets, and for Puppeteer specifically it pairs well, because a real browser plus a real home IP is a convincing combination. The tradeoff is cost, billed per gigabyte, which matters more with Puppeteer than with raw HTTP because a browser loads whole pages. More on trimming that below. If you are new to the category, our explainer on what a residential proxy is covers the mechanics.
ISP / static residential proxies. Residential-looking but stable and fast. Their niche with Puppeteer is authenticated crawling: any flow that logs in and has to stay logged in, where mid-session rotation would break the session.
Mobile proxies. IPs from cellular carriers. Because a carrier shares one IP across many real subscribers, blocking it risks blocking innocent people, so sites rarely do. That makes mobile the heavyweight option for the most aggressively defended targets, at the highest price.
| Target | Proxy type | Why |
|---|---|---|
| Local testing, undefended sites | Datacenter | Fast and cheap, no reputation check to fail |
| Major retail, search, social | Rotating residential | Reads as a real home user, survives reputation checks |
| Login-gated crawls | ISP / static residential | Session persists, IP stays stable and fast |
| The most bot-hostile targets | Mobile | Carrier-shared IP makes blocks rare |
The rule that saves money: use the cheapest tier the target tolerates, and escalate only when block rates prove you have to.
Free versus paid: the honest reality for Puppeteer
Free proxies are worse for Puppeteer than for almost any other tool, for a structural reason. Most free proxies are datacenter IPs that die within minutes, and only a small fraction are alive at any moment. A raw HTTP request that hits a dead proxy just retries. A Puppeteer page load that loses its proxy halfway through renders broken: half the subresources fail, scripts throw, and your extraction reads garbage. Free proxy latency also blows past browser navigation timeouts, and because these IPs are already flagged, defended targets block them on contact.
Where free proxies genuinely help is learning the wiring and hitting undefended targets. Our free proxy list re-checks and refreshes every few minutes, spans 100+ countries, and covers HTTP, HTTPS, SOCKS4 and SOCKS5, which is plenty for wiring up --proxy-server, confirming your exit IP changes, and testing a scraper against a site that does not fight back. For a production Puppeteer run against anything defended, it is the wrong tool, and no amount of retry logic changes that.
Paid residential is the step up, and our pay-as-you-go residential proxies start at $0.99/GB with no KYC. The one number to watch with Puppeteer is bandwidth: a browser downloads images, fonts and video that a data scraper never needs, and on per-GB billing that adds up. The next sections fix both halves, the wiring and the bandwidth.
How to set a proxy in Puppeteer
Set the proxy when you launch the browser, through Chrome's --proxy-server flag. It applies to the whole browser instance, every page and every request.
const puppeteer = require('puppeteer');
const browser = await puppeteer.launch({
headless: 'new',
args: ['--proxy-server=http://203.0.113.10:8080'],
});
const page = await browser.newPage();
await page.goto('https://httpbin.org/ip'); // shows the exit IP the site sees
console.log(await page.content());
await browser.close();
For a SOCKS5 proxy, change the scheme:
args: ['--proxy-server=socks5://198.51.100.42:1080']
The authentication wall
Here is where nearly everyone gets stuck. Chrome's --proxy-server flag does not accept a username and password in the URL. Write http://user:pass@host:port and Chrome throws the credentials away, then the proxy rejects you with 407 Proxy Authentication Required. Puppeteer has a first-party fix that Selenium lacks: page.authenticate().
const page = await browser.newPage();
await page.authenticate({ username: 'user', password: 'pass' });
await page.goto('https://httpbin.org/ip');
The credentials live in page.authenticate(), never in the launch flag, so the browser answers the 407 challenge on its own and the request goes through. Call it on each page before the first navigation. The alternative, if your provider supports it, is IP allowlisting: register the IP of the machine running Puppeteer with your provider, and the proxy trusts you by address with no credentials in code at all. On a server with a stable IP that is the cleanest option.
Rotation: how many IPs, sticky versus rotating
A --proxy-server value is read once at launch and cannot change on a running browser. So rotation in Puppeteer takes one of three shapes.
A new browser per proxy. Launch, use one identity, close, move on. Clean, because closing the browser also drops the cookies and storage from the last run, so the new IP is not instantly tied to the old session. Heavy if you do it thousands of times, since each launch spins up a full Chrome.
A new context per proxy. Recent Puppeteer lets you set a proxy per browser context, so one Chrome process can run several identities at once:
const context = await browser.createBrowserContext({
proxyServer: 'http://203.0.113.11:8080',
});
const page = await context.newPage();
await page.authenticate({ username: 'user', password: 'pass' });
// ... work for this identity ...
await context.close();
Each context is isolated, with its own cookies and cache, which is exactly what you want when running parallel identities.
One rotating gateway. The simplest and usually the best: point Puppeteer at a single rotating residential gateway, one host and port, and let the pool hand you a fresh exit upstream. Your launch code stays a plain one-proxy script and the rotation happens outside it.
How many IPs you need comes from request rate and per-IP limits, not a round number. The Puppeteer-specific wrinkle is that each browser or context is one coherent identity, so you want sticky behavior inside a task and rotation between tasks. A multi-step flow (log in, paginate, add to cart) should hold one IP for the whole flow, because an IP that jumps countries mid-session describes a bot in one sentence. Stateless page grabs can rotate freely. The full prevention checklist lives in avoiding IP bans while scraping.
Cut your residential bandwidth
This is the tip that pays for itself on per-GB billing. A browser downloads images, fonts, media and stylesheets that pure data extraction never uses. Puppeteer can abort those requests before they cost you anything, through request interception:
await page.setRequestInterception(true);
page.on('request', (req) => {
const type = req.resourceType();
if (['image', 'media', 'font'].includes(type)) {
req.abort();
} else {
req.continue();
}
});
On an image-heavy page this can cut bandwidth by a large margin, which on residential is real money. Two cautions. First, page.authenticate() also uses request interception under the hood, so if you combine both, test that they coexist on your Puppeteer version. Second, do not strip resources on a site whose defenses check that a real browser loaded them, or when you need the page to render for a screenshot. For headless data extraction against ordinary targets, it is close to free savings.
A proxy fixes the IP, not the fingerprint
Worth being blunt here, because it is where Puppeteer scrapers lose the most time. A proxy changes the address a site sees. It does nothing about the many other signals that give automation away. Puppeteer-driven Chrome sets navigator.webdriver to true, runs over a DevTools Protocol connection that leaves detectable artifacts, and in headless mode exposes tells a normal window does not. A serious anti-bot system reads all of that no matter how clean your exit IP is.
The good news for Puppeteer specifically is that a real browser wins the hardest layer for free: the TLS and HTTP/2 handshake genuinely is Chrome's, because it genuinely is Chrome, so the fingerprinting that catches raw HTTP libraries does not fire the same way. What remains is the JavaScript-level automation flags and your behavior. The common patch is puppeteer-extra with its stealth plugin, which hides many of the tells, and the newer headless mode leaks less than the old one. Honest caveat: stealth is a cat-and-mouse patch, not an invisibility cloak, and no proxy and no plugin makes a bot invisible on a site that truly cares. We wrote the full breakdown from the defender's seat in how websites detect proxies. Set expectations accordingly: a real browser on a clean residential IP, paced like a human, gets you a long way, and it is not magic.
Where to point Puppeteer
Start free to learn the moving parts. Wire --proxy-server and page.authenticate() against our free proxy list, confirm your exit IP actually changes, and prove your scraper logic on a target that does not fight back. Before you trust any proxy in a run, drop the exit into our proxy checker to see its real IP, anonymity grade and speed, so you know the address Puppeteer will wear is clean before a target does.
When free stops scaling, which against any defended target it will, move to pay-as-you-go residential at $0.99/GB, no KYC, with a balance that does not expire so a paused project does not burn prepaid credit. Point Puppeteer at one rotating gateway, block the subresources you do not need, pace like a person, and the proxy side of Puppeteer stops being the hard part. That frees you to spend your time on the automation you actually set out to build.
Frequently asked questions
How do I set a proxy in Puppeteer?
Set it at launch through Chrome's --proxy-server flag: pass args: ['--proxy-server=http://IP:PORT'] to puppeteer.launch. The proxy then applies to the whole browser instance, every page and every request. For a SOCKS5 proxy, change the scheme to socks5://. If the proxy needs a username and password, call page.authenticate({ username, password }) on each page before the first navigation, because the flag itself does not accept credentials.
Why does my proxy username and password not work in Puppeteer?
Because Chrome's --proxy-server flag ignores credentials in the URL. If you write http://user:pass@IP:PORT, Chrome drops the user:pass part and the proxy rejects you with 407 Proxy Authentication Required. The fix is Puppeteer's own page.authenticate({ username, password }) method, which answers the 407 challenge for you, or IP allowlisting, where you register your machine's IP with the provider and send no credentials in code at all.
What proxy type is best for Puppeteer?
Rotating residential for defended targets like major retail, search and social, because a real browser on a real home IP is a convincing pair. Datacenter proxies are fine and much cheaper for local testing and sites that do not check IP reputation. ISP or static residential fit login-gated crawls where the session must persist, and mobile proxies are the heavyweight option for the most bot-hostile targets. Use the cheapest tier a target tolerates and escalate only when block rates force it.
Can I use a different proxy per page or rotate within one browser?
A --proxy-server value is fixed at launch and cannot change on a running browser. To rotate, use one of three patterns: launch a new browser per proxy, create a new browser context per proxy with createBrowserContext({ proxyServer }) so one Chrome runs several identities at once, or point Puppeteer at a single rotating residential gateway and let the pool change the exit IP for you upstream. The gateway is usually the simplest.
Does a proxy stop Puppeteer from being detected?
No. A proxy fixes IP reputation only. Puppeteer-driven Chrome still sets navigator.webdriver to true, runs over a DevTools Protocol connection that leaves artifacts, and in headless mode exposes tells a normal window does not. To lower detection you also need a stealth patch like puppeteer-extra, the newer headless mode, human-like pacing and no DNS or WebRTC leaks. A clean residential IP is necessary, not sufficient.