Skip to content

Guide

Puppeteer proxy with authentication

This is the one that catches everybody. You pass the proxy in a launch argument, it looks right, and every page load fails or hangs. The reason is that Chrome ignores credentials embedded in the proxy server flag, so the proxy is set but you are never authenticating. Credentials go through page.authenticate instead.

Credentials do not go in the launch flag

Writing user and password into the proxy server argument is the natural thing to try and it silently does nothing. Chrome accepts the host and port from that flag and discards everything else, which is why the failure looks like a broken proxy rather than a rejected login.

javascript
// This looks correct and does not work.
const browser = await puppeteer.launch({
  args: ['--proxy-server=http://USERNAME:[email protected]:80'],
})

The setup that works

Host and port in the launch argument, credentials through page.authenticate. Call authenticate before the first navigation, and call it on every page you open, because it applies per page rather than per browser.

javascript
import puppeteer from 'puppeteer'

const browser = await puppeteer.launch({
  args: ['--proxy-server=gate-eu.proxies.nullvault.shop:80'],
})

const page = await browser.newPage()
await page.authenticate({ username: 'USERNAME', password: 'PASSWORD' })

await page.goto('https://api.ipify.org?format=json')
console.log(await page.content())

await browser.close()

One address for the whole browser session

A browser is a session by nature. Cookies, storage and fingerprint all persist across navigations, so the exit address should persist too. Use a sticky session rather than a rotating one, otherwise the site sees one visitor whose country changes between clicks, which is a stronger signal than any single request would be.

javascript
// Sticky, so the address survives the whole browser session.
// Residential holds up to six hours, mobile up to 120 minutes.
args: ['--proxy-server=gate-eu.proxies.nullvault.shop:80']

Skip images if you are paying by the gigabyte

A browser downloads everything a browser downloads, and on a metered plan the images and fonts are billed exactly like the HTML you actually wanted. Blocking the request types you do not need often cuts traffic by most of the page weight and costs nothing in accuracy for scraping work.

javascript
await page.setRequestInterception(true)
page.on('request', (req) => {
  const type = req.resourceType()
  if (type === 'image' || type === 'font' || type === 'media') return req.abort()
  req.continue()
})

What to buy

Residential sticky for anything that has to look like a person using a browser. If you are only rendering pages nobody is guarding, datacenter is a great deal cheaper and works fine.

Common questions

Why is my proxy ignored in Puppeteer?

Because credentials in the proxy server flag are discarded by Chrome. The host and port take effect, so the browser is genuinely using the proxy, it just never authenticates and every request is refused. Move the credentials into page.authenticate and it works.

Do I need to authenticate on every page?

Yes. It applies per page, not per browser, so a page opened later in the run needs its own call. A common bug is authenticating the first page and then having every popup or new tab fail.

Can I use a different proxy per page?

Not in one browser instance, because the proxy is set when Chrome launches. Launch a browser per proxy, or use a rotating gateway if what you actually want is a fresh address per session rather than per page.

Try it against your own target

Metered bandwidth is billed per gigabyte and does not expire, so testing a small amount against the site you actually care about costs very little.

More guides