Back

How to Use Rotating Proxies With Puppeteer in 2026

How to Use Rotating Proxies With Puppeteer in 2026

Puppeteer drives a real headless Chrome instance, handles JavaScript rendering, manages sessions, and automates browser interactions that lightweight HTTP clients can't touch.

The catch is scale. Point it at a real target from a single IP and rate limits kick in fast. Rotating residential proxies fix this by cycling through a pool of real residential IPs, so each session looks like a distinct organic user.

This guide is for Node.js developers who want a reliable, production-ready proxy rotation setup with Puppeteer.

Key Takeaways

  • Puppeteer sets its proxy at browser launch via the --proxy-server Chrome flag, which applies to every page in that instance.
  • Chrome ignores credentials in the proxy URL, so authentication needs a separate page.authenticate() call before the first navigation – or the proxy-chain library, which is cleaner.
  • Rotating to a fresh IP means launching a new browser per proxy, or pointing proxy-chain at a rotating gateway that handles rotation server-side.
  • Residential proxies are the right choice for most Puppeteer scraping, since their IPs are assigned by real ISPs.
  • Puppeteer suits large-scale automation, but full browser instances are expensive – a lightweight HTTP client is faster and cheaper for simple targets.

Tools you need before using rotating proxies with Puppeteer

Puppeteer is the Node.js library that controls headless Chrome. It bundles a compatible Chromium binary, so there's no separate browser setup.

For proxy authentication, the proxy-chain package (from the apify/proxy-chain project) is the most reliable option. Puppeteer's --proxy-server flag sets the proxy at launch, but Chrome ignores credentials embedded in the proxy URL. proxy-chain solves this by running a local proxy server that holds your credentials and forwards traffic upstream, which removes the need to call page.authenticate() on every page.

To reduce headless detection, puppeteer-extra combined with puppeteer-extra-plugin-stealth is the standard open-source approach. The stealth plugin patches fingerprinting vectors that Chromium exposes by default, including navigator.webdriver, missing browser plugins, and inconsistent WebGL values.

Finally, you need a rotating residential proxy provider with a gateway endpoint that cycles IPs automatically.

If you're starting a new project and have no Chrome-specific requirement, Playwright is a strong alternative with built-in per-context proxy support. For existing Puppeteer codebases, there's no compelling reason to migrate.

How to use rotating proxies with Puppeteer (step-by-step)

Step 1: Install Puppeteer and dependencies

Initialize a Node.js project and install the core stack: Puppeteer, proxy-chain for authentication, and puppeteer-extra with the stealth plugin.

bash

npm install puppeteer proxy-chain puppeteer-extra puppeteer-extra-plugin-stealth

That gives you Puppeteer with its bundled Chromium binary, the proxy-chain library, and the puppeteer-extra plugin system with stealth evasions.

Step 2: Set a proxy at browser launch

Puppeteer exposes Chrome's --proxy-server flag through the args option in puppeteer.launch(). This sets the proxy for the entire browser instance, so every page routes through the same endpoint.

javascript

const puppeteer = require('puppeteer-extra');

const StealthPlugin = require('puppeteer-extra-plugin-stealth');

puppeteer.use(StealthPlugin());

(async () => {

  const browser = await puppeteer.launch({

    headless: true,

    args: [`--proxy-server=http://${process.env.PROXY_HOST}:${process.env.PROXY_PORT}`]

  });

  const page = await browser.newPage();

  await page.goto('https://httpbin.org/ip', { waitUntil: 'domcontentloaded' });

  console.log(await page.evaluate(() => document.body.innerText));

  await browser.close();

})();

If you're running in Docker or CI, add '--no-sandbox' and '--disable-dev-shm-usage' to the args array. Missing these is the most common cause of a launch failure that looks like a proxy problem.

Step 3: Authenticate the proxy with page.authenticate()

Most residential proxy providers require username and password authentication. Chrome ignores credentials in the proxy URL, so you call page.authenticate() on each new page – and it has to javascript

const puppeteer = require('puppeteer-extra');

const StealthPlugin = require('puppeteer-extra-plugin-stealth');

puppeteer.use(StealthPlugin());

