A Puppeteer proxy takes two things: the proxy address, which Chrome gets as a launch flag, and the username and password, which Puppeteer answers when the proxy asks. This guide shows the working setup with --proxy-server and page.authenticate(), how to give each browser context its own proxy, how to rotate IPs or keep sticky sessions with rotating residential proxies, and how to fix the errors people hit most often.
The short version
Launch Chrome with --proxy-server=http://HOST:PORT, then call await page.authenticate({ username, password }) before page.goto(). Don’t put the username and password in the proxy URL: Chrome ignores them there. For a different proxy per session, create a browser context with its own proxyServer. Chrome can’t use SOCKS5 with a password, so use the HTTP endpoint for authenticated proxies.
How Puppeteer Uses a Proxy
Chrome connects, Puppeteer answers the loginPuppeteer drives a real Chrome or Chromium browser, and it’s the browser, not your Node.js code, that opens the network connections. So the proxy has to be given to the browser. There are two places to do it: the --proxy-server command-line flag when you launch Chrome, which applies to the whole browser, or the proxyServer option when you create a browser context, which applies to that context only.
Authentication is a separate step. When Chrome connects to a proxy that needs a login, the proxy replies with 407 Proxy Authentication Required, and the browser has to answer with credentials. In a normal Chrome window that would be a sign-in box. In Puppeteer, page.authenticate() supplies the username and password so the page can load without anyone typing them.
puppeteer.launch({ args: ['--proxy-server=http://HOST:PORT'] })
|
v
Chrome ----- CONNECT ----> proxy (asks for a login: 407)
^ |
| v
page.authenticate({ username, password }) --> target website
The Puppeteer documentation describes page.authenticate() simply as providing credentials for HTTP authentication, and notes that request interception is turned on behind the scenes to do it, which can affect performance. For most scraping and testing jobs that cost is small.
Puppeteer Proxy with Authentication: the Standard Setup
Launch flag plus page.authenticate()This is the setup that works for almost everyone. Get your host, port, username and password from the Proxy Manager in your ProxyEmpire dashboard, then:
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({
args: ['--proxy-server=http://v2.proxyempire.io:5000'],
});
const page = await browser.newPage();
// Answer the proxy's login request for this page
await page.authenticate({
username: process.env.PROXY_USER,
password: process.env.PROXY_PASS,
});
await page.goto('https://api.ipify.org?format=json');
console.log(await page.evaluate(() => document.body.innerText));
await browser.close();
A few details that save time:
- Call
page.authenticate()on every page, before its firstgoto(). A new tab opened withbrowser.newPage()needs its own call. - Keep credentials out of your code. Read them from environment variables or a secrets manager. Anyone with your proxy username and password can spend your bandwidth.
- Use the IP check to confirm it works. The response should show an address and location from the proxy, not your own connection.
- Your username encodes your targeting. With ProxyEmpire, the username changes when you change country, city or session settings, so copy it again from the dashboard after any change.
Putting the credentials in the URL, as in --proxy-server=http://user:pass@host:port, is the most common mistake. Chrome doesn’t accept credentials in that flag, so the proxy keeps returning a 407 and pages fail to load.
A Different Proxy for Each Browser Context
One browser, many identitiesLaunching a new browser for every proxy is slow. Puppeteer’s browser.createBrowserContext() creates an isolated context that doesn’t share cookies or cache with other contexts, and its proxyServer option gives that context its own proxy. The Puppeteer docs note that the username and password for it are set with page.authenticate(), the same as before.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
async function openWithProxy(username) {
const context = await browser.createBrowserContext({
proxyServer: 'http://v2.proxyempire.io:5000',
});
const page = await context.newPage();
await page.authenticate({ username, password: process.env.PROXY_PASS });
return { context, page };
}
const a = await openWithProxy(process.env.PROXY_USER_US);
const b = await openWithProxy(process.env.PROXY_USER_DE);
await a.page.goto('https://example.com');
await b.page.goto('https://example.com');
await a.context.close();
await b.context.close();
await browser.close();
Because the ProxyEmpire username carries the targeting and session settings, two contexts with two usernames can browse from two different countries, or two different sticky IPs, in the same browser. createBrowserContext() is the current name for what older Puppeteer versions called createIncognitoBrowserContext(). If you are on an old version, upgrade or use the older name.
The proxy-chain Alternative
When page.authenticate() isn’t an optionSome setups can’t call page.authenticate(): service workers and some pop-ups, tools that wrap Puppeteer, or code that needs to pass one proxy URL to Chrome and forget about it. The open-source proxy-chain package solves this. Its anonymizeProxy() function starts a small local proxy with no password that forwards everything to your authenticated proxy, so Chrome only ever sees a local address.
import puppeteer from 'puppeteer';
import { anonymizeProxy, closeAnonymizedProxy } from 'proxy-chain';
const upstream = `http://${process.env.PROXY_USER}:${process.env.PROXY_PASS}@v2.proxyempire.io:5000`;
const localProxy = await anonymizeProxy(upstream); // e.g. http://127.0.0.1:12345
const browser = await puppeteer.launch({ args: [`--proxy-server=${localProxy}`] });
const page = await browser.newPage(); // no page.authenticate() needed
await page.goto('https://api.ipify.org?format=json');
await browser.close();
await closeAnonymizedProxy(localProxy, true);
If your password contains characters such as @, : or /, URL-encode it before building the upstream URL. Remember to close the local proxy when you’re done, or it keeps running in your process.
Rotating IPs and Sticky Sessions in Puppeteer
Match the session to the jobWith a rotating proxy endpoint, you choose the behaviour in the dashboard rather than in your code:
For a browser, sticky sessions are usually the right default. A single page load pulls in scripts, images and API calls, and if those come from different IPs, some sites treat the visit as suspicious. Use one sticky session per browser context, and switch to a new session, by using a new username from the dashboard, when you want a fresh identity.
Save bandwidth in Puppeteer
- Block images, fonts and media you don’t need with request interception, since you pay per GB of proxy traffic.
- Reuse a context for several pages on the same site instead of starting from zero each time.
- Close contexts and pages as soon as you’re done with them, so background requests stop.
- Where a site offers the same data in its HTML or a public API, a plain HTTP request is cheaper than a full browser.
Running many sessions in parallel
Scraping jobs often run dozens of pages at once. The pattern that scales is one browser, many contexts: each context gets its own proxy username and its own cookies, and a small pool of contexts works through a queue of URLs. ProxyEmpire has no limit on concurrent sessions, so the practical limit is your machine’s memory and CPU, not the proxy. Keep an eye on both, since every Chrome context and tab uses real resources.
Give each worker a clear lifecycle: create the context, authenticate its pages, process a batch of URLs, then close the context and start a fresh one with a new session. Recycling contexts regularly keeps memory in check and gives you a new IP at predictable points instead of in the middle of a job.
Checking that the proxy is really used
Before a long run, load an IP-check page in each context and log the result, as in the first example. If a context prints your own address, its proxy wasn’t applied; if it prints nothing, the login probably failed. A second quick check is to confirm the country matches the targeting you chose, since a working proxy in the wrong location still ruins location-specific data.
SOCKS5 and Puppeteer
Chrome can’t send a SOCKS5 passwordChrome accepts SOCKS proxies in --proxy-server, but it has no way to send a username and password to a SOCKS5 proxy. The feature request for SOCKS5 authentication in the Chromium issue tracker is closed without being implemented. If you pass an authenticated SOCKS5 proxy to Puppeteer, requests fail or the credentials are dropped.
The fix is simple: use the HTTP endpoint. ProxyEmpire supports HTTP, HTTPS and SOCKS5 on the same account, and HTTP with page.authenticate() still carries HTTPS traffic through an encrypted tunnel to the target site. If you really need SOCKS5 in the browser, proxy-chain can sit in the middle so Chrome talks to a local proxy without a password.
Troubleshooting Puppeteer Proxy Errors
The errors we see most| What you see | Likely cause and fix |
|---|---|
| ERR_TUNNEL_CONNECTION_FAILED | The proxy refused the tunnel, most often because of a missing or wrong login. Check that page.authenticate() runs before goto() and that the username matches your current targeting. |
| ERR_INVALID_AUTH_CREDENTIALS | The username or password is wrong, or was copied with a space. Copy both again from the Proxy Manager. |
| ERR_PROXY_CONNECTION_FAILED | Chrome can’t reach the proxy at all. Check the host and port, and that nothing on your network blocks outgoing connections to it. |
| Page shows your own IP | The proxy flag didn’t apply. Check the args array, and that you didn’t create the context without proxyServer. |
| Works for the first page, fails for new tabs | Each new page needs its own page.authenticate() call. |
| Some sites never load | Financial, government and other high-risk sites are blocked on the network, and only ports 80 and 443 are open by default. |
| Everything stops at once | Your bandwidth ran out. Check the balance in the dashboard and top up. |
If none of these fit, the proxy troubleshooting guide in our docs covers the rest, and the 24/7 live chat is answered by real people who can look at your setup with you.
Which Proxies to Use with Puppeteer
Match the IP type to the sitePuppeteer is often used on the sites that are hardest to reach, the ones that need JavaScript and check visitors closely. That makes the IP type matter.
- Rotating residential proxies come from real home broadband connections in 179 countries and territories, with targeting down to city, ZIP, ISP and ASN. They are the default choice for browser automation, from $3.50 for one GB down to $1.50/GB.
- Rotating mobile proxies use 4G carrier networks in 174 countries, from $2.00/GB. Use them for mobile sites, app back ends and anything that treats phones differently.
- Datacenter proxies are cheapest, from $0.35/GB, and fine for sites without strong bot checks.
All three work with the setup in this guide, and unused bandwidth rolls over on every plan. For more on running browsers at scale, see our guide to web automation with Chromium.
Puppeteer Proxy FAQ
Quick answersHow do I set a proxy in Puppeteer?
Pass --proxy-server=http://HOST:PORT in the args of puppeteer.launch(), or set proxyServer when you call browser.createBrowserContext() to give one context its own proxy.
How do I add a proxy username and password in Puppeteer?
Call await page.authenticate({ username, password }) on each page before it navigates. Don’t put the credentials in the --proxy-server URL, because Chrome ignores them there.
Can I use a different proxy for each page?
Use a separate browser context per proxy. Pages in the same context share its proxy; each context can have its own proxyServer and its own username.
Does Puppeteer support SOCKS5 proxies?
Chrome supports SOCKS5 without a password, but it can’t authenticate to a SOCKS5 proxy. For authenticated proxies, use the HTTP endpoint, or put proxy-chain in front of the SOCKS5 proxy.
Why do I get ERR_TUNNEL_CONNECTION_FAILED?
Usually the proxy rejected the login. Make sure page.authenticate() runs before page.goto(), and that the username matches the targeting you set in the dashboard.
Does page.authenticate() slow Puppeteer down?
It turns on request interception behind the scenes, which adds a little overhead. For most jobs the difference is small. If it matters, proxy-chain avoids it by handling the login outside the browser.
Does this work with Playwright too?
The idea is the same, but Playwright takes the proxy and its username and password together in the proxy option of launch() or newContext(). The ProxyEmpire host, port and credentials are the same.
References
Sources and documentation- Puppeteer —
Page.authenticate(). pptr.dev/api/puppeteer.page.authenticate - Puppeteer —
Browser.createBrowserContext(). pptr.dev/api/puppeteer.browser.createbrowsercontext - Puppeteer —
BrowserContextOptions(proxyServer,proxyBypassList). pptr.dev/api/puppeteer.browsercontextoptions - Chromium — Network settings (the
--proxy-serverflag). chromium.org/developers/design-documents/network-settings - Chromium issue tracker — SOCKS5 authentication support. issues.chromium.org/issues/40323993
- Apify — proxy-chain (
anonymizeProxy,closeAnonymizedProxy). github.com/apify/proxy-chain - MDN — 407 Proxy Authentication Required. developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/407
Run Puppeteer on residential and mobile IPs
Test the setup in this guide with the $1.97 trial: 100 MB of residential and 50 MB of mobile bandwidth, sticky or rotating sessions, targeting down to city and ISP, and 24/7 support from real people.














