How to Set Up Proxies with Puppeteer: Complete Guide with Code
Last updated: April 2026 | Author: Hex Proxies Team
page.authenticate() method. This guide covers basic setup, authenticated proxies, per-page rotation, sticky sessions, headless configuration, and performance optimization. All examples use Hex Proxies gateway at gate.hexproxies.com:8080. Residential starts at $1.70/GB.
Puppeteer is Google's Node.js library for controlling Chrome and Chromium browsers. For web scraping, testing, and automation workloads that need proxy support, Puppeteer offers a cleaner proxy authentication experience than Selenium — the page.authenticate() method handles credentials natively without extensions or third-party wrappers.
This guide provides production-ready Puppeteer proxy configurations for every common scenario, from basic setup to advanced rotation patterns.
Basic Proxy Setup
Launch with Proxy
Puppeteer accepts proxy settings as a Chrome launch argument:
const puppeteer = require('puppeteer');
async function basicProxy() {
const browser = await puppeteer.launch({
args: ['--proxy-server=http://gate.hexproxies.com:8080']
});
const page = await browser.newPage();
// Authenticate with proxy credentials
await page.authenticate({
username: 'YOUR_USERNAME-country-us',
password: 'YOUR_PASSWORD'
});
await page.goto('https://httpbin.org/ip');
const content = await page.evaluate(() => document.body.textContent);
console.log('IP:', content);
await browser.close();
}
basicProxy();
The page.authenticate() call sets credentials for HTTP 407 proxy authentication challenges. This is Puppeteer's native approach — no extensions or middleware needed.
Authenticated Proxy with Full Configuration
const puppeteer = require('puppeteer');
async function authenticatedProxy() {
const PROXY_HOST = 'gate.hexproxies.com';
const PROXY_PORT = 8080;
const PROXY_USER = 'YOUR_USERNAME-country-us';
const PROXY_PASS = 'YOUR_PASSWORD';
const browser = await puppeteer.launch({
headless: 'new', // Use new headless mode (Chrome 109+)
args: [
`--proxy-server=http://${PROXY_HOST}:${PROXY_PORT}`,
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
'--disable-accelerated-2d-canvas',
'--disable-gpu'
]
});
const page = await browser.newPage();
// Set proxy authentication
await page.authenticate({
username: PROXY_USER,
password: PROXY_PASS
});
// Set viewport and user agent for realistic browsing
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/133.0.0.0 Safari/537.36'
);
// Navigate with timeout and wait for content
await page.goto('https://example.com', {
waitUntil: 'networkidle2',
timeout: 30000
});
const title = await page.title();
console.log('Page title:', title);
await browser.close();
}
authenticatedProxy();
Rotating Proxies: Per-Request IP Rotation
With Hex Proxies gateway, each new browser instance gets a new IP automatically. For rotation within the same browser, create new pages with different session identifiers:
Method 1: New Browser Per Request
const puppeteer = require('puppeteer');
async function scrapeWithRotation(urls) {
const results = [];
for (const url of urls) {
const browser = await puppeteer.launch({
headless: 'new',
args: ['--proxy-server=http://gate.hexproxies.com:8080']
});
const page = await browser.newPage();
await page.authenticate({
username: 'YOUR_USERNAME-country-us',
password: 'YOUR_PASSWORD'
});
try {
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 });
const content = await page.evaluate(() => document.body.innerHTML);
results.push({ url, content, success: true });
} catch (error) {
results.push({ url, error: error.message, success: false });
}
await browser.close();
// Human-like delay between requests
await new Promise(resolve =>
setTimeout(resolve, 2000 + Math.random() * 3000)
);
}
return results;
}
Method 2: Session-Based Rotation (Same Browser)
const puppeteer = require('puppeteer');
const crypto = require('crypto');
async function scrapeWithSessionRotation(urls) {
const browser = await puppeteer.launch({
headless: 'new',
args: ['--proxy-server=http://gate.hexproxies.com:8080']
});
const results = [];
for (const url of urls) {
const page = await browser.newPage();
// Unique session per page = unique IP
const sessionId = crypto.randomBytes(4).toString('hex');
await page.authenticate({
username: `YOUR_USERNAME-country-us-session-${sessionId}`,
password: 'YOUR_PASSWORD'
});
try {
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 });
const title = await page.title();
results.push({ url, title, success: true });
} catch (error) {
results.push({ url, error: error.message, success: false });
}
await page.close();
await new Promise(resolve =>
setTimeout(resolve, 1500 + Math.random() * 2500)
);
}
await browser.close();
return results;
}
Sticky Sessions: Same IP for Multiple Pages
When you need to maintain the same IP across multiple page navigations (e.g., logging in and then navigating), use a fixed session identifier:
const puppeteer = require('puppeteer');
async function stickySessionScrape(urls, sessionId = 'my-sticky-session') {
const browser = await puppeteer.launch({
headless: 'new',
args: ['--proxy-server=http://gate.hexproxies.com:8080']
});
const page = await browser.newPage();
// Same session ID = same IP for all requests
await page.authenticate({
username: `YOUR_USERNAME-country-us-session-${sessionId}`,
password: 'YOUR_PASSWORD'
});
await page.setViewport({ width: 1920, height: 1080 });
for (const url of urls) {
await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 });
// All requests use the same proxy IP
console.log(`Loaded: ${url}`);
await new Promise(resolve =>
setTimeout(resolve, 2000 + Math.random() * 3000)
);
}
await browser.close();
}
Geo-Targeted Proxies
Hex Proxies supports geo-targeting through username parameters. This is useful for scraping location-specific content:
const puppeteer = require('puppeteer');
async function geoTargetedScrape(url, country) {
const browser = await puppeteer.launch({
headless: 'new',
args: ['--proxy-server=http://gate.hexproxies.com:8080']
});
const page = await browser.newPage();
await page.authenticate({
username: `YOUR_USERNAME-country-${country}`,
password: 'YOUR_PASSWORD'
});
// Match Accept-Language to proxy country
const langMap = {
us: 'en-US,en;q=0.9',
gb: 'en-GB,en;q=0.9',
de: 'de-DE,de;q=0.9,en;q=0.8',
fr: 'fr-FR,fr;q=0.9,en;q=0.8',
jp: 'ja-JP,ja;q=0.9,en;q=0.8'
};
await page.setExtraHTTPHeaders({
'Accept-Language': langMap[country] || 'en-US,en;q=0.9'
});
await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 });
const content = await page.content();
await browser.close();
return content;
}
// Scrape the same page from different countries
const countries = ['us', 'gb', 'de', 'jp'];
for (const country of countries) {
const content = await geoTargetedScrape('https://example.com', country);
// Compare regional content differences
}
Performance Optimization
Block Unnecessary Resources
Blocking images, fonts, and CSS dramatically reduces bandwidth consumption and page load time when scraping through proxies:
const puppeteer = require('puppeteer');
async function optimizedScrape(url) {
const browser = await puppeteer.launch({
headless: 'new',
args: ['--proxy-server=http://gate.hexproxies.com:8080']
});
const page = await browser.newPage();
await page.authenticate({
username: 'YOUR_USERNAME-country-us',
password: 'YOUR_PASSWORD'
});
// Enable request interception
await page.setRequestInterception(true);
page.on('request', (req) => {
const blockedTypes = ['image', 'stylesheet', 'font', 'media'];
if (blockedTypes.includes(req.resourceType())) {
req.abort();
} else {
req.continue();
}
});
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 });
const data = await page.evaluate(() => {
// Extract only the data you need
return {
title: document.title,
text: document.body.innerText.substring(0, 5000)
};
});
await browser.close();
return data;
}
This optimization typically reduces bandwidth by 60-80%, which directly reduces proxy costs at $1.70/GB.
Bandwidth Impact Comparison
| Loading Mode | Avg. Page Size | Pages per GB | Cost per 1K Pages |
|---|---|---|---|
| Full page (all resources) | 2-5 MB | 200-500 | $3.40-$8.50 |
| HTML + JS only (no images/CSS) | 200-800 KB | 1,250-5,000 | $0.34-$1.36 |
| HTML only (block all except document) | 50-300 KB | 3,333-20,000 | $0.085-$0.51 |
SOCKS5 Proxy with Puppeteer
const puppeteer = require('puppeteer');
async function socks5Proxy() {
const browser = await puppeteer.launch({
headless: 'new',
args: [
'--proxy-server=socks5://gate.hexproxies.com:1080',
// Resolve DNS through the proxy to prevent leaks
'--host-resolver-rules=MAP * ~NOTFOUND, EXCLUDE localhost'
]
});
const page = await browser.newPage();
await page.authenticate({
username: 'YOUR_USERNAME',
password: 'YOUR_PASSWORD'
});
await page.goto('https://httpbin.org/ip');
console.log(await page.evaluate(() => document.body.textContent));
await browser.close();
}
For protocol comparison and when to use SOCKS5 vs HTTP, see our protocol guide.
Error Handling and Retry Logic
const puppeteer = require('puppeteer');
const crypto = require('crypto');
async function scrapeWithRetry(url, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const browser = await puppeteer.launch({
headless: 'new',
args: ['--proxy-server=http://gate.hexproxies.com:8080']
});
try {
const page = await browser.newPage();
// New session per retry = new IP
const sessionId = crypto.randomBytes(4).toString('hex');
await page.authenticate({
username: `YOUR_USERNAME-country-us-session-${sessionId}`,
password: 'YOUR_PASSWORD'
});
page.setDefaultTimeout(30000);
const response = await page.goto(url, {
waitUntil: 'domcontentloaded',
timeout: 30000
});
// Check for blocks
const status = response.status();
if (status === 403 || status === 429) {
console.log(`Blocked (${status}) on attempt ${attempt + 1}, retrying...`);
await browser.close();
// Exponential backoff
await new Promise(resolve =>
setTimeout(resolve, Math.pow(2, attempt) * 2000)
);
continue;
}
// Check for CAPTCHA in content
const content = await page.content();
if (content.includes('captcha') || content.includes('challenge')) {
console.log(`CAPTCHA detected on attempt ${attempt + 1}, retrying...`);
await browser.close();
await new Promise(resolve =>
setTimeout(resolve, Math.pow(2, attempt) * 3000)
);
continue;
}
const result = await page.evaluate(() => ({
title: document.title,
html: document.documentElement.outerHTML
}));
await browser.close();
return { success: true, data: result };
} catch (error) {
await browser.close();
if (attempt === maxRetries - 1) {
return { success: false, error: error.message };
}
await new Promise(resolve =>
setTimeout(resolve, Math.pow(2, attempt) * 2000)
);
}
}
}
Puppeteer vs Selenium vs Playwright: Proxy Comparison
| Feature | Puppeteer | Selenium | Playwright |
|---|---|---|---|
| Native proxy auth | Yes (page.authenticate) | No (needs extension/wire) | Yes (launch option) |
| Per-page proxy | Via authentication change | No (browser-level only) | Yes (per-context) |
| Headless proxy support | Full | Limited (needs wire) | Full |
| Browser support | Chrome/Chromium | Chrome, Firefox, Edge, Safari | Chrome, Firefox, WebKit |
| SOCKS5 support | Yes (launch arg) | Yes (options) | Yes (launch option) |
| Request interception | Yes (built-in) | No (needs wire) | Yes (built-in) |
For Selenium proxy setup, see our Selenium proxy configuration guide. For a broader proxy comparison, see our integrations page.
Frequently Asked Questions
Does page.authenticate() work in headless mode?
Yes. Unlike Selenium's extension-based approach, Puppeteer's page.authenticate() works in both headed and headless modes, including the new headless mode (headless: 'new'). This is one of Puppeteer's key advantages for proxy-based scraping.
Can I use different proxies for different pages in the same browser?
Yes. Call page.authenticate() with different credentials on each new page. Using different session parameters in the username (e.g., session-page1, session-page2) will route each page through a different IP while reusing the same browser instance.
How do I reduce bandwidth costs when scraping with Puppeteer?
Use request interception to block images, fonts, CSS, and media files. This typically reduces bandwidth by 60-80%. At $1.70/GB, blocking unnecessary resources is the single most effective cost optimization. Additionally, use domcontentloaded instead of networkidle2 for the wait condition when you only need HTML content.
Is Puppeteer or Playwright better for proxy-based scraping?
Both work well. Playwright has a slight edge with per-context proxy configuration (different proxies per browser context without re-authentication). Puppeteer has a larger ecosystem and more community resources. For most proxy-based scraping workloads, both are equally effective. Choose based on your team's familiarity and language preference (Puppeteer is Node.js only; Playwright supports Python, Java, and .NET).
How do I handle sites that block headless Chrome?
Use the new headless mode (headless: 'new') which is harder to detect than the old mode. Set a realistic user agent and viewport size. Consider using puppeteer-extra with the stealth plugin to patch known detection vectors. Combining stealth patches with residential proxies from Hex Proxies provides the highest success rates. See our anti-bot detection guide for advanced strategies.