Why authentication is the first Puppeteer proxy hurdle
Nearly every paid proxy requires authentication, and nearly everyone's first attempt in Puppeteer looks like this:
// This does NOT work
const browser = await puppeteer.launch({
args: ['--proxy-server=http://USER:PASS@gate.example.com:7777'],
});Chrome ignores credentials in the --proxy-server flag. The result is a 407 Proxy Authentication Required error, an ERR_INVALID_AUTH_CREDENTIALS, or an authentication dialog in headful mode. This guide covers the methods that do work. For the full Puppeteer proxy picture, see our Puppeteer proxies pillar guide.
Method 1: page.authenticate()
The standard approach. Launch with the proxy host and port only, then give each page the credentials:
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({
args: ['--proxy-server=http://gate.example.com:7777'],
});
const page = await browser.newPage();
await page.authenticate({ username: 'USER', password: 'PASS' });
await page.goto('https://httpbin.org/ip', { waitUntil: 'domcontentloaded' });
console.log(await page.evaluate(() => document.body.innerText));
await browser.close();How it works: when Chrome receives a 407 challenge from the proxy, Puppeteer intercepts it and responds with your credentials.
Things to know:
- Call it on every page before the first navigation. New pages, including popups, don't inherit credentials automatically, so handle
targetcreatedevents if your flow opens new tabs. - It works for HTTP/HTTPS proxies, not SOCKS5.
- Changing credentials on an existing page (for example, to switch session IDs) may not take effect immediately because Chrome caches proxy auth and reuses connections. For a fresh IP, use a new browser context or browser.
Handling popups:
browser.on('targetcreated', async (target) => {
if (target.type() === 'page') {
const p = await target.page();
if (p) await p.authenticate({ username: 'USER', password: 'PASS' });
}
});Method 2: IP whitelisting
If your server has a fixed public IP, add it to your provider's whitelist. Then no credentials are needed:
const browser = await puppeteer.launch({
args: ['--proxy-server=http://gate.example.com:7777'],
});Pros: simplest code, works with SOCKS5, no credential handling. Cons: only works from whitelisted IPs, and targeting must be set by port or dashboard rather than username. See proxy authentication methods.
Method 3: proxy-chain local forwarder
The proxy-chain package starts a local proxy without authentication that forwards to your upstream proxy with credentials:
import puppeteer from 'puppeteer';
import proxyChain from 'proxy-chain';
const upstream = 'http://customer-USER-country-us-session-abc123:PASS@gate.example.com:7777';
const local = await proxyChain.anonymizeProxy(upstream); // e.g. http://127.0.0.1:41235
const browser = await puppeteer.launch({ args: [`--proxy-server=${local}`] });
const page = await browser.newPage();
await page.goto('https://httpbin.org/ip');
console.log(await page.evaluate(() => document.body.innerText));
await browser.close();
await proxyChain.closeAnonymizedProxy(local, true);Use this when:
- You need authenticated SOCKS5 (proxy-chain can forward to SOCKS upstreams in recent versions).
- You want credentials out of Puppeteer code paths entirely.
- You use tools on top of Puppeteer that don't expose
page.authenticate().
Method 4: Per-context proxies with credentials
Recent Puppeteer versions let each browser context use its own proxy server. Combine with page.authenticate() on pages in that context:
const browser = await puppeteer.launch();
const ctx = await browser.createBrowserContext({ proxyServer: 'http://gate.example.com:7777' });
const page = await ctx.newPage();
await page.authenticate({ username: 'customer-USER-session-1', password: 'PASS' });
await page.goto('https://httpbin.org/ip');This is the cleanest way to run several identities in one browser. See Puppeteer proxy per page.
Special characters in passwords
With page.authenticate(), pass the raw password. With URL-based configuration (proxy-chain), URL-encode special characters:
const pass = encodeURIComponent('p@ss:w/rd');
const upstream = `http://USER:${pass}@gate.example.com:7777`;Proxy tip: If you need a pool to run this against, Geonode works with every tool in this guide: rotate per request for stateless pages, or hold a sticky session for logins and pagination. See using Geonode with Puppeteer for working code.
Troubleshooting authentication
| Symptom | Likely cause | Fix |
|---|---|---|
407 or ERR_INVALID_AUTH_CREDENTIALS | Credentials in flag, wrong password, authenticate() not called | Use page.authenticate() before goto() |
| Works on first page, fails on popups | New pages lack credentials | Handle targetcreated |
| Session ID change has no effect | Connection reuse / auth cache | New context or browser per session |
| SOCKS5 with credentials fails | Chrome lacks SOCKS5 auth | Whitelist IP or use proxy-chain |
| Works locally, fails on server | Server IP not whitelisted | Update whitelist |
More: common proxy errors.
Rotating credentials safely
When you rotate session IDs in the username, follow this sequence for reliable results:
- Create a new browser context (or browser).
- Open a page in it.
- Call
page.authenticate()with the new session username. - Navigate.
- Close the context when finished.
Reusing a context after changing credentials risks Chrome reusing a pooled connection authenticated with the old session.
Keeping credentials out of code
const { PROXY_HOST, PROXY_USER, PROXY_PASS } = process.env;
const browser = await puppeteer.launch({ args: [`--proxy-server=${PROXY_HOST}`] });
const page = await browser.newPage();
await page.authenticate({ username: PROXY_USER, password: PROXY_PASS });Use environment variables or a secrets manager, and mask credentials in logs.
Authentication with request interception
If you use page.setRequestInterception(true) for resource blocking, page.authenticate() still works. Call authenticate() before enabling interception and navigating, and make sure your request handler always calls continue() or abort() for every request, including the initial navigation.
Checking that authentication succeeded
Navigate to an IP echo endpoint and confirm the IP isn't yours:
await page.goto('https://api.ipify.org');
console.log('exit IP:', await page.evaluate(() => document.body.innerText));If you see your own IP, the proxy isn't being applied. If navigation fails with an auth error, credentials are wrong or not being sent. See how to check if a proxy works.
The hidden cost of page.authenticate()
page.authenticate() is the easiest method, but it isn't free. Puppeteer's own documentation notes that request interception is turned on behind the scenes to implement authentication, and that this might affect performance. Every request the page makes passes through that interception layer.
For a handful of pages, you won't notice. At scale, it can matter:
| Situation | Better option |
|---|---|
| Fixed servers with stable IPs | IP whitelisting: no credentials, no interception |
| Many pages per browser, heavy pages | A local forwarder (such as proxy-chain) that adds credentials outside the browser |
| Per-context proxies with different users | page.authenticate() per page is still the simplest; measure before optimising |
| SOCKS5 with credentials | A forwarder, since Chrome can't authenticate to SOCKS5 |
To see whether it matters for you, time the same batch of pages twice: once with page.authenticate() and once through a whitelisted endpoint or a local forwarder. Compare median page load time and CPU use. If the difference is small, keep the simple approach.
The method signature and notes are on the official Page.authenticate reference. For forwarders, see Method 3 above and proxy chains; for whitelisting, proxy authentication methods. And if you're scaling up, scaling Puppeteer with puppeteer-cluster covers worker design.
Key takeaways
- Chrome ignores credentials in
--proxy-server; usepage.authenticate(), IP whitelisting or a local forwarder. - Call
page.authenticate()on every new page, including popups, before navigation. - For new session IDs, use a new browser context or browser to avoid reused connections.
- SOCKS5 authentication isn't supported by Chrome; use whitelisting or proxy-chain.
- Keep credentials in environment variables and mask them in logs. See puppeteer proxies.
FAQ
Can I put credentials in the --proxy-server flag?
No, Chrome ignores them.
Does page.authenticate() work with SOCKS5?
No. Use IP whitelisting or a local forwarder. See Puppeteer SOCKS5 proxy.
Which method is best?
IP whitelisting on fixed servers; page.authenticate() otherwise; proxy-chain for SOCKS5 or complex setups.
Editorial note: Proxy providers change their pools, features and prices often, so treat any figures here as typical ranges at the time of writing and confirm current terms with the provider before you buy.