وكيل رخيص
التكاملات
Puppeteer proxy integration: the complete developer guide

Puppeteer Proxy Integration

Puppeteer reads one proxy per browser process, so scaling identities means choosing between per-context, per-browser, or gateway-based rotation. This guide covers working code for launch args, authentication, WebRTC leaks, and the proxy type that fits each workload.
احصل على بروكسيات لـ Puppeteer proxy integration: the complete developer guide
Puppeteer proxy integration: the complete developer guide
What is Puppeteer?
Puppeteer is a Node.js library that controls Chrome or Chromium through the DevTools Protocol. It's used for browser automation and scraping at scale. Because it reads only one proxy per browser process, running many identities takes specific rotation patterns.

Key takeaways

  • Puppeteer reads one proxy per browser process via the Chromium flag --proxy-server, so per-IP rotation requires either multiple browser contexts, multiple browser instances, or a local forwarding proxy.
  • Chromium silently drops user:pass@host:port credentials embedded in the proxy URL. Use page.authenticate({ username, password }) for HTTP basic auth proxies, or chain through a local forwarder for HTTPS and SOCKS5 cases.
  • The modern API for per-identity proxying is browser.createBrowserContext({ proxyServer, proxyBypassList }), introduced in Puppeteer 22.0.0 and replacing the deprecated createIncognitoBrowserContext name.
  • The most common failures (407, ERR_NO_SUPPORTED_PROXIES, ERR_TUNNEL_CONNECTION_FAILED, WebRTC IP exposure) come from four root causes: bad scheme, embedded credentials, missing CONNECT auth, and Chromium routing UDP outside the proxy.

Puppeteer request flow through a proxy

Puppeteer drives Chromium over the DevTools Protocol. Every outbound request from the browser exits through the configured proxy.

What Puppeteer does and why proxy choice matters

Puppeteer is a Node.js library that controls Chrome or Chromium through the Chrome DevTools Protocol. It launches a real browser, renders JavaScript, drives forms, captures screenshots, generates PDFs, and runs end-to-end tests. Headless is the default in Puppeteer 22 and later, and headless: false opens a visible window for debugging.

Browser automation produces traffic that looks very different from a plain HTTP client. A single page load can fire fifty or more requests, run third-party scripts, execute fingerprint probes, and hold a TLS session open for the duration. When that traffic all originates from one IP, anti-bot scoring picks up the pattern within a few page loads. That is where proxies enter: they let you spread Puppeteer traffic across many exit IPs, target specific geographies, and keep session quality stable across long-running jobs. Picking the right proxy type, and wiring it correctly, is the difference between a script that runs once and a pipeline that runs daily.

Proxy schemes Chromium accepts

The --proxy-server flag accepts five schemes: http://, https://, socks4://, socks5://, and direct://. The format applies to one proxy that handles all traffic from that browser instance, unless you scope it to a context. Chromium also supports a per-scheme map syntax, useful when you want HTTP and HTTPS requests routed differently:

// per-scheme proxy map: HTTPS uses proxy1, plain HTTP uses a SOCKS4 proxy
const args = ['--proxy-server=https=proxy1.example.com:8443;http=socks4://proxy2.example.com:1080'];

Two flags partner with --proxy-server. --proxy-bypass-list excludes hosts from proxying, and --no-proxy-server disables proxying entirely. The exclusion list is helpful when local services (a local API on 127.0.0.1, a Docker registry, a metadata endpoint) must skip the proxy.

If you want SOCKS5 specifically, SOCKS5 proxies are available across Proxy-Cheap product lines and carry both TCP and UDP. Note that Chromium has no built-in mechanism to send SOCKS5 username and password, so authenticated SOCKS5 should be paired with an IP whitelist or a local forwarder (covered below).

Four canonical Puppeteer proxy integration methods

1. Browser-wide proxy via launch args

This is the simplest configuration. One proxy handles every request from the browser, and you set it once at launch.

import puppeteer from 'puppeteer';
 
const browser = await puppeteer.launch({
  headless: true,
  args: [
    '--proxy-server=http://gateway.example.com:8000',
    '--no-sandbox',
  ],
});
 
