Journal All articles

Setting up authenticated proxies in Playwright and Puppeteer

EngineeringOct 9, 2026·1 min read

02 Engineering

Launch options, per-page credentials and the one setting that quietly breaks authentication in headless mode.

Configure your connection

Both libraries support authenticated proxies, but they handle the credentials differently, and the difference is where most setups go wrong.

Playwright

Playwright takes the credentials at launch and handles the 407 challenge for you:

const browser = await chromium.launch({
  proxy: { server: "http://ADDRESS:PORT", username: "USERNAME", password: "PASSWORD" },
});

You can also pass proxy to browser.newContext() to give each context its own IP, which is the cleanest way to run one session per proxy.

Puppeteer

Puppeteer passes the proxy to Chrome as a flag and leaves authentication to you. Chrome has no flag for credentials, so you answer the challenge per page:

const browser = await puppeteer.launch({ args: ["--proxy-server=ADDRESS:PORT"] });
const page = await browser.newPage();
await page.authenticate({ username: "USERNAME", password: "PASSWORD" });

Call page.authenticate() before the first navigation on every new page, including pages opened by the site itself.

The setting that breaks it

Headless Chrome ignores proxy settings for localhost and, by default, bypasses the proxy for anything in --proxy-bypass-list. If your code sets a bypass list for development and forgets to clear it, requests silently go direct. Log the exit IP from a known endpoint at startup and assert it is not your own.

Avoiding the challenge entirely

If you control the machine’s public IP, switch the plan to IP allowlist. Chrome then needs only --proxy-server, and there is no per-page authentication to forget.