(async () => {

  const browser = await puppeteer.launch({

    headless: true,

    args: [`--proxy-server=http://${process.env.PROXY_HOST}:${process.env.PROXY_PORT}`]

  });

  const page = await browser.newPage();

  // Authenticate before any navigation

  await page.authenticate({

    username: process.env.PROXY_USER,

    password: process.env.PROXY_PASS

  });

  await page.goto('https://httpbin.org/ip', { waitUntil: 'domcontentloaded' });

  console.log(await page.evaluate(() => document.body.innerText));

  await browser.close();

})();

Get your credentials from the GoProxies dashboard and drop them straight into your environment.

Step 4: Use proxy-chain for cleaner authentication

The proxy-chain library wraps your authenticated proxy URL into a local endpoint, so Puppeteer connects locally and credentials are injected transparently. No more page.authenticate() on every page.

anonymizeProxy takes your full proxy URL and returns a local endpoint to pass to --proxy-server:

javascript

const puppeteer = require('puppeteer-extra');

const StealthPlugin = require('puppeteer-extra-plugin-stealth');

const proxyChain = require('proxy-chain');

puppeteer.use(StealthPlugin());

(async () => {

  const originalProxy = `http://${process.env.PROXY_USER}:${process.env.PROXY_PASS}@${process.env.PROXY_HOST}:${process.env.PROXY_PORT}`;

  const anonymizedProxy = await proxyChain.anonymizeProxy(originalProxy);

  const browser = await puppeteer.launch({

    headless: true,

    args: [`--proxy-server=${anonymizedProxy}`]

  });

  const page = await browser.newPage();

  await page.goto('https://httpbin.org/ip', { waitUntil: 'domcontentloaded' });

  console.log(await page.evaluate(() => document.body.innerText));

  await browser.close();

  await proxyChain.closeAnonymizedProxy(anonymizedProxy, true);

})();

Always call closeAnonymizedProxy after closing the browser, or you'll leak local ports across a long run.

Step 5: Rotate per browser session

Because --proxy-server is fixed for the lifetime of a browser instance, rotating to a fresh IP means launching a new browser per session. The pattern below cycles through a proxy list, spawning a fresh browser for each URL and closing it when done:

javascript

const puppeteer = require('puppeteer-extra');

const StealthPlugin = require('puppeteer-extra-plugin-stealth');

const proxyChain = require('proxy-chain');

puppeteer.use(StealthPlugin());

const proxies = [

  `http://${process.env.PROXY_USER}:${process.env.PROXY_PASS}@HOST1:PORT`,

  `http://${process.env.PROXY_USER}:${process.env.PROXY_PASS}@HOST2:PORT`,

  `http://${process.env.PROXY_USER}:${process.env.PROXY_PASS}@HOST3:PORT`

];

const urls = [

  'https://example.com/page-1',

  'https://example.com/page-2',

  'https://example.com/page-3'

];

async function scrapeWithProxy(url, proxyUrl) {

  const anonymizedProxy = await proxyChain.anonymizeProxy(proxyUrl);

  const browser = await puppeteer.launch({

    headless: true,

    args: [`--proxy-server=${anonymizedProxy}`]

  });

  try {

    const page = await browser.newPage();

    await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 });

    const content = await page.content();

    console.log(`Scraped ${url}: ${content.length} bytes`);

  } finally {

    await browser.close();

    await proxyChain.closeAnonymizedProxy(anonymizedProxy, true);

  }

}

(async () => {

  for (let i = 0; i < urls.length; i++) {

    const proxy = proxies[i % proxies.length];

    await scrapeWithProxy(urls[i], proxy);

  }

})();

If you're using a rotating residential gateway instead of a static list, a single endpoint is enough. The provider assigns a fresh IP on each new connection, so launching a new browser per URL achieves rotation on its own.

Step 6: Verify your proxy is working

Before hitting real targets, confirm the proxy is returning a residential IP. A request to an IP-check endpoint returns your exit IP as plain JSON:

javascript

const puppeteer = require('puppeteer-extra');

const StealthPlugin = require('puppeteer-extra-plugin-stealth');

