Skip to content
Automation

Puppeteer Proxies: Why Chromium Ignores Your Password, and What to Do About It

Chromium's `--proxy-server` flag does not accept credentials, and it never has. Putting `user:pass@` in front of the host does not fail loudly — the userinfo is simply discarded, the request goes out unauthenticated, and the proxy answers 407. Puppeteer's `page.authenticate()` solves this for a single identity, and understanding why it does not solve it for several is the difference between a demo and a working crawler.

seamless Team10 min readAugust 9, 2026
  • puppeteer
  • automation
  • browser automation
  • 407
A chamber anchored to one fixed conduit that glows crimson at the joint, with a second conduit lying unconnected beside it

If you only need the working setup, the Puppeteer integration page has it. This article is about the constraint underneath it, because that constraint determines your architecture rather than just your configuration.

What actually happens

javascript
// Looks reasonable. Chromium throws the credentials away.
const browser = await puppeteer.launch({
  args: ["--proxy-server=http://USERNAME:PASSWORD@PROXY_HOST:PROXY_PORT"],
});

Chromium parses that flag as a host and port and ignores everything before the @. The first navigation reaches the proxy without a Proxy-Authorization header, the proxy replies with a 407 challenge, and Chromium — having no credentials to offer — renders an error page. Nothing in the launch call warned you.

javascript
const browser = await puppeteer.launch({
  args: ["--proxy-server=http://PROXY_HOST:PROXY_PORT"],
});

const page = await browser.newPage();
await page.authenticate({ username: "USERNAME", password: "PASSWORD" });
await page.goto("https://api.ipify.org?format=json");
authenticate() registers a handler for the 407 challenge. It must run before the first navigation.

page.authenticate() is scoped to that page. Every new page needs its own call, and a page opened by target=_blank will not inherit it — a common cause of a crawl that works for the first result and fails on the rest.

The constraint that shapes everything else

The proxy is a browser-process launch flag, so it cannot be changed at runtime. Puppeteer has no equivalent of Playwright's per-context proxy: browser.createIncognitoBrowserContext() isolates cookies and storage, but every context in the process still exits through the same address.

What you needWhat it costs in Puppeteer
One exit, many pagesNothing — the default case
Isolated cookies, same exitAn incognito context per identity
A different exit per identityA separate browser process, or a local relay
Rotation mid-sessionRestart the browser, or route through a relay

Pattern 1: one browser per identity

javascript
const crypto = require("crypto");

async function browserWithFreshExit() {
  const tag = crypto.randomBytes(4).toString("hex");
  const browser = await puppeteer.launch({
    args: ["--proxy-server=http://PROXY_HOST:PROXY_PORT"],
  });
  const page = await browser.newPage();
  await page.authenticate({
    username: `USERNAME-session-${tag}`,
    password: "PASSWORD",
  });
  return { browser, page };
}

Simple and correct, and the right answer for small concurrency. The cost is real though: a Chromium process is 100–300MB, so twenty identities is several gigabytes of RAM and twenty cold starts.

Pattern 2: a local relay

Point Chromium at http://127.0.0.1:PORT — a local proxy with no authentication — and let that process add the upstream credentials and choose the exit per connection. Chromium never learns that the exit changed, so one browser serves many identities without a restart.

  • No credential handling in Chromium at all, so page.authenticate() disappears from your code.
  • The exit can change per connection, driven by whatever logic you put in the relay.
  • One browser process, so memory stays flat as concurrency grows.
  • The relay is one more moving part, and it is now in the path of every request.

This is what most 'rotating proxy for Puppeteer' packages do internally. Knowing that is useful: it tells you where to look when one of them misbehaves.

Pattern 3: per-request routing

For collecting data rather than driving a UI, the browser does not need to fetch everything. Intercept requests and pull the ones you care about with a plain HTTP client through whichever exit you like, leaving Chromium to render.

javascript
await page.setRequestInterception(true);
page.on("request", (req) => {
  if (["image", "font", "media"].includes(req.resourceType())) return req.abort();
  return req.continue();
});
Aborting assets you never read is the fastest way to cut residential bandwidth spend.

Static exits, no rotation to manage

One stable residential-registered IP per worker means no relay, no restarts and no session bookkeeping. ISP proxies from €1.80/IP.

See ISP proxies

Verifying, and the headless tell

Confirm the exit from inside the browser, then confirm the browser does not announce itself. Old headless Chrome shipped HeadlessChrome in the User-Agent and left navigator.webdriver set to true; the modern headless mode fixed much of that, but only if you use it.

javascript
const browser = await puppeteer.launch({
  headless: "new",
  args: [
    "--proxy-server=http://PROXY_HOST:PROXY_PORT",
    "--disable-blink-features=AutomationControlled",
  ],
});

Beyond that, resist the urge to spoof aggressively. A User-Agent that disagrees with the TLS handshake or the rendered feature set is a stronger signal than the default would have been — the reasoning is set out in how to avoid getting blocked.

Symptom to cause

SymptomCause
407 on first navigationCredentials in --proxy-server, or authenticate() called too late
Works on page one, fails on page twoauthenticate() not called on the new page
ERR_TUNNEL_CONNECTION_FAILEDWrong port, wrong protocol, or the exit refused CONNECT
Real IP shows throughFlag missing from args, or NO_PROXY excludes the host
Every identity shares one exitWorking as designed — the proxy is per process

The whole subject reduces to one design decision: whether the browser or something in front of it owns the proxy. Puppeteer makes that decision for you at launch, which is precisely why it is worth making deliberately.

Frequently asked questions

Why does Puppeteer ignore my proxy username and password?

Chromium's --proxy-server flag accepts only a host and port. Anything before the @ is discarded without warning, so the request goes out unauthenticated and the proxy responds with 407. Use page.authenticate() before the first navigation instead.

Can I use a different proxy per page in Puppeteer?

Not directly. The proxy is a launch argument for the whole browser process, and incognito contexts isolate cookies but share the exit. Run one browser per identity, or point Chromium at a local relay that picks the upstream exit per connection.

How do I rotate proxies in Puppeteer without restarting the browser?

Route through an unauthenticated local proxy that holds the upstream credentials and selects the exit itself. Chromium keeps talking to 127.0.0.1 while the relay changes the exit underneath, which is how most rotating-proxy plugins work internally.

Does page.authenticate work for popups and new tabs?

No. It applies to the page you called it on. A page opened by target=_blank or window.open starts without it, which is why crawls often succeed on the first result and fail on everything opened from it.

What causes ERR_TUNNEL_CONNECTION_FAILED with a proxy?

The CONNECT request to the proxy failed. The usual causes are the wrong port for the protocol, a SOCKS endpoint addressed as HTTP, credentials that were never sent, or an exit that refused the tunnel.

Is Puppeteer or Playwright better for proxy work?

Playwright, if you need several exits at once, because it supports a proxy per browser context and handles credentials natively. Puppeteer is perfectly adequate when one process needs one exit.

SE
seamless Team
Proxy infrastructure

The seamless team runs residential, ISP and datacenter proxy infrastructure and writes these guides from day-to-day operational experience.

Ready to try seamless proxies?

Residential, ISP and datacenter proxies with no data expiry.

Browse Plans