const page = await browser.newPage();
await page.goto('https://httpbin.org/ip');
const body = await page.evaluate(() => document.body.innerText);
console.log(body);
 
await browser.close();

Use this when you need one consistent identity per browser process, for example with a single static IP or a server-rotating gateway.

2. Per-context proxy with createBrowserContext

A single browser process can host multiple browser contexts, each with its own cookies, storage, and proxy. This is the modern way to run multiple identities without the cost of launching N browsers.

import puppeteer from 'puppeteer';
 
const browser = await puppeteer.launch({ headless: true });
 
const usContext = await browser.createBrowserContext({
  proxyServer: 'http://us.gateway.example.com:8080',
  proxyBypassList: ['localhost', '127.0.0.1'],
});
 
const deContext = await browser.createBrowserContext({
  proxyServer: 'http://de.gateway.example.com:8080',
});
 
const pageUS = await usContext.newPage();
const pageDE = await deContext.newPage();
 
await pageUS.authenticate({ username: 'user1', password: 'pass1' });
await pageDE.authenticate({ username: 'user2', password: 'pass2' });
 
await Promise.all([
  pageUS.goto('https://httpbin.org/ip'),
  pageDE.goto('https://httpbin.org/ip'),
]);
 
await browser.close();

The API name createBrowserContext replaced createIncognitoBrowserContext in Puppeteer 22.0.0. If you are reading older code or stuck on Puppeteer 21.x, you may see browser.createIncognitoBrowserContext({ proxyServer, proxyBypassList }) with proxyBypassList passed as a semicolon-separated string instead of an array. The semantics are otherwise the same.

This pattern is the foundation for multi-account management workflows, where each account needs its own session jar and its own exit IP without the memory overhead of separate browser processes.

3. Request interception for custom routing

page.setRequestInterception(true) lets you observe, modify, or short-circuit every request the browser issues. It does not itself reroute traffic through a different proxy; the browser still emits the outbound TCP connection. What it does well is header rewriting, resource filtering to save bandwidth, and pairing with an external Node-side fetcher that replays the request through a chosen upstream and returns the response.

import puppeteer from 'puppeteer';
 
const browser = await puppeteer.launch({
  args: ['--proxy-server=http://gateway.example.com:8000'],
});
 
const page = await browser.newPage();
await page.authenticate({ username: 'user', password: 'pass' });
 
await page.setRequestInterception(true);
page.on('request', (req) => {
  if (req.isInterceptResolutionHandled()) return;
  const blocked = ['image', 'media', 'font', 'stylesheet'];
  if (blocked.includes(req.resourceType())) {
    return req.abort();
  }
  return req.continue();
});
 
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await browser.close();

Two cautions. First, page.authenticate() enables interception under the hood, so always call req.isInterceptResolutionHandled() before continue, abort, or respond to avoid the runtime error about a request already being handled. Second, replaying requests from Node strips Chrome’s real TLS fingerprint and substitutes Node’s, which can hurt session quality on targets that fingerprint TLS.

4. Rotating across multiple browser instances

When you need a different exit IP per worker, the most robust pattern is one browser per IP. It is heavier on memory than per-context rotation, but it guarantees process-level isolation, which matters for cookies, service workers, and CDP state.

import puppeteer from 'puppeteer';
 
const proxies = [
  'http://gateway-1.example.com:8080',
  'http://gateway-2.example.com:8080',
  'http://gateway-3.example.com:8080',
];
 
async function fetchWithProxy(url, proxyUrl, credentials) {
  const browser = await puppeteer.launch({
    headless: true,
    args: [`--proxy-server=${proxyUrl}`, '--no-sandbox'],
  });
  try {
    const page = await browser.newPage();
    await page.authenticate(credentials);
    await page.setDefaultNavigationTimeout(60000);
    await page.goto(url, { waitUntil: 'domcontentloaded' });
    return await page.content();
  } finally {
    await browser.close();
  }
}
 
const credentials = { username: 'user', password: 'pass' };
const results = await Promise.all(
  proxies.map((p) => fetchWithProxy('https://httpbin.org/ip', p, credentials))
);
console.log(results.map((html) => html.length));