puppeteer.use(StealthPlugin());

(async () => {

  const browser = await puppeteer.launch({

    headless: true,

    args: [`--proxy-server=http://${process.env.PROXY_HOST}:${process.env.PROXY_PORT}`]

  });

  const page = await browser.newPage();

  await page.authenticate({

    username: process.env.PROXY_USER,

    password: process.env.PROXY_PASS

  });

  await page.goto('https://httpbin.org/ip', { waitUntil: 'domcontentloaded' });

  const result = await page.evaluate(() => JSON.parse(document.body.innerText));

  console.log('Exit IP:', result.origin);

  await browser.close();

})();

If the returned IP matches your own machine, authentication failed. Check that page.authenticate() was called before page.goto() and that your credentials are correct.

How to avoid being blocked when using Puppeteer with proxies

Use high-quality rotating residential proxies

Proxy quality is the single biggest factor in scraping success. Datacenter IPs get flagged more often on protected targets because their ranges are well known and their fraud scores tend to run high. Residential IPs assigned by real ISPs to real households are far harder for anti-bot systems to single out.

GoProxies offers 30 million residential IPs across 200+ locations with 99.99% uptime and pay-as-you-go pricing. Check the rotating residential proxy plans to find the right fit for your volume. [CONFIRM: pool size, location count, uptime figure]

Manage sessions and cookies carefully

Some targets use session continuity as a detection signal. Rotating to a new IP mid-session on a site that expects the same IP can trigger a block. Use sticky sessions for workflows that need state across pages – logging in and navigating a funnel, for instance – and reserve rotating IPs for independent, stateless requests.

Apply the stealth plugin

Headless Chrome exposes properties that headful browsers don't: navigator.webdriver is set to true, plugin lists are empty, and certain WebGL and canvas fingerprints differ from real browsers. puppeteer-extra-plugin-stealth patches these automatically and should be applied on every project.

Randomize user agents, headers, and delays

Rotating IPs without varying anything else still leaves a fingerprint. Set a realistic user agent with page.setUserAgent(), match the Accept-Language header to the proxy's geographic location, and add randomized delays between requests. Uniform timing is one of the clearest bot signals a site can read.

Implement retries and error handling

Wrap every page.goto() in a try-catch. On a timeout or connection error, retry with the next proxy rather than letting the script crash. Log failures so you can see which proxies are underperforming.

Parallelize with browser pools

A browser pool launches a fixed number of concurrent instances, each with its own proxy, and distributes URLs across them. Keep concurrency moderate: too many Chrome instances in parallel will exhaust server memory long before anti-bot systems become your bottleneck.

Main challenges when using Puppeteer with rotating proxies

Puppeteer is detectable by default. Chromium's headless mode leaves signals that anti-bot systems actively check for, and adding a proxy only covers the IP. Everything else needs deliberate configuration.

Headless browser detection

Sites running fingerprinting checks look for properties that differ between headless Chromium and a real browser: navigator.webdriver set to true, an empty plugins array, inconsistent screen dimensions. These persist regardless of which proxy you use. The stealth plugin patches most of them, but complete evasion isn't guaranteed on the most aggressively protected targets.

CAPTCHA triggers

A CAPTCHA on the first request usually traces back to the browser rather than the proxy, which is the opposite of what most people assume. Check in this order: whether the stealth plugin is actually applied and navigator.webdriver is no longer exposed, then whether your Accept-Language header matches the proxy's geography, then the exit IP's fraud score.

The reason for that order is that Puppeteer's fingerprint is the loudest signal in the stack. An IP with a perfect reputation won't save a browser that announces itself as automated.

IP reputation and rotation rate

Even residential IPs can carry poor reputation if they've been used heavily for scraping. If you're blocked on the first request, check the exit IP's fraud score before concluding that the target blocks all residential traffic.

Rotating too quickly on session-aware targets can also cause blocks, since no organic user switches IPs every few seconds.

Authentication errors and page.authenticate() timing

The most common setup mistake is calling page.authenticate() after page.goto(). Chrome sends the proxy authentication challenge during the connection handshake, so credentials have to arrive before navigation starts. proxy-chain's anonymizeProxy eliminates the timing issue entirely.

