A Puppeteer proxy takes two steps: start Chrome with --proxy-server=http://HOST:PORT, then call page.authenticate() with the username and password before the first navigation. Chrome itself will not take credentials in the flag, so the second step is not optional.

const browser = await puppeteer.launch({ args: ['--proxy-server=http://HOST:PORT'] });
const page = await browser.newPage();
await page.authenticate({ username: 'USERNAME', password: 'PASSWORD' });
await page.goto('https://api.ipify.org?format=json');

The snippets on this page ran on 2026-09-29 with Puppeteer 25.12 driving Chrome 154, against two local authenticating HTTP proxies and a SOCKS5 proxy that enforces credentials.

Before you start: copy your proxy details

  1. Open your order in the dashboard at https://app.proxyhive.io.
  2. Copy HOST, PORT, USERNAME and PASSWORD. Each ISP or datacenter IP is its own endpoint, with its own HTTP, HTTPS and SOCKS5 ports on the order. Use the HTTP port with Puppeteer unless you allowlist your IP.
  3. Export them:
export PROXY_SERVER="http://HOST:PORT" PROXY_USER=USERNAME PROXY_PASS=PASSWORD

For long-lived browser sessions, static ISP proxies keep one address for the whole term, sold from a single IP.

Puppeteer proxy with --proxy-server

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch({
  args: [`--proxy-server=${process.env.PROXY_SERVER}`],
});
const page = await browser.newPage();
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();

--proxy-server is a Chrome flag, so it applies to the whole browser. Add --proxy-bypass-list=localhost;*.internal.example for hosts that should go direct.

Puppeteer proxy authentication with page.authenticate

What we saw with each way of passing credentials:

ApproachResult in our run
--proxy-server=http://HOST:PORT plus page.authenticate()Works
--proxy-server=http://HOST:PORT, no authenticatenet::ERR_INVALID_AUTH_CREDENTIALS
page.authenticate() with a wrong passwordNo exception: goto resolves with status 407
--proxy-server=http://USER:PASS@HOST:PORTnet::ERR_NO_SUPPORTED_PROXIES

Two things to know about page.authenticate():

  • It is per page. A page opened later, including a popup, needs its own call.
  • The Puppeteer API reference notes it turns on request interception behind the scenes, which can slow pages a little. The allowlist below avoids that.

IP allowlist: no credentials at all

On a server with a fixed public IP, switch the proxy IP to allowlist authentication on the order in the dashboard and add the server's address. Then --proxy-server=http://HOST:PORT is the whole setup, with no authenticate call, no interception, and nothing secret in your code. For browsers this is the cleanest option.

A proxy per browser context: createBrowserContext({ proxyServer })

To run several IPs from one Chrome process, give each browser context its own proxy:

import puppeteer from 'puppeteer';

const servers = process.env.PROXY_SERVERS.split(',');
const browser = await puppeteer.launch();

for (const proxyServer of servers) {
  const context = await browser.createBrowserContext({ proxyServer });
  const page = await context.newPage();
  await page.authenticate({
    username: process.env.PROXY_USER,
    password: process.env.PROXY_PASS,
  });
  await page.goto('https://api.ipify.org?format=json');
  console.log(proxyServer, await page.evaluate(() => document.body.innerText));
  await context.close();
}

await browser.close();

Our run sent the two contexts through their two proxies from one browser. createBrowserContext() is the current name; code written for Puppeteer before v22 calls it createIncognitoBrowserContext(). Each context has separate cookies and storage, so one IP's session never bleeds into another's.

This rotates per context, not per request, which suits browsers: a login that hops addresses mid-session looks worse than one that stays put. Rotating vs static proxies covers when per-request rotation is still the right call.

Puppeteer SOCKS5 proxy: no authentication

--proxy-server=socks5://HOST:PORT works for an allowlisted IP. With credentials it does not: our SOCKS server logged that Chrome offered only the "no authentication" method, and the page failed with net::ERR_SOCKS_CONNECTION_FAILED. page.authenticate() never gets a chance, because SOCKS auth happens before any HTTP. So:

  1. HTTP port with page.authenticate(), or
  2. allowlist your IP and use socks5://HOST:SOCKS5_PORT with no credentials.

HTTP vs SOCKS5 proxies explains why the HTTP port loses a browser nothing.

Verify the exit IP

The snippets print the address from https://api.ipify.org?format=json. Compare it with the IP on your order. If it shows your own address, the flag did not reach Chrome: check args and that you are not connecting to an already-running browser with puppeteer.connect(), which ignores launch flags.

Timeouts and retries

page.setDefaultNavigationTimeout(45_000);

for (let attempt = 1; attempt <= 3; attempt++) {
  try {
    await page.goto(url, { waitUntil: 'domcontentloaded' });
    break;
  } catch (err) {
    if (attempt === 3) throw err;
  }
}

The first navigation in a context is the slowest, since it pays for the proxy handshake. domcontentloaded avoids waiting on third-party requests that never settle.

Common Puppeteer proxy errors

ErrorCauseFix
net::ERR_INVALID_AUTH_CREDENTIALSNo page.authenticate() on this pageCall it on every page, popups included
goto resolves, but response.status() is 407Wrong username or password passed to authenticateRe-copy the credentials; check the status, since nothing throws
net::ERR_NO_SUPPORTED_PROXIESCredentials inside --proxy-serverScheme, host and port only
net::ERR_SOCKS_CONNECTION_FAILEDSOCKS5 with credentialsHTTP port, or allowlist
net::ERR_PROXY_CONNECTION_FAILEDWrong host or portRe-copy the port for your protocol

For status codes returned by the site, see proxy error codes.

Next steps