Products
Datacenter proxies from $19/mo Rotating proxies from $49/mo ISP proxies from $33/mo Dedicated proxies from $3.50/mo UDP proxies from $5/mo Try proxies
Use cases
Data & scraping AI services Social media & messaging E-commerce & finance Media & entertainment Marketing & ads Automation & tools All use cases →
Pricing
Full pricing table All 20 locations Money-back guarantee
Resources
Blog Proxy API MCP server Setup guides FAQ For business About us Affiliate program
English
English Русский

How to set a proxy in Puppeteer

Puppeteer drives Chrome, so a proxy is set the Chrome way — with the --proxy-server launch flag. What Puppeteer adds on top is page.authenticate(), which answers the proxy's login prompt programmatically, and per-context proxies via createBrowserContext(). This guide covers all three, plus SOCKS5 and rotating proxies from a list.

Before you start: proxy details and ports

You'll need your proxy IP address and port. Both are listed in your Dashboard — open your active subscription to see the full connection details. The port depends on how you want to authenticate:

Authentication HTTP port SOCKS5 port Good for
Username / password
credentials from your Dashboard
8080 1080 Any machine — Puppeteer handles the login via page.authenticate() (HTTP proxies only)
IP whitelisting
no login required
8085 1085 Servers with a static IP — no authentication code, required for SOCKS5

With IP whitelisting, you first add your server's IP address to the whitelist in the Dashboard. After that, the proxy accepts requests from that machine with no credentials — skip the page.authenticate() calls below.

Set a proxy for the whole browser

Pass the flag in args and, if you're on the username/password port, call page.authenticate() before the first navigation:

import puppeteer from 'puppeteer'; const browser = await puppeteer.launch({ args: ['--proxy-server=http://123.45.67.89:8080'], }); const page = await browser.newPage(); await page.authenticate({ username: 'your-login', password: 'your-password', }); await page.goto('https://api.ipify.org'); console.log(await page.evaluate(() => document.body.innerText)); // prints the proxy IP await browser.close();

This is the answer to the classic “proxy authentication” problem: login:password@ in the --proxy-server URL doesn't work — Chrome strips the credentials. page.authenticate() responds to the proxy's authentication challenge through the DevTools Protocol instead. Call it on every page that goes through the proxy, before goto(), and keep the credentials in environment variables rather than in code.

A different proxy per context

A browser context is an isolated “incognito profile” with its own cookies and storage — and, since Puppeteer 22, its own proxy. This lets one browser run several accounts or locations at once:

const browser = await puppeteer.launch(); const context = await browser.createBrowserContext({ proxyServer: 'http://123.45.67.89:8080', }); const page = await context.newPage(); await page.authenticate({ username: 'your-login', password: 'your-password', }); await page.goto('https://example.com');

On older Puppeteer versions (below 22) the method is called createIncognitoBrowserContext() and takes the same proxyServer option.

SOCKS5 proxies

Change the scheme in the flag to use SOCKS5:

args: ['--proxy-server=socks5://123.45.67.89:1085']

One important limitation: Chrome doesn't support authentication for SOCKS5 proxies, and page.authenticate() can't help — there's no login challenge to answer. So for SOCKS5, use port 1085 with IP whitelisting. For most Puppeteer work an HTTP proxy is the simpler choice anyway.

You may have seen socks5h:// in curl or Python requests manuals — there the extra “h” makes the proxy resolve domain names instead of your machine, so DNS lookups don't leak outside the proxy. Chrome doesn't recognize that scheme and doesn't need it: per the Chromium documentation, with a plain socks5:// proxy name resolution is always done on the proxy side.

Sending some hosts around the proxy

The proxyBypassList option lists hosts that should be reached directly — your own test servers, for example:

const context = await browser.createBrowserContext({ proxyServer: 'http://123.45.67.89:8085', proxyBypassList: ['localhost', '*.internal.example.com'], });

For a launch-level proxy, the equivalent is the --proxy-bypass-list flag next to --proxy-server in args.

Loading proxies from a list and rotating them

With a proxy package, nobody types addresses into code by hand: export your full list from the Dashboard to a file — one IP:port per line, the field order and delimiter are configurable at export — and load it at the start of the script. Contexts are cheap, so “one context per proxy from the list” is the standard rotation pattern:

import fs from 'node:fs'; import puppeteer from 'puppeteer'; // proxies.txt — one address per line: 123.45.67.89:8085 const proxies = fs.readFileSync('proxies.txt', 'utf8') .split('\n').map(s => s.trim()).filter(Boolean); const browser = await puppeteer.launch(); for (const address of proxies) { const context = await browser.createBrowserContext({ proxyServer: `http://${address}`, }); const page = await context.newPage(); await page.goto('https://example.com'); // ... scrape ... await context.close(); } await browser.close();

If your list runs on the credential ports (8080 for HTTP), you don't need a new export — set the username and password once as constants and call page.authenticate() before the first navigation in each context:

import fs from 'node:fs'; import puppeteer from 'puppeteer'; const LOGIN = 'your-login'; const PASSWORD = 'your-password'; // proxies.txt — one address per line: 123.45.67.89:8080 const proxies = fs.readFileSync('proxies.txt', 'utf8') .split('\n').map(s => s.trim()).filter(Boolean); const browser = await puppeteer.launch(); for (const address of proxies) { const context = await browser.createBrowserContext({ proxyServer: `http://${address}`, }); const page = await context.newPage(); await page.authenticate({ username: LOGIN, password: PASSWORD }); await page.goto('https://example.com'); // ... scrape ... await context.close(); } await browser.close();

And if your exported file already embeds credentials (login:password@IP:port lines), split each line once at @: the address part goes to proxyServer, the credentials to page.authenticate() — Chromium won't read them out of the proxy URL itself.

Our packages start at 100 IPs, so the list itself isn't the bottleneck — and when a target starts rate-limiting, refresh the list on request and re-export the file. Which of our proxy types fits Puppeteer best, with pricing and specs, is broken down on the proxies for Puppeteer page. If you'd rather not manage rotation in code at all, a rotating proxy flips the logic: you keep one gateway address in Puppeteer, and the exit IP rotates on every request.

If something doesn't work

The most common causes: ERR_TUNNEL_CONNECTION_FAILED or ERR_PROXY_CONNECTION_FAILED usually means the port doesn't match the authentication method (see the table) — or, on ports 8085/1085, that the machine's IP isn't whitelisted; net::ERR_INVALID_AUTH_CREDENTIALS or endless 407 responses mean page.authenticate() wasn't called before navigation, was called on a different page object, or carries a typo from the Dashboard credentials; and an authentication error on a SOCKS5 proxy means credentials were expected to work where Chrome doesn't send them — switch to port 1085 with whitelisting. If none of these is it, message us in live chat right on this page — we'll help you get connected.