v1.10.90-0e025b8
Skip to main content
TutorialPuppeteer

How to Set Up Proxies with Puppeteer: Complete Guide with Code

12 min read

By Hex Proxies Engineering Team

How to Set Up Proxies with Puppeteer: Complete Guide with Code

Last updated: April 2026 | Author: Hex Proxies Team

TL;DR: Puppeteer supports proxy configuration at browser launch with native authentication via the 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 ModeAvg. Page SizePages per GBCost per 1K Pages
Full page (all resources)2-5 MB200-500$3.40-$8.50
HTML + JS only (no images/CSS)200-800 KB1,250-5,000$0.34-$1.36
HTML only (block all except document)50-300 KB3,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

FeaturePuppeteerSeleniumPlaywright
Native proxy authYes (page.authenticate)No (needs extension/wire)Yes (launch option)
Per-page proxyVia authentication changeNo (browser-level only)Yes (per-context)
Headless proxy supportFullLimited (needs wire)Full
Browser supportChrome/ChromiumChrome, Firefox, Edge, SafariChrome, Firefox, WebKit
SOCKS5 supportYes (launch arg)Yes (options)Yes (launch option)
Request interceptionYes (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.