For high parallelism, wrap this in a worker queue with a hard cap on concurrent browsers (typically four to eight per host, depending on RAM). Recycle each browser after a fixed number of pages to avoid the renderer-process memory drift documented in long-running Puppeteer jobs.

Authenticating proxies the right way

The single most common Puppeteer proxy mistake is putting credentials in the URL. Chromium intentionally strips the userinfo portion of --proxy-server, so http://user:pass@host:port becomes http://host:port before the network stack ever sees it. This is documented behavior in the Chromium network code, not a bug, and it is the source of the majority of 407 reports filed against Puppeteer.

The canonical authentication call is page.authenticate:

const page = await browser.newPage();
await page.authenticate({ username: 'user', password: 'pass' });
await page.goto('https://example.com');

This works cleanly for HTTP basic auth proxies and answers the request-level 407 challenge through the DevTools Fetch domain. It has three real limitations worth knowing in advance.

Limitation one: HTTPS proxies that require CONNECT-level auth. When the proxy is itself reached over HTTPS, the 407 happens during the CONNECT tunnel setup, before Chromium hooks the Fetch domain. page.authenticate never sees the challenge, so you get ERR_TUNNEL_CONNECTION_FAILED. The fix is a local forwarder: run a small Node process on 127.0.0.1 that listens for plain HTTP and forwards each request upstream with the credentials baked in. Then point Puppeteer at the local forwarder.

import puppeteer from 'puppeteer';
import { anonymizeProxy } from 'proxy-chain';
 
const upstream = 'http://user:[email protected]:8443';
const localUrl = await anonymizeProxy(upstream);
 
const browser = await puppeteer.launch({
  args: [`--proxy-server=${localUrl}`],
});

Limitation two: SOCKS5 username and password. Chromium does not implement SOCKS5 auth at all. If you need authenticated SOCKS5, configure an IP whitelist on the proxy account or wrap the SOCKS5 upstream behind a local HTTP forwarder.

Limitation three: conflict with site-level basic auth. page.authenticate sets one credential pair per page. If both your proxy and the target origin require basic auth, you must offload the proxy auth to the local forwarder and reserve page.authenticate for the target.

Rotation strategies and when to use each

Puppeteer offers four practical rotation strategies. Each has a different cost and reliability profile.

The first is gateway rotation, where a provider serves a single endpoint and rotates the underlying IP server-side every request or on a sticky session window. Your code uses one --proxy-server value and never thinks about pools. This is the lowest-friction option and the right default for high-volume scraping, which is why most users start with rotating residential proxies backed by a gateway endpoint.

The second is per-context rotation, using createBrowserContext with a different proxyServer value per context. One browser hosts many identities; each context has isolated cookies and storage. This is ideal for parallel logged-in sessions and multi-profile workflows where the proxy and the session jar must stay paired.

The third is per-browser rotation, launching one browser per IP. It is the heaviest pattern in memory terms but the most isolated. Use it when service workers, cached HTTP responses, or other process-scoped state would otherwise leak between identities.

The fourth is sticky-session rotation, where the gateway holds the same exit IP for a configurable window (typically ten minutes for residential, longer for ISP). Sticky sessions are essential when a workflow spans several requests that the target ties to a session cookie. The static versus rotating proxies explainer walks through the trade-off in more depth.

Puppeteer product fit matrix

Match the Puppeteer workload to the right product line: throughput, session persistence, identity quality, or mobile-property coverage.

Pairing proxies with detection-resistant fingerprints

A clean exit IP alone is not enough. Default headless Chromium exposes the string HeadlessChrome in the user agent, sets navigator.webdriver to true, and ships a handful of CDP-detectable runtime quirks. Anti-bot scoring picks those up before it even looks at the IP.

Three practical pairings turn a working proxy into a stable session. First, run a stealth or fingerprint plugin that patches the obvious leaks (navigator.webdriver, the missing chrome runtime object, plugin and language enumeration, WebGL vendor strings). Second, match the browser locale, timezone, and accept-language headers to the proxy exit-node country. A US residential IP combined with Europe/Vilnius timezone is a strong tell. Third, randomize the viewport and user agent within realistic distributions, not arbitrary values. Setting the user agent to a six-month-old Chrome version on Windows 11 while running new headless Chromium 130 on Linux is itself a fingerprint.

