How to Use a Proxy in Puppeteer
This tool empowers you to interact with web pages programmatically for tasks like web scraping, automated testing, capturing screenshots or PDFs, automating form submissions, and more. Puppeteer boasts navigation, DOM manipulation, network interception, and JavaScript execution within a web page’s context.
Puppeteer takes the proxy as a launch flag, and the credentials separately through page.authenticate. Putting the login into the flag is the mistake almost everyone makes first: Chromium ignores it.
args: [‘–proxy-server=gw.dataimpulse.com:823’]
});
const page = await browser.newPage();
await page.authenticate({ username: ‘LOGIN’, password: ‘PASSWORD’ });
await page.goto(‘https://ip-api.com/json’);
Below: the three setup methods, authentication, rotation, SOCKS5, how to confirm the exit IP, and what each error means.
What you need before you start
Node.js 18 or newer, npm, and a proxy account. Install Puppeteer in the folder that holds your package.json:
npm install puppeteer
| What | Value | Notes |
|---|---|---|
| Host | gw.dataimpulse.com | same gateway for residential and mobile |
| Port | 823 for HTTP, 10000 for SOCKS5 | the protocol and the port must match |
| Login and password | from your dashboard | passed through page.authenticate, never inside the flag |
| Targeting | appended to the login | LOGIN__cr.us;sid.job01;sessttl.600 |
Three ways to set a proxy in Puppeteer
They differ in scope: the whole browser, one browser context, or one request layer. Pick by what you need to vary.

Method 1: one proxy for the whole browser
The simplest form. Every page in this browser goes through the same exit IP.
const puppeteer = require('puppeteer');
const browser = await puppeteer.launch({
headless: 'new',
args: [
'--proxy-server=gw.dataimpulse.com:823',
'--no-sandbox'
]
});
const page = await browser.newPage();
await page.authenticate({ username: 'LOGIN', password: 'PASSWORD' });
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await browser.close();
Method 2: a different proxy per browser context
One browser process, several isolated contexts, each with its own exit IP and its own cookie jar. This is how you run
several accounts in parallel without giving them a shared fingerprint.
const context = await browser.createIncognitoBrowserContext({
proxyServer: 'gw.dataimpulse.com:823'
});
const page = await context.newPage();
await page.authenticate({ username: 'LOGIN__cr.us;sid.acct07', password: 'PASSWORD' });
Method 3: a new browser per session
The most wasteful in memory and the most reliable in isolation. Launch a browser per job, close it when the job ends,
and let the session id decide whether the next job reuses the same IP.
Puppeteer proxy authentication with username and password
This is where most setups break. Chromium does not accept credentials inside the proxy flag, so this does nothing:
// wrong: Chromium ignores the credentials in the flag
'--proxy-server=LOGIN:[email protected]:823'
Credentials go through the page instead, and they must be set before the first navigation:
const page = await browser.newPage();
await page.authenticate({ username: 'LOGIN', password: 'PASSWORD' });
await page.goto('https://ip-api.com/json');
Every new page needs its own call. If a page navigates before authenticate runs, the proxy answers with 407 and the
navigation fails. Targeting parameters live on the username, so country and session are set here too:
await page.authenticate({
username: 'LOGIN__cr.us;ci.newyork;sid.job01;sessttl.600',
password: 'PASSWORD'
});
Proxy rotation in Puppeteer
Rotation is not a Puppeteer feature, it is a property of the session id you send. No session id means a fresh IP per
request. A session id means the same IP for as long as the TTL holds.
// a fresh exit IP on every job
await page.authenticate({ username: 'LOGIN__cr.us', password: 'PASSWORD' });
// the same exit IP for ten minutes, per worker
const sid = `w${workerId}`;
await page.authenticate({
username: `LOGIN__cr.us;sid.${sid};sessttl.600`,
password: 'PASSWORD'
});
| Job | Session | Why |
|---|---|---|
| Crawling many pages of one site | no sid, rotating | spreads the load across the pool |
| A logged in session | sid plus sessttl | the site sees one stable location |
| Parallel workers | one sid per worker | workers never share an IP |
| Checkout or payment flows | sid, longer TTL | an IP change mid flow looks like a hijack |
The rule that matters: never reuse one session id across two accounts. Two accounts on one IP link
themselves together far more reliably than any fingerprint.
SOCKS5 in Puppeteer
Chromium supports SOCKS5 in the same flag, on a different port. The catch: Chromium does not send credentials for
SOCKS5, so authentication has to come from the network side.
args: ['--proxy-server=socks5://gw.dataimpulse.com:10000']
If your workflow does not specifically need SOCKS5, stay on HTTP through port 823. It authenticates cleanly through
page.authenticate and behaves the same for browser traffic.
How to check the proxy actually works
Do not trust a page that simply loaded. Print the exit IP from inside the browser, and compare it with what you asked for:
const page = await browser.newPage();
await page.authenticate({ username: 'LOGIN__cr.us', password: 'PASSWORD' });
await page.goto('https://ip-api.com/json');
console.log(await page.evaluate(() => document.body.innerText));
// expect countryCode: "US"
- Country and city must match the targeting you sent, not your server location.
- Timezone and locale should follow the exit node. Chromium does not change them by itself, so set them
explicitly if the target site checks. - Sticky sessions should return the same address twice in a row. If they do not, the TTL is too short
or the session id changed.
Puppeteer proxy not working: errors and fixes
| What you see | What it actually is | Fix |
|---|---|---|
| ERR_TUNNEL_CONNECTION_FAILED | credentials never reached the proxy | call page.authenticate before the first goto, on every page |
| 407 Proxy Authentication Required | wrong password, or no traffic left on the plan | re-copy the password without spaces, check the balance |
| ERR_PROXY_CONNECTION_FAILED | protocol and port do not match | 823 for HTTP, 10000 for SOCKS5 |
| Navigation timeout | the target is slow through a residential hop | raise the timeout and wait for domcontentloaded rather than networkidle |
| Works once, fails after a restart | credentials in the launch flag instead of authenticate | move them to page.authenticate |
| Exit IP is not the country you asked for | targeting written into the wrong field | parameters belong on the username after two underscores |
The npm route: proxy-chain for authenticated proxies
page.authenticate covers the browser. It does not cover a request made outside the page context, and it does not help
when a library you use launches Chromium for you. The usual answer is proxy-chain: it opens a local anonymous proxy that
forwards to the authenticated one, so Chromium only ever sees a clean host and port.
npm install proxy-chain
const proxyChain = require('proxy-chain');
const upstream = 'http://LOGIN__cr.us;sid.job01:[email protected]:823';
const local = await proxyChain.anonymizeProxy(upstream);
const browser = await puppeteer.launch({
args: [`--proxy-server=${local}`]
});
// ... work ...
await browser.close();
await proxyChain.closeAnonymizedProxy(local, true);
Two things people forget here. Close the anonymized proxy when the browser closes, otherwise the local ports pile up
across a long run. And keep one anonymized proxy per session id, not one shared by all workers, or every worker ends up
on the same exit IP.
A proxy alone does not stop blocks
A residential IP fixes the network signal. It does nothing about the browser signal, and Puppeteer announces itself
loudly by default: navigator.webdriver is set, the user agent says HeadlessChrome, and the viewport is a default size no
real user has.
npm install puppeteer-extra puppeteer-extra-plugin-stealth
const puppeteer = require('puppeteer-extra');
const stealth = require('puppeteer-extra-plugin-stealth');
puppeteer.use(stealth());
const browser = await puppeteer.launch({
args: ['--proxy-server=gw.dataimpulse.com:823', '--window-size=1366,768']
});
| Signal | What gives you away | What to align it with |
|---|---|---|
| IP | datacenter range on a consumer site | residential or mobile exit node |
| Timezone | your server timezone with a US exit IP | the country you targeted |
| Locale and Accept-Language | en-US on a German exit node | the country you targeted |
| User agent | HeadlessChrome in the string | a current desktop Chrome string |
| Viewport | 800 by 600 default | a common real resolution |
The order matters: set the proxy first, then align the rest to the exit node. An American IP with a Kyiv timezone is a
worse signal than no proxy at all.
Running it at scale without burning traffic
Residential traffic is billed by the gigabyte, and a browser is an expensive client: it fetches images, fonts, video
and analytics on every page. Blocking what you do not parse is the single biggest saving in any Puppeteer job.
await page.setRequestInterception(true);
page.on('request', req => {
const type = req.resourceType();
if (['image', 'media', 'font', 'stylesheet'].includes(type)) return req.abort();
req.continue();
});
On a typical product page this cuts the transfer by a large factor, because images and fonts are most of the weight.
Keep stylesheets if the site renders content through CSS driven layout, drop them otherwise.
For parallel work, give every worker its own session id and its own browser context, and cap concurrency to what your
plan allows rather than to what your machine allows. Ten workers on one session id is one IP taking ten times the load,
which is exactly the pattern anti-bot systems are built to catch.
FAQ
How do I use a proxy in Puppeteer?
Pass the proxy as a launch argument, then send the credentials with page.authenticate before the first navigation. The launch flag carries only host and port; a login written into the flag is ignored by Chromium.
How do I set a Puppeteer proxy with authentication?
Call page.authenticate with your username and password on every page you create, before goto. Targeting parameters such as country or session id are appended to the username, not passed separately.
How do I rotate proxies in Puppeteer?
Rotation depends on the session id you send. Omit the session id for a new exit IP on each connection, or set sid and sessttl to keep the same address for a fixed time.
Does Puppeteer support SOCKS5 proxies?
Yes, through –proxy-server=socks5://host:port. Chromium does not send SOCKS5 credentials, so authentication has to be handled on the network side. HTTP on port 823 is the simpler path.
Why does Puppeteer return ERR_TUNNEL_CONNECTION_FAILED?
The proxy refused the connection because it never received valid credentials. In almost every case page.authenticate was called too late, was skipped for that page, or the credentials were placed in the launch flag.
Running the same proxies elsewhere? See our setups for Scrapy, aiohttp and Multilogin, or read how residential proxies and mobile proxies differ for automated browsers.

State/City/Zip/ASN Targeting 