Common errors when using Puppeteer with proxies and how to fix them

  • ERR_TUNNEL_CONNECTION_FAILED: Puppeteer reached the proxy server but the proxy couldn't establish a tunnel to the target. Usually an incorrect host or port, or a proxy that's offline. Also check you're using HTTP rather than SOCKS5 for authenticated residential proxies, since Chromium doesn't support username and password authentication over SOCKS5.
  • 407 Proxy Authentication Required: The proxy rejected your credentials. If you're using the --proxy-server flag directly, it usually means page.authenticate() wasn't called, was called after page.goto(), or your credentials contain special characters that weren't URL-encoded. Switching to proxy-chain eliminates this whole class of error.
  • net::ERR_PROXY_CONNECTION_FAILED: Puppeteer couldn't reach the proxy server at all. Check the host and port, confirm your network allows outbound connections on that port, and verify the proxy service is online. In containerized environments, check whether a firewall rule is blocking outbound proxy traffic.
  • A CAPTCHA or block page on the first request: Work through the three causes in the order given above: stealth plugin applied, Accept-Language matched to geo, exit IP fraud score. If all three are addressed and blocks persist, the target is fingerprinting at a level the stealth plugin doesn't currently cover.

What you can extract using Puppeteer with rotating proxies

Puppeteer's advantage over lightweight HTTP clients is JavaScript execution. Single-page applications load their content through client-side API calls after the initial HTML arrives. A standard HTTP request captures that empty shell; Puppeteer waits for the full DOM and captures what a user would actually see. Dynamic product listings, infinite scroll feeds, and client-side search results all fall into this category.

Geo-restricted content is another strong fit. Routing traffic through a residential IP in a specific country, state, or city makes localized pricing, region-specific search results, and location-gated content reachable from any server. Authenticated content, paginated flows, multi-step form submissions, and pages that need a scroll before revealing data are all natural fits too.

GoProxies' 30 million residential IPs across 200+ locations give you the geographic coverage to collect this at scale. Start scraping with GoProxies and reach any target anywhere.

Is it legal to use Puppeteer for web scraping?

Puppeteer is a browser automation library and is legally neutral on its own. Legality depends on what you collect and how.

Publicly accessible data – content any visitor can see without logging in – is generally treated differently from account-restricted data. Collecting public data for research, price monitoring, or competitive analysis is widely practiced and broadly accepted. Accessing data behind authentication without authorization is a different matter and carries significantly higher legal risk.

Most sites' terms of service prohibit automated access in some form. Violating a ToS isn't a criminal act in most jurisdictions, but it can result in account termination and civil claims. Read the ToS of any site you plan to scrape.

Ethical practice also means respecting rate limits, honoring robots.txt, and avoiding unnecessary server load. Those habits reduce your legal exposure and make your scraping more sustainable. If your use case is commercial and the target is high-profile, get qualified legal advice before running at scale.

Using a lightweight HTTP client instead of Puppeteer

Puppeteer launches a full Chrome instance per browser session, which carries a real CPU and memory cost. If the target delivers its content in the initial HTML response without requiring JavaScript, a lightweight HTTP client is faster, cheaper, and simpler.

The call is straightforward: use Puppeteer when the target requires JavaScript rendering, user interaction, or browser-level session management. Use a lightweight HTTP client when the raw HTML response has what you need. Many stacks use both, with the HTTP client handling the bulk of requests and Puppeteer reserved for pages that genuinely need a full browser.

Can you use rotating proxies with Puppeteer for free?

Technically yes. Free proxy lists exist and can be passed to --proxy-server. In practice they create more problems than they solve.

Free proxies are shared across many users, so they accumulate blocks and high fraud scores fast. The IP that works on your first request may be blocklisted by the second. They also lack authentication, offer no uptime guarantees, and go offline without notice. Keeping a working pool of free proxies takes constant maintenance that eats the time you'd spend on actual scraping logic.

For anything beyond initial testing, a reliable rotating residential plan pays for itself in reliability alone. GoProxies offers pay-as-you-go pricing with no minimums or contracts. See the rotating residential proxy pricing to get started.