Even with stealth plugins, the IP class matters. High-volume scraping calls for rotating residential or unlimited bandwidth proxies when GB usage is the bottleneck. Persistent account sessions favor static residential proxies or ISP proxies for stable identity. Bulk crawls of public content can run on datacenter proxies at a fraction of the cost. Mobile-property testing and the most challenging consumer targets are a fit for mobile proxies.

Launch options that pair well with proxy use

A few Chromium flags and Puppeteer options come up repeatedly when running proxies in production.

--ignore-certificate-errors is necessary when the proxy presents an intercepting certificate. Combine it with the launch option acceptInsecureCerts: true (which replaced the older ignoreHTTPSErrors field in recent versions). Apply this only to traffic that genuinely needs it; do not leave it on for general browsing.

--disable-blink-features=AutomationControlled removes the automation banner and the navigator.webdriver property surface. It is one of the cheapest fingerprint cleanups available.

WebRTC requires an extra flag, because Chromium routes UDP STUN packets outside HTTP and SOCKS proxies. Without an override, a page can read the host’s real public IP via WebRTC ICE candidates while every other request goes through the proxy. The verified fix:

args: [
  `--proxy-server=${proxyUrl}`,
  '--force-webrtc-ip-handling-policy=disable_non_proxied_udp',
]

For headless behavior, headless: true is the new headless Chrome (the default in Puppeteer 22 and later). headless: 'shell' runs the lighter legacy chrome-headless-shell binary, faster but with reduced parity to real Chrome. headless: false opens a visible window and is the right choice for debugging proxy auth flows visually.

Two timeouts matter when traffic exits through residential or mobile networks. protocolTimeout defaults to 180 seconds and can only be set at launch. defaultNavigationTimeout defaults to 30 seconds and is too short for slow exit nodes. Raise it explicitly with page.setDefaultNavigationTimeout(60000) and prefer waitUntil: 'domcontentloaded' over 'networkidle0', which rarely fires over residential connections.

Proxy-Cheap product fit by Puppeteer workload

Puppeteer workloadRecommended productWhy it fits
High-volume scraping with rotating identitiesRotating residentialPer-request rotation or sticky session via one gateway endpoint, broad country and city targeting
Persistent logged-in sessions and account workStatic residential or ISPOne stable IP per context with residential or ISP trust, paired cleanly with createBrowserContext
Bulk crawls of public documentation, RSS feeds, sitemapsDatacenterHigh throughput, very low cost per request, ideal for unprotected public content
Mobile site and app-property testingMobile (4G or 5G)Carrier-class IPs and mobile network behavior, the closest match to a real handset
Long-running jobs where bandwidth is the cost driverUnlimited bandwidthFlat-rate billing, no per-GB pressure on retry budgets
Tools that need SOCKS5 specificallySOCKS5 across product linesTCP and UDP support, no HTTP header rewriting overhead

For the use-case framing rather than the product framing, the dedicated proxy for scraping page walks through headless browser support and IP rotation patterns from the workload side.

Common errors and verified fixes

net::ERR_NO_SUPPORTED_PROXIES almost always means a bad scheme or embedded credentials. Strip user:pass@ from the URL and verify the scheme is one of http, https, socks4, or socks5. Do not use socks5h://; Chromium does not implement it.

net::ERR_PROXY_CONNECTION_FAILED is a TCP-level failure. The host is unreachable, the port is wrong, or the proxy closed the connection. Test the upstream with curl -x before debugging Puppeteer. If you are pointing at an HTTPS proxy, make sure the scheme is https://, not http://.

net::ERR_TUNNEL_CONNECTION_FAILED combined with a 407 on the CONNECT request means CONNECT-level auth. page.authenticate cannot fix this; chain through a local forwarder that injects Proxy-Authorization.

net::ERR_CERT_AUTHORITY_INVALID happens with intercepting proxies that present their own CA, or when an intermediate certificate is missing from the headless trust store. Set acceptInsecureCerts: true and add --ignore-certificate-errors only after verifying the proxy is the cause.

Navigation hangs typically point at waitUntil: 'networkidle0', which requires zero in-flight requests for 500 ms. Through a residential exit node, analytics beacons and websockets keep the network busy past that threshold. Switch to 'domcontentloaded' and wait explicitly for the selector you need. Skip heavy resource types with request interception to help the network actually go idle.

WebRTC exposing the real IP through STUN ICE candidates is solved at launch by --force-webrtc-ip-handling-policy=disable_non_proxied_udp. Patching RTCPeerConnection from evaluateOnNewDocument is not enough on its own, because the script may not run before the first peer connection.

Best practices for Puppeteer at scale

Five patterns recur in production Puppeteer fleets that consume proxies at volume. Reuse one browser process and isolate identities with createBrowserContext, rather than launching N browsers, until per-process state forces you to. Cap browser pool size by available RAM and recycle each browser after a few hundred pages to release the slow renderer memory drift. Wrap every page.goto in a retry loop that catches ERR_TUNNEL_CONNECTION_FAILED, ERR_PROXY_CONNECTION_FAILED, and TimeoutError, and pulls a fresh upstream from the rotator on retry. Run a local forwarding proxy in front of authenticated upstreams; the price of one local hop is worth the simpler error model and the per-request rotation it enables. Treat one context as one identity: cookies, storage, fingerprint, and proxy session ID all stay paired for the life of that context.

Pulling these together: a typical production worker holds one browser, a small pool of contexts each pinned to a sticky session through a rotating residential gateway, a stealth plugin loaded once at launch, and a retry-with-new-context outer loop. Start a free Proxy-Cheap account or top up on pay-as-you-go credit to test the configuration against your actual targets before scaling out.

الأسئلة الشائعة

Not natively. Chromium reads --proxy-server once at process start, and there is no DevTools Protocol method to change the upstream per request. The two practical workarounds are running a local forwarding proxy that round-robins upstreams server-side, or replaying requests from Node through a chosen upstream and feeding the response back via request.respond(). The local-forwarder route is the standard choice because it keeps Chrome’s real TLS fingerprint intact.

No. page.authenticate answers HTTP basic auth challenges through the Fetch domain. Chromium’s SOCKS5 implementation does not negotiate username and password at all, so authenticated SOCKS5 must either rely on IP whitelisting at the provider or be wrapped behind a local HTTP forwarder that handles the auth.

Navigate to an IP echo endpoint like https://httpbin.org/ip or https://api.ipify.org?format=json from inside Puppeteer and read the response. Compare the result against the IP returned by curl https://api.ipify.org from the same host. If they match, the proxy is not active. Also check that the proxy logs show the request, and that no requests slip through DNS over UDP outside the proxy by setting --host-resolver-rules when paranoid.

The createBrowserContext name was introduced in Puppeteer 22.0.0, which followed the rename from createIncognitoBrowserContext. Puppeteer follows the latest Node.js LTS maintenance line, so Node 18 or later is required for current versions, and TypeScript 4.7.4 or later for typed code. Older Puppeteer 21.x code calling createIncognitoBrowserContext({ proxyServer }) still works on that branch but should be migrated.

Pass --force-webrtc-ip-handling-policy=disable_non_proxied_udp in the launch args. Optionally pair it with --disable-features=WebRtcHideLocalIpsWithMdns to make Chromium behave like a regular browser instance for testing. Do not rely solely on JavaScript patches of RTCPeerConnection, because the patch may not run before the first peer connection is constructed.

Not for general browsing. Treat it as a per-job switch when the proxy presents an MITM certificate or when scraping internal targets with self-signed TLS. For any traffic that touches real credentials or sensitive data, install the proxy’s root CA into the trust store the headless Chromium reads, rather than disabling certificate validation globally.

page.authenticate enables request interception internally to answer the auth challenge. If your own code also registers a request handler and calls req.continue() or req.abort() without checking first, you double-handle the request. Guard every handler with if (req.isInterceptResolutionHandled()) return; before any action, and use cooperative interception mode when stacking multiple handlers.