Developer Tutorial
15 min read

Puppeteer Proxy Setup: Complete Guide with Mobile Proxies (2026)

Three methods for routing Puppeteer through proxies—launch args, proxy-chain, and per-page rotation—plus stealth patches, common error fixes, and why mobile proxies deliver 92-99% success rates on protected sites.

3
Proxy Methods
5
Errors Solved
92%+
Mobile IP Success
8
Code Examples

Puppeteer in 2026 — Still Relevant?

Puppeteer remains one of the most widely used browser automation libraries in the Node.js ecosystem, even as Playwright has gained significant ground. With roughly 90,300 GitHub stars and combined npm weekly downloads of 8.2 million (puppeteer) plus 10.9 million (puppeteer-core), Puppeteer is far from dead.

That said, Playwright overtook Puppeteer in aggregate npm downloads during 2024, now sitting at approximately 33.3 million weekly downloads. Playwright offers multi-browser support (Chrome, Firefox, WebKit), built-in proxy configuration, and a richer API surface. For new projects with no Chrome-only requirement, Playwright is often the better choice.

But if you have an existing Puppeteer codebase, are targeting Chrome-only automation, or are working with tools built on Puppeteer (like Crawlee or Apify), there is no compelling reason to migrate. This guide covers everything you need to integrate proxies into Puppeteer effectively.

90.3K

GitHub Stars

19.1M

npm Downloads/Week (combined)

33.3M

Playwright npm Downloads/Week

puppeteer-extra-plugin-stealth is unmaintained

Version 2.11.2 was last published approximately 3 years ago. The plugin is no longer actively maintained and does not cover newer fingerprinting vectors deployed by Cloudflare, DataDome, and Akamai in 2025-2026. See Section 6 for manual stealth patches that work today.

Sources: npm registry, HackerNoon Playwright vs Puppeteer 2026

Method 1: Launch Args (Simplest)

The most straightforward way to route Puppeteer through a proxy is the --proxy-server Chrome flag, passed via args in puppeteer.launch(). This sets the proxy at the browser level—all pages opened in that browser instance use the same proxy.

javascript
const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({
    args: [
      '--proxy-server=http://138.201.158.43:8522',
      '--no-sandbox',
    ],
    headless: 'new',
  });

  const page = await browser.newPage();

  // Authenticate with proxy credentials
  await page.authenticate({
    username: 'user_abc123',
    password: 'pass456',
  });

  await page.goto('https://httpbin.org/ip');
  const content = await page.textContent('body');
  console.log('Your IP:', content);

  await browser.close();
})();

Chrome does NOT support credentials in the proxy URL

Unlike curl or Python requests, Chrome ignores user:pass@host:port in the --proxy-server flag. You must use page.authenticate() for proxy credentials, and it must be called before any page.goto() call.

This method works well for single-proxy setups where all pages share the same proxy. The proxy applies to every request the browser makes, including images, stylesheets, and XHR calls. For per-page proxy rotation, see Method 3 below.

Method 2: proxy-chain Package (SOCKS5 Support)

The proxy-chain npm package (by the Apify team) solves two problems that Chrome cannot handle natively: SOCKS5 proxy authentication and credential-embedded proxy URLs. It works by spinning up a local proxy server that forwards traffic to your authenticated upstream proxy.

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

(async () => {
  // proxy-chain creates a local proxy that forwards to the authenticated proxy
  const proxyUrl = await proxyChain.anonymizeProxy(
    'http://user_abc123:pass456@138.201.158.43:8522'
  );

  const browser = await puppeteer.launch({
    args: [`--proxy-server=${proxyUrl}`],
    headless: 'new',
  });

  const page = await browser.newPage();
  await page.goto('https://httpbin.org/ip');
  const content = await page.textContent('body');
  console.log('Your IP:', content);

  await browser.close();
  await proxyChain.closeAnonymizedProxy(proxyUrl);
})();

Install with npm install proxy-chain. The package creates an anonymous local proxy (typically on 127.0.0.1 with a random port) that handles authentication with the upstream proxy on your behalf.

When to use proxy-chain

  • Fixes ERR_NO_SUPPORTED_PROXIES for SOCKS5h proxies
  • Enables SOCKS5 proxy authentication (which Puppeteer does not natively support)
  • Allows credential-embedded proxy URLs without page.authenticate()

