How to Use Proxies with Puppeteer in 2026: Complete Guide
Puppeteer remains one of the most widely used headless browser libraries in the Node.js ecosystem. Whether you are scraping product data, running automated tests, or monitoring competitor websites, proxies are essential for avoiding IP blocks and accessing geo-restricted content.
This guide walks through every proxy configuration method available in Puppeteer, from basic setup to advanced rotation strategies, with real code you can copy and run immediately.
Why Proxies Matter for Puppeteer
When Puppeteer launches Chromium, every HTTP request originates from your server’s IP address. Without proxies:
- Target websites will rate-limit or ban your IP after a few hundred requests
- You cannot access content restricted to specific geographic regions
- Your server IP gets flagged in abuse databases
- Concurrent scraping sessions all share the same IP, making detection trivial
Proxies solve all of these problems. If you need to estimate proxy bandwidth costs for your project, check out our proxy cost calculator.
Prerequisites
- Node.js 18+ installed
- npm or yarn package manager
- A proxy provider with HTTP/HTTPS or SOCKS5 endpoints
Install Puppeteer
npm init -y
npm install puppeteerThis installs Puppeteer along with a bundled Chromium binary.
Basic Proxy Setup via Launch Args
Puppeteer passes command-line arguments directly to Chromium. The --proxy-server flag is how you configure a proxy:
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({
args: ['--proxy-server=http://proxy.example.com:8080']
});
const page = await browser.newPage();
await page.goto('https://httpbin.org/ip');
const body = await page.$eval('body', el => el.textContent);
console.log('IP:', body);
await browser.close();
})();SOCKS5 Proxy
const browser = await puppeteer.launch({
args: ['--proxy-server=socks5://proxy.example.com:1080']
});Bypassing Proxy for Specific Domains
const browser = await puppeteer.launch({
args: [
'--proxy-server=http://proxy.example.com:8080',
'--proxy-bypass-list=localhost,127.0.0.1,*.internal.com'
]
});Authenticated Proxy Handling
Chromium’s --proxy-server flag does not support inline credentials (unlike cURL). Puppeteer provides page.authenticate() for this:
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({
args: ['--proxy-server=http://proxy.example.com:8080']
});
const page = await browser.newPage();
// Set proxy credentials BEFORE navigating
await page.authenticate({
username: 'your_username',
password: 'your_password'
});
await page.goto('https://httpbin.org/ip');
const body = await page.$eval('body', el => el.textContent);
console.log('IP:', body);
await browser.close();
})();Important: Call page.authenticate() before any page.goto() call. If you call it after navigation has started, authentication will fail for that request.
Per-Page Proxy Switching
Puppeteer sets the proxy at the browser level, which means all pages share the same proxy. To use different proxies for different pages, you need to launch separate browser instances:
const puppeteer = require('puppeteer');
async function scrapeWithProxy(url, proxy, credentials) {
const browser = await puppeteer.launch({
args: [`--proxy-server=${proxy}`]
});
const page = await browser.newPage();
if (credentials) {
await page.authenticate(credentials);
}
try {
await page.goto(url, { timeout: 30000, waitUntil: 'domcontentloaded' });
const title = await page.title();
return { url, title, proxy, status: 'success' };
} catch (err) {
return { url, error: err.message, proxy, status: 'failed' };
} finally {
await browser.close();
}
}
// Usage
(async () => {
const tasks = [
{ url: 'https://example.com', proxy: 'http://proxy1.example.com:8080' },
{ url: 'https://example.org', proxy: 'http://proxy2.example.com:8080' },
];
const results = await Promise.all(
tasks.map(t => scrapeWithProxy(t.url, t.proxy, {
username: 'user',
password: 'pass'
}))
);
console.log(results);
})();Alternative: Request Interception for Per-Request Proxy
For finer control, you can intercept requests and route them through different proxies using a local proxy server like proxy-chain:
npm install proxy-chainconst puppeteer = require('puppeteer');
const proxyChain = require('proxy-chain');
(async () => {
// Create an anonymized proxy URL (handles authentication)
const proxyUrl = await proxyChain.anonymizeProxy(
'http://username:password@proxy.example.com:8080'
);
const browser = await puppeteer.launch({
args: [`--proxy-server=${proxyUrl}`]
});
const page = await browser.newPage();
await page.goto('https://httpbin.org/ip');
console.log(await page.$eval('body', el => el.textContent));
await browser.close();
await proxyChain.closeAnonymizedProxy(proxyUrl);
})();The proxy-chain library is especially useful because it handles authentication transparently, so you do not need page.authenticate().
Using puppeteer-extra with Stealth Plugin
The puppeteer-extra ecosystem provides plugins that make Puppeteer harder to detect. The stealth plugin patches numerous fingerprinting vectors:
npm install puppeteer-extra puppeteer-extra-plugin-stealthconst puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
puppeteer.use(StealthPlugin());
(async () => {
const browser = await puppeteer.launch({
headless: 'new',
args: [
'--proxy-server=http://proxy.example.com:8080',
'--disable-blink-features=AutomationControlled',
'--no-sandbox'
]
});
const page = await browser.newPage();
await page.authenticate({
username: 'user',
password: 'pass'
});
// Set a realistic viewport and user agent
await page.setViewport({ width: 1920, height: 1080 });
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'
);
await page.goto('https://bot.sannysoft.com');
await page.screenshot({ path: 'stealth-results.png', fullPage: true });
await browser.close();
})();Verify that your stealth configuration works by testing against our browser fingerprint tester.
Proxy Rotation Strategies
Strategy 1: Round-Robin Rotation
Cycle through a list of proxies sequentially:
const puppeteer = require('puppeteer');
class ProxyRotator {
constructor(proxies) {
this.proxies = proxies;
this.index = 0;
}
next() {
const proxy = this.proxies[this.index];
this.index = (this.index + 1) % this.proxies.length;
return proxy;
}
remove(proxy) {
this.proxies = this.proxies.filter(p => p.server !== proxy.server);
if (this.index >= this.proxies.length) this.index = 0;
}
}
(async () => {
const rotator = new ProxyRotator([
{ server: 'http://proxy1.example.com:8080', username: 'user', password: 'pass' },
{ server: 'http://proxy2.example.com:8080', username: 'user', password: 'pass' },
{ server: 'http://proxy3.example.com:8080', username: 'user', password: 'pass' },
]);
const urls = [
'https://example.com/page1',
'https://example.com/page2',
'https://example.com/page3',
'https://example.com/page4',
'https://example.com/page5',
];
for (const url of urls) {
const proxy = rotator.next();
const browser = await puppeteer.launch({
args: [`--proxy-server=${proxy.server}`]
});
const page = await browser.newPage();
await page.authenticate({ username: proxy.username, password: proxy.password });
try {
await page.goto(url, { timeout: 20000 });
console.log(`[${proxy.server}] ${url}: ${await page.title()}`);
} catch (err) {
console.error(`[${proxy.server}] ${url}: ${err.message}`);
}
await browser.close();
}
})();Strategy 2: Rotating Gateway Proxy
Use a single gateway endpoint from your proxy provider. The provider handles rotation server-side:
const browser = await puppeteer.launch({
args: ['--proxy-server=http://gate.provider.com:7777']
});
const page = await browser.newPage();
await page.authenticate({
username: 'user-country-us-session-' + Date.now(),
password: 'password'
});By appending a unique session ID to the username, most providers assign a fresh IP per session.
Strategy 3: Concurrent Pool with Browser Reuse
const puppeteer = require('puppeteer');
const proxyChain = require('proxy-chain');
async function createPool(proxies, concurrency = 3) {
const pool = [];
for (let i = 0; i < concurrency; i++) {
const proxy = proxies[i % proxies.length];
const anonUrl = await proxyChain.anonymizeProxy(
`http://${proxy.username}:${proxy.password}@${proxy.host}:${proxy.port}`
);
const browser = await puppeteer.launch({
args: [`--proxy-server=${anonUrl}`]
});
pool.push({ browser, anonUrl, proxy });
}
return pool;
}
async function destroyPool(pool) {
for (const { browser, anonUrl } of pool) {
await browser.close();
await proxyChain.closeAnonymizedProxy(anonUrl);
}
}
// Usage
(async () => {
const proxies = [
{ host: 'proxy1.example.com', port: 8080, username: 'user', password: 'pass' },
{ host: 'proxy2.example.com', port: 8080, username: 'user', password: 'pass' },
{ host: 'proxy3.example.com', port: 8080, username: 'user', password: 'pass' },
];
const pool = await createPool(proxies, 3);
let poolIndex = 0;
const urls = Array.from({ length: 10 }, (_, i) => `https://example.com/page${i + 1}`);
for (const url of urls) {
const { browser } = pool[poolIndex % pool.length];
poolIndex++;
const page = await browser.newPage();
try {
await page.goto(url, { timeout: 20000 });
console.log(`${url}: ${await page.title()}`);
} catch (err) {
console.error(`${url}: ${err.message}`);
} finally {
await page.close();
}
}
await destroyPool(pool);
})();Error Handling and Timeout Management
Robust error handling is critical when working with proxies:
const puppeteer = require('puppeteer');
async function scrapeWithRetry(url, proxyConfig, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
let browser;
try {
browser = await puppeteer.launch({
args: [`--proxy-server=${proxyConfig.server}`],
timeout: 15000 // Browser launch timeout
});
const page = await browser.newPage();
await page.authenticate({
username: proxyConfig.username,
password: proxyConfig.password
});
// Set navigation timeout
page.setDefaultNavigationTimeout(25000);
// Set up request failure logging
page.on('requestfailed', request => {
if (request.url() === url) {
console.warn(`Request failed: ${request.failure()?.errorText}`);
}
});
const response = await page.goto(url, { waitUntil: 'domcontentloaded' });
if (!response) {
throw new Error('No response received');
}
if (response.status() === 403 || response.status() === 429) {
throw new Error(`Blocked with status ${response.status()}`);
}
const content = await page.content();
return { success: true, content, status: response.status() };
} catch (err) {
console.error(`Attempt ${attempt}/${maxRetries} failed: ${err.message}`);
if (attempt === maxRetries) {
return { success: false, error: err.message };
}
// Exponential backoff
const delay = Math.min(1000 * Math.pow(2, attempt), 10000);
await new Promise(resolve => setTimeout(resolve, delay));
} finally {
if (browser) await browser.close();
}
}
}
// Usage
(async () => {
const result = await scrapeWithRetry('https://example.com', {
server: 'http://proxy.example.com:8080',
username: 'user',
password: 'pass'
});
if (result.success) {
console.log(`Got ${result.content.length} bytes`);
} else {
console.error('All retries failed:', result.error);
}
})();Blocking Unnecessary Resources
Save proxy bandwidth by intercepting and blocking requests for images, fonts, and analytics:
await page.setRequestInterception(true);
page.on('request', (request) => {
const blockedTypes = ['image', 'font', 'media', 'stylesheet'];
const blockedDomains = ['google-analytics.com', 'facebook.net', 'doubleclick.net'];
const url = request.url();
const type = request.resourceType();
if (blockedTypes.includes(type) || blockedDomains.some(d => url.includes(d))) {
request.abort();
} else {
request.continue();
}
});Troubleshooting Common Issues
Error: net::ERR_PROXY_CONNECTION_FAILED
Cause: Proxy server is down or the address is incorrect.
Fix:
- Test the proxy with cURL:
curl -x http://proxy:port https://httpbin.org/ip - Check that the proxy port is correct
- Verify your IP is whitelisted with the proxy provider
Error: net::ERR_PROXY_AUTH_FAILED
Cause: Wrong credentials or page.authenticate() called after navigation.
Fix: Always call page.authenticate() before page.goto(). Double-check your username and password.
Page Loads but Shows CAPTCHA
Cause: The proxy IP is flagged or you are detected as a bot.
Fix:
- Use residential proxies instead of datacenter proxies
- Add the stealth plugin (
puppeteer-extra-plugin-stealth) - Rotate proxies more frequently
- Add realistic delays between actions:
await page.waitForTimeout(2000 + Math.random() * 3000)
Memory Leaks with Many Browser Instances
Cause: Not closing browser instances properly.
Fix: Always call browser.close() in a finally block. For long-running scripts, monitor memory usage:
const used = process.memoryUsage();
console.log(`Memory: RSS=${Math.round(used.rss / 1024 / 1024)}MB, Heap=${Math.round(used.heapUsed / 1024 / 1024)}MB`);Proxy Works in cURL but Not in Puppeteer
Cause: The proxy may require a specific protocol or TLS version that Chromium handles differently.
Fix: Try using proxy-chain to create a local proxy wrapper:
const proxyUrl = await proxyChain.anonymizeProxy('http://user:pass@proxy:port');Summary
Puppeteer’s proxy support works through Chromium’s command-line flags, which is straightforward but has limitations — particularly around per-page proxy switching and authentication. Key takeaways:
- Use
--proxy-serverlaunch arg for basic proxy setup - Use
page.authenticate()for proxy credentials (call it before navigation) - Use
proxy-chainfor cleaner authentication handling and anonymization - Use
puppeteer-extrawith the stealth plugin to avoid detection - Always implement retry logic with exponential backoff
- Close browser instances properly to prevent memory leaks
For cost estimation, use our proxy cost calculator. To verify your bot fingerprint, try the browser fingerprint tester.
- How to Use Proxies with cURL in 2026: Complete Guide
- How to Use Proxies with Node.js Axios in 2026: Complete Guide
- Best Proxies for Amazon 2026: Complete Guide
- Best Proxies for Discord 2026: Bot Hosting & Account Management
- Best Proxies for eBay 2026: Complete Guide
- Best Proxies for Netflix 2026: Geo-Unblocking & Catalog Access
- How to Use Proxies with cURL in 2026: Complete Guide
- How to Use Proxies with Node.js Axios in 2026: Complete Guide
- Best Proxies for Amazon 2026: Complete Guide
- Best Proxies for Discord 2026: Bot Hosting & Account Management
- Best Proxies for eBay 2026: Complete Guide
- Best Proxies for Netflix 2026: Geo-Unblocking & Catalog Access
- How to Use Proxies with cURL in 2026: Complete Guide
- How to Use Proxies with Node.js Axios in 2026: Complete Guide
- Best Proxies for Amazon 2026: Complete Guide
- Best Proxies for Discord 2026: Bot Hosting & Account Management
- Best Proxies for eBay 2026: Complete Guide
- Best Proxies for Netflix 2026: Geo-Unblocking & Catalog Access
Related Reading
- How to Use Proxies with cURL in 2026: Complete Guide
- How to Use Proxies with Node.js Axios in 2026: Complete Guide
- Best Proxies for Amazon 2026: Complete Guide
- Best Proxies for Discord 2026: Bot Hosting & Account Management
- Best Proxies for eBay 2026: Complete Guide
- Best Proxies for Netflix 2026: Geo-Unblocking & Catalog Access