How to Use a Proxy in Puppeteer, with Login (Tested)

Set a proxy in Puppeteer with --proxy-server, log in with page.authenticate(), give each browser context its own proxy and read the errors. Tested in Chrome.

TutorialsPublished Oct 25, 20264 min read

On this page

To use a proxy in Puppeteer, pass Chrome the --proxy-server=HOST:PORT flag when you launch, then call page.authenticate({ username, password }) on each page before it loads anything. The flag cannot carry a login: Chrome rejected a proxy URL with credentials in it in our test. For several proxies in one browser, create a browser context per proxy with createBrowserContext({ proxyServer }). Use HTTP or HTTPS proxies: Chrome does not log in to SOCKS5 proxies.

Puppeteer sets the proxy with --proxy-server=HOST:PORT and logs in with page.authenticate. Chrome sends CONNECT example.com:443, the proxy answers 407 Proxy Authentication Required, Chrome sends CONNECT again with a Proxy-Authorization header, and the proxy answers 200 Connection established. Our test proxy logged this 407-then-200 pair for each new connection
The flag sets the proxy; page.authenticate() answers the proxy's login challenge.

Before you start

bash
npm install puppeteer

puppeteer downloads a matching Chrome on install. The examples are ES modules (.mjs files) with top-level await.

Set a proxy for the whole browser

Save this as launch.mjs and run it with node launch.mjs:

js
import puppeteer from 'puppeteer';

const browser = await puppeteer.launch({
  args: ['--proxy-server=HOST:PORT'],
});

const page = await browser.newPage();
await page.authenticate({ username: 'USERNAME', password: 'PASSWORD' });
await page.goto('https://ipinfo.io/json');
console.log(await page.$eval('body', (body) => body.innerText));
await browser.close();

It prints the exit IP as JSON with its country, city and org. Three details:

  • Call authenticate() before goto(), on every page that uses the proxy. Without it, our test failed with net::ERR_INVALID_AUTH_CREDENTIALS.
  • Keep the login out of the flag. --proxy-server=http://USERNAME:PASSWORD@HOST:PORT failed with net::ERR_NO_SUPPORTED_PROXIES.
  • Write the scheme for HTTPS proxies. HOST:PORT alone means an HTTP proxy; for a proxy you reach over TLS, use --proxy-server=https://HOST:PORT.

The login has its own fields, so special characters in the password need no encoding.

Give each browser context its own proxy

A browser context is an isolated session with its own cookies and storage, like a separate profile. Passing proxyServer to createBrowserContext() gives each context its own proxy inside one browser. Each page still logs in with authenticate(). This is contexts.mjs:

js
import puppeteer from 'puppeteer';

const proxies = [
  { server: 'http://HOST_1:PORT_1', username: 'USERNAME_1', password: 'PASSWORD_1' },
  { server: 'http://HOST_2:PORT_2', username: 'USERNAME_2', password: 'PASSWORD_2' },
];

const browser = await puppeteer.launch();

for (const proxy of proxies) {
  const context = await browser.createBrowserContext({ proxyServer: proxy.server });
  const page = await context.newPage();
  await page.authenticate({ username: proxy.username, password: proxy.password });
  await page.goto('https://ipinfo.io/ip');
  console.log(proxy.server, '→', (await page.$eval('body', (body) => body.innerText)).trim());
  await context.close();
}

await browser.close();
One browser with three contexts: context 1 with proxyServer HOST_1:PORT_1 exits from IP A, context 2 with HOST_2:PORT_2 from IP B, context 3 with HOST_3:PORT_3 from IP C, each with its own cookies and storage
One browser, one proxy per context, each with its own cookies and storage.

To send some hosts around the proxy, add --proxy-bypass-list=localhost;internal.example to the launch arguments (semicolon-separated); in our test a bypassed site loaded even with the proxy pointed at a closed port.

Check the result, not just for exceptions

What each result means: goto resolving with status 407 means a wrong username or password; ERR_INVALID_AUTH_CREDENTIALS means page.authenticate was not called; ERR_NO_SUPPORTED_PROXIES means the login was put in the flag; ERR_SOCKS_CONNECTION_FAILED means a SOCKS5 proxy with a login, so use HTTP; ERR_PROXY_CONNECTION_FAILED means a wrong host or port, or a blocked connection
Every one of these came up in our tests.

The most surprising result: with a wrong password, page.goto() did not throw. It resolved, and response.status() was 407. A script that only catches exceptions will carry on with a page that never loaded. Check the status:

js
const response = await page.goto('https://ipinfo.io/json');
if (response.status() === 407) {
  throw new Error('The proxy rejected the username or password');
}

The other failures throw errors you can match on:

ErrorCause
net::ERR_INVALID_AUTH_CREDENTIALSNo login was given; call page.authenticate() before goto()
net::ERR_NO_SUPPORTED_PROXIESThe login was put in --proxy-server; move it to authenticate()
net::ERR_SOCKS_CONNECTION_FAILEDA SOCKS5 proxy that needs a login (see below)
net::ERR_PROXY_CONNECTION_FAILEDWrong host or port, or your network blocks the connection

To rule out the login itself, test the same proxy with curl; how to use curl with a proxy shows the command, and proxy not working covers the other causes.

Why SOCKS5 with a login fails

Our SOCKS5 test proxy logged what Chrome offered at the start of the connection: only the "no login" method. Chrome never sends a SOCKS5 username and password, so page.authenticate() cannot help. For proxies that need a login, use HTTP or HTTPS. The same limit applies to Playwright; see how to use a proxy in Playwright.

Mind background traffic on per-GB plans

Through our test proxy, Chrome also opened connections of its own to Google services while the script ran. On a proxy billed by traffic, that traffic counts too. It is small next to real pages, but in long runs it adds up; keep scripts short-lived, and block requests you do not need with request interception.

Using NanoIP proxies with Puppeteer

Residential and mobile proxies

Use --proxy-server=https://gateway.nanoip.net:1111 and page.authenticate() with the login of a route from the dashboard's Dynamic Routes page. Pick a sticky session for browser work: a page loads over several connections, and a sticky route keeps all of them, and the steps after, on one IP. For parallel contexts, generate one sticky route per context. Rotating vs sticky sessions explains the choice; see residential proxies.

ISP proxies

Each ISP proxy has its own host and port. Use --proxy-server=HOST:PORT (HTTP) with the IP's username and password. The IP stays the same for the whole plan, which suits contexts that sign in to the same account every day, as in account management.

Frequently asked questions

Can I change the proxy of a running page?

No. A page uses the proxy of its browser or context. Create a new context with the new proxy.

Does page.authenticate() apply to other pages?

No. Call it on every page that goes through a proxy needing a login, including pages opened later in the same context.

Puppeteer or Playwright for proxies?

Both work. Playwright takes the login in its proxy option and sets proxies per context in one call; Puppeteer needs the flag or proxyServer plus authenticate() on each page. Neither logs in to SOCKS5.

Written by NanoIP Team · Proxy infrastructure and customer support team

Related articles

Need help with your setup?

Our support team can check your proxy configuration with you.