Always call proxyChain.closeAnonymizedProxy(proxyUrl) after closing the browser to clean up the local proxy server and free the port.

Method 3: Per-Page Proxy Rotation

Because --proxy-server is a browser-level flag, you cannot change the proxy per-page within the same browser instance. The workaround is to launch a new browser for each proxy. This costs more memory but gives you true IP rotation per request—essential for scraping multiple URLs across different sites.

javascript
const puppeteer = require('puppeteer');

const PROXIES = [
  { server: 'http://138.201.158.43:8522', user: 'user1', pass: 'pass1' },
  { server: 'http://138.201.158.43:8523', user: 'user2', pass: 'pass2' },
  { server: 'http://62.30.207.124:8002', user: 'user3', pass: 'pass3' },
];

async function scrapeWithRotation(urls) {
  const results = [];

  for (const url of urls) {
    const proxy = PROXIES[Math.floor(Math.random() * PROXIES.length)];

    const browser = await puppeteer.launch({
      args: [`--proxy-server=${proxy.server}`],
      headless: 'new',
    });

    const page = await browser.newPage();
    await page.authenticate({ username: proxy.user, password: proxy.pass });

    try {
      await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 });
      const title = await page.title();
      results.push({ url, title, proxy: proxy.server });
    } catch (err) {
      console.error(`Failed ${url}: ${err.message}`);
    }

    await browser.close();
    await new Promise(r => setTimeout(r, 2000 + Math.random() * 3000));
  }

  return results;
}

The random delay between 2 and 5 seconds (2000 + Math.random() * 3000) prevents request patterns that anti-bot systems detect as automated. Each URL gets a randomly selected proxy from the pool, and the browser is fully closed between requests so no cookies or session state leak between different sites.

For high-volume workloads, consider running multiple instances in parallel with Promise.allSettled() and a concurrency limiter like p-limit to control how many browsers are open simultaneously.

Common Errors & Fixes

These are the five most common proxy-related errors in Puppeteer, sourced from GitHub issues #2472 , #4406 , #2234 and Stack Overflow.

ErrorCauseFix
ERR_PROXY_CONNECTION_FAILEDProxy server is unreachable or offlineVerify the proxy address and port are correct. Ensure your firewall allows outbound connections on the proxy port.
ERR_NO_SUPPORTED_PROXIESSOCKS5h with credentials in the URLUse the proxy-chain package to create a local forwarding proxy. Chrome cannot handle SOCKS5h auth natively.
ERR_TUNNEL_CONNECTION_FAILEDHTTPS CONNECT tunneling failureVerify the proxy supports the CONNECT method for HTTPS. Some free proxies only support HTTP.
HTTP 407Proxy authentication failedCheck credentials in page.authenticate(). Ensure username and password are correct and the proxy requires auth.
ERR_INVALID_ARGUMENTMalformed proxy URL in launch argsFormat must be protocol://host:port with no credentials. Use page.authenticate() for username/password.

Debug tip

Launch Puppeteer with headless: false and navigate to chrome://net-internals/#proxy to verify the proxy configuration Chrome is actually using. This page shows the resolved proxy settings and any errors in the proxy chain.

Stealth Setup for Anti-Bot Sites

The puppeteer-extra-plugin-stealth package (v2.11.2) was last published roughly 3 years ago and is no longer actively maintained. Its evasion techniques have not been updated for the latest fingerprinting vectors from Cloudflare Turnstile, DataDome, and Akamai Bot Manager. For 2026, use manual stealth patches instead:

javascript
// puppeteer-extra-plugin-stealth is unmaintained since 2023
// For 2026, use manual stealth patches instead:

const page = await browser.newPage();

// Override webdriver detection
await page.evaluateOnNewDocument(() => {
  Object.defineProperty(navigator, 'webdriver', { get: () => false });

  // Override chrome runtime
  window.chrome = { runtime: {} };

  // Override permissions
  const originalQuery = window.navigator.permissions.query;
  window.navigator.permissions.query = (parameters) =>
    parameters.name === 'notifications'
      ? Promise.resolve({ state: Notification.permission })
      : originalQuery(parameters);
});

// Set realistic viewport
await page.setViewport({ width: 1920, height: 1080 });

These patches address the most common detection vectors: the navigator.webdriver flag that Puppeteer sets to true by default, the missing window.chrome runtime object, and the permissions API inconsistency that reveals headless operation.

