Use --proxy-server for the proxy host
Puppeteer does not use a proxy because a URL exists somewhere in your config. Chromium needs the proxy host at launch. Put the proxy server in --proxy-server. Put the username and password into page.authenticate().
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({
args: ['--proxy-server=http://proxynade.net:2555']
});
const page = await browser.newPage();
await page.authenticate({
username: process.env.PROXY_USER,
password: process.env.PROXY_PASS
});
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
Do not put user:pass@ in the launch argument. It often creates parser issues, shell quoting bugs, and leaked credentials in process lists.
Separate 407 from target blocking
A 407 Proxy Authentication Required comes from the proxy. A 403, captcha, or empty search result comes from the target. Mixing those two wastes time.
| Result | What it means | Next check |
|---|---|---|
407 | Credentials failed at the proxy | Check username, password, account balance, and special characters. |
| Navigation timeout | Target, network, or proxy route stalled | Test a small HTML endpoint before loading a heavy page. |
403 | The proxy connected, but target refused | Change headers, pacing, session age, or target policy. |
| Same IP every request | Browser reused the same tunnel | Restart the browser or create a new proxy assignment. |
Rotation needs process boundaries
Changing a string in memory does not guarantee a new exit. Chromium keeps connections alive. For strict per-task rotation, start a fresh browser with a fresh proxy assignment. For sticky sessions, keep the same browser open until the account flow is done.
If you only need lower bandwidth, block obvious waste after the page loads correctly.
await page.setRequestInterception(true);
page.on('request', request => {
const type = request.resourceType();
if (['image', 'font', 'media'].includes(type)) {
return request.abort();
}
return request.continue();
});
Remember that page.authenticate() turns on request interception internally. That can affect performance. Test throughput with the same auth path you use in production.
The proxy dashboard sees more than Puppeteer
Puppeteer reports what your page code observes. The proxy meter sees the lower-level traffic. Redirect chains, failed TLS attempts, blocked HTML, font files, and retries still count. That is why app-level counters often look cleaner than the bandwidth bill.
A minimal Puppeteer proxy health check
Before running the real target, run one small navigation through the same browser shape. Do not use a different HTTP library for the health check. That only proves the credentials, not Puppeteer.
async function proxyHealthCheck(page) {
const started = Date.now();
const response = await page.goto('https://example.com', {
waitUntil: 'domcontentloaded',
timeout: 30000
});
return {
status: response?.status(),
finalUrl: page.url(),
ms: Date.now() - started
};
}
If this check fails with 407, fix credentials. If it times out, test a smaller target and check routing. If it returns a target block, Puppeteer reached the site and the proxy setup is no longer the main question.
When to restart the browser
Restart for hard proxy rotation, major credential changes, and any test where you must prove a new exit. Keep the browser for one sticky account flow. Mixing those two goals causes the classic bug where the code says it rotated but Chromium is still using an old connection.
For queue workers, treat the browser as part of the proxy assignment. A task gets a proxy label, a browser instance, and a retry budget. When the task ends, close the browser. That costs startup time, but it makes rotation honest.
Puppeteer proxy FAQ
Where do proxy credentials go in Puppeteer? Put the proxy host in --proxy-server. Put credentials in page.authenticate() before navigation.
Can Puppeteer rotate proxies per request? Not cleanly at the browser tunnel level. For hard rotation, use a fresh browser or a fresh isolated task boundary.
Why does Puppeteer still use the old proxy? Chromium can keep sockets open. Close the browser between hard rotations.
Why does auth slow down Puppeteer? The authentication path uses request interception behind the scenes, which can affect throughput.
Production checks
- Keep credentials out of CLI args.
- Use one browser per hard proxy rotation.
- Set timeouts per navigation.
- Log status code and proxy label together.
- Stop retrying when the response is a stable block.