Conclusion

Using rotating proxies with Puppeteer is straightforward once you understand the core constraint: the proxy is fixed at browser launch, so rotation happens at the browser level.

The setup comes down to three things working together: the --proxy-server flag or proxy-chain for authentication, the stealth plugin for fingerprint evasion, and a quality rotating residential gateway for IP diversity.

The difficulty is moderate for developers comfortable with Node.js and async/await. Use proxy-chain to handle credentials cleanly, apply the stealth plugin on every project, match Accept-Language headers to the proxy's geography, and verify your exit IP before running at scale.

Gintarė specializes in content related to proxies, web scraping, data collection, and internet infrastructure. With a solid background in information technology, networking, and cybersecurity, she understands both the technical and practical aspects of large-scale data acquisition.

Turn data insights into growth with GoProxies
Millions of IPs are just a click away!

What’s a Rich Text element?

The rich text element allows you to create and format headings, paragraphs, blockquotes, images, and video all in one place instead of having to add and format them individually. Just double-click and easily create content.

Static and dynamic content editing

A rich text element can be used with static or dynamic content. For static content, just drop it into any page and begin editing. For dynamic content, add a rich text field to any collection and then connect a rich text element to that field in the settings panel. Voila!

How to customize formatting for each rich text

Headings, paragraphs, blockquotes, figures, images, and figure captions can all be styled after a class is added to the rich text element using the "When inside of" nested selector system.

FAQ

What Are Rotating Residential Proxies?
Rotating Residential Proxies offer you the best solution for scaling your scraping without getting blocked.

Rotating proxies provide a different IP each time you make a request. With this automated rotation of IPs, you get unlimited scraping without any detection. It provides an extra layer of anonymity and security for higher-demand web scraping needs.

IP addresses change automatically, so after the initial set up you’re ready to scrape as long and much as you need. IPs may shift after a few hours, a few minutes or after each session depending on your configuration. We do this by pulling legitimate residential IPs from our pool.
Why Do You Need Rotating Residential Proxies?
There are a number of use cases for rotating residential proxies. One of the most common ones is bypassing access limitations.

Some websites have specific measures in place to block IP access after a certain number of requests over an extended period of time.

This limits your activity and hinders scalability. With rotating residential IP addresses, it's almost impossible for websites to detect that you are the same user, so you can continue scraping with ease.
When to Use Static Residential Proxies Instead?
There are particular cases where static residential proxies may be more useful for your needs, such as accessing services that require logins.

Rotating IPs might lead to sites not functioning well if they are more optimised for regular use from a single IP.

Learn if our static residential proxies are a better fit for your needs.
Can I choose the IP location by city?
Yes. GoProxies has IPs spread across almost every country and city worldwide.
Can I choose the IP location by country state?
Yes. GoProxies has IPs spread across X countries with localised IPs in every state.

Does Puppeteer support SOCKS5 proxy authentication?

No. Puppeteer doesn't support username and password authentication over SOCKS5. Chromium handles SOCKS5 connections but drops credentials passed in the URL. For authenticated residential proxies, use the HTTP or HTTPS endpoint your provider supplies and authenticate via page.authenticate() or proxy-chain.

What's the difference between rotating and sticky sessions in Puppeteer?

Rotating sessions assign a fresh IP on each new browser instance, which suits stateless requests where each page load is independent. Sticky sessions hold the same IP for a set duration, which is necessary for workflows that need session continuity, such as logging in and navigating a multi-step flow. Most providers let you switch between modes via a parameter in the proxy username.

Do I need puppeteer-extra to use proxies with Puppeteer?

No. The --proxy-server flag and page.authenticate() work with standard Puppeteer. puppeteer-extra is only needed for the stealth plugin and other detection evasion features, which are strongly recommended for targets that actively fingerprint headless browsers.

How many concurrent Puppeteer browser instances can I run?

Your limit is available memory. Each Chrome instance typically consumes a few hundred megabytes depending on the pages it loads, so a small server will hit memory pressure sooner than you'd expect. Start conservative, measure actual usage on your own hardware, and scale from there.