Additional stealth headers

javascript
// Set realistic headers to match a real browser session
await page.setExtraHTTPHeaders({
  'Accept-Language': 'en-US,en;q=0.9',
  'Accept-Encoding': 'gzip, deflate, br',
  'sec-ch-ua': '"Chromium";v="124", "Google Chrome";v="124"',
  'sec-ch-ua-mobile': '?0',
  'sec-ch-ua-platform': '"Windows"',
});

// Randomize User-Agent
await page.setUserAgent(
  'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ' +
  'AppleWebKit/537.36 (KHTML, like Gecko) ' +
  'Chrome/124.0.0.0 Safari/537.36'
);

For new projects, consider alternatives

If you are starting fresh and need strong anti-detection, consider Camoufox (a Firefox fork with native fingerprint resistance) or Playwright with playwright-extra. Both offer more actively maintained stealth capabilities than the Puppeteer ecosystem in 2026.

Mobile Proxy Advantage for Puppeteer

Even with perfect stealth patches, your success rate is ultimately determined by the IP address your traffic comes from. Mobile proxies use real 4G/5G carrier IPs that benefit from CGNAT (Carrier-Grade NAT)—a network architecture where thousands of legitimate mobile subscribers share each public IP simultaneously. Anti-bot systems cannot block these IPs without also blocking real mobile users.

Success Rate by Proxy Type on Protected Sites

Datacenter Proxies20-40%
Residential Proxies85-95%
Mobile Proxies (4G/5G)92-99%

Why datacenter proxies fail

Datacenter IPs come from known hosting ranges (AWS, GCP, OVH, Hetzner). Anti-bot systems maintain blocklists of these ranges and flag traffic from them immediately, resulting in 20-40% success rates.

Why mobile proxies succeed

Mobile IPs are assigned by carriers like T-Mobile, Verizon, and Vodafone. Thousands of real users share each IP via CGNAT. Blocking a mobile IP means blocking hundreds of real customers—a risk no website takes.

Combining Puppeteer stealth patches with mobile proxies is the highest-success-rate configuration available in 2026. The proxy handles IP reputation while the stealth patches handle browser fingerprinting—covering both layers of anti-bot detection.

Puppeteer vs Playwright: Quick Comparison

If you are evaluating whether to stick with Puppeteer or migrate to Playwright, here is a direct comparison of the features that matter most for proxy-based scraping:

FeaturePuppeteerPlaywright
npm Downloads8.2M/week33.3M/week
Browser SupportChrome onlyChrome, Firefox, WebKit
Proxy SetupLaunch args + authenticate()Built-in proxy option
Stealth PluginUnmaintained (v2.11.2)playwright-extra
LanguagesJS/TS onlyJS, TS, Python, Java, C#
RecommendationExisting Chrome projectsNew projects

The bottom line: if you already have a Puppeteer codebase that works, keep using it. The proxy setup methods in this guide work reliably. If you are starting a new project, Playwright's built-in proxy support, multi-browser coverage, and larger ecosystem make it the stronger choice. Both work equally well with mobile proxies from PROXIES.SX.

Frequently Asked Questions

How do I set up a proxy in Puppeteer?

Pass --proxy-server=http://host:port in the args array when calling puppeteer.launch(). For authenticated proxies, call page.authenticate({ username, password }) before navigating. Chrome does not support embedding credentials in the proxy URL, so page.authenticate() is required for any proxy that needs a username and password.

Why does Puppeteer show ERR_NO_SUPPORTED_PROXIES with SOCKS5?

Chrome cannot handle SOCKS5h authentication when credentials are embedded in the proxy URL. Install the proxy-chain npm package and use proxyChain.anonymizeProxy() to create a local forwarding proxy that handles authentication. Then point Puppeteer at the local proxy URL. This resolves the error completely.

Is puppeteer-extra-plugin-stealth still maintained in 2026?

No. Version 2.11.2 was last published approximately three years ago and is not actively maintained. Its evasion techniques have not been updated for modern anti-bot systems like Cloudflare Turnstile or DataDome v4. Use manual stealth patches with evaluateOnNewDocument() as shown in Section 6, or consider Playwright or Camoufox for new projects.

Get Mobile Proxies for Puppeteer

Every code example in this guide works out of the box with PROXIES.SX credentials. Start with 1GB free trial bandwidth + 2 ports. Setup takes less than 60 seconds.