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.
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.
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_PROXIESfor 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.
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.
| Error | Cause | Fix |
|---|---|---|
| ERR_PROXY_CONNECTION_FAILED | Proxy server is unreachable or offline | Verify the proxy address and port are correct. Ensure your firewall allows outbound connections on the proxy port. |
| ERR_NO_SUPPORTED_PROXIES | SOCKS5h with credentials in the URL | Use the proxy-chain package to create a local forwarding proxy. Chrome cannot handle SOCKS5h auth natively. |
| ERR_TUNNEL_CONNECTION_FAILED | HTTPS CONNECT tunneling failure | Verify the proxy supports the CONNECT method for HTTPS. Some free proxies only support HTTP. |
| HTTP 407 | Proxy authentication failed | Check credentials in page.authenticate(). Ensure username and password are correct and the proxy requires auth. |
| ERR_INVALID_ARGUMENT | Malformed proxy URL in launch args | Format 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:
// 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
// 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
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:
| Feature | Puppeteer | Playwright |
|---|---|---|
| npm Downloads | 8.2M/week | 33.3M/week |
| Browser Support | Chrome only | Chrome, Firefox, WebKit |
| Proxy Setup | Launch args + authenticate() | Built-in proxy option |
| Stealth Plugin | Unmaintained (v2.11.2) | playwright-extra |
| Languages | JS/TS only | JS, TS, Python, Java, C# |
| Recommendation | Existing Chrome projects | New 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.