A Playwright proxy can be set in two places: on the whole browser at launch, or on each browser context. Puppeteer works similarly but handles credentials differently. Knowing which level to use decides whether one script can run several countries side by side, or whether every account ends up sharing the same IP. This guide shows working setups for both tools with an authenticated dedicated proxy, then covers SOCKS5, verification and the errors you are most likely to hit.
Where the proxy lives: launch, context or page
Before writing code, decide how many identities one script needs.
| Level | Playwright | Puppeteer | Use when |
|---|---|---|---|
| Browser launch | launch({ proxy }) | --proxy-server launch argument | One IP for the whole run |
| Browser context | newContext({ proxy }) | createBrowserContext({ proxyServer }) | Several IPs or accounts in one browser process |
| Page credentials | Not needed, set in proxy | page.authenticate() | Puppeteer only, per page |
A browser context is an isolated session with its own cookies, storage and cache. Pairing one context with one dedicated IP is the cleanest way to keep identities apart, which is the same principle behind the dedicated proxy guide: one IP, one purpose.
In the examples, replace HOST, PORT, USERNAME and PASSWORD with the values shown for each proxy in the Cloudj console.
Playwright proxy at launch (Node.js)
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({
proxy: {
server: 'http://HOST:PORT',
username: 'USERNAME',
password: 'PASSWORD',
},
});
const page = await browser.newPage();
await page.goto('https://api.ipify.org?format=json');
console.log(await page.textContent('body'));
await browser.close();
})();
Playwright handles the 407 Proxy Authentication Required challenge for you, so there is no extra step for credentials. The printed IP should be the proxy's address, not your own.
One proxy per context
This is where Playwright shines. Each context gets its own proxy, locale and timezone, so a Japan session and a United States session can run in the same browser without sharing anything:
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch();
const jp = await browser.newContext({
proxy: { server: 'http://JP_HOST:PORT', username: 'USERNAME', password: 'PASSWORD' },
locale: 'ja-JP',
timezoneId: 'Asia/Tokyo',
});
const us = await browser.newContext({
proxy: { server: 'http://US_HOST:PORT', username: 'USERNAME', password: 'PASSWORD' },
locale: 'en-US',
timezoneId: 'America/New_York',
});
for (const context of [jp, us]) {
const page = await context.newPage();
await page.goto('https://api.ipify.org?format=json');
console.log(await page.textContent('body'));
}
await browser.close();
})();
Set locale and timezoneId to match where the IP is. A Tokyo IP reporting a Vietnam timezone is an obvious inconsistency to any site that checks. The ad verification proxy guide uses exactly this pattern to check localized landing pages.
The same in Python
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch()
context = browser.new_context(
proxy={"server": "http://HOST:PORT", "username": "USERNAME", "password": "PASSWORD"},
locale="ja-JP",
timezone_id="Asia/Tokyo",
)
page = context.new_page()
page.goto("https://api.ipify.org?format=json")
print(page.inner_text("body"))
browser.close()
The full list of proxy options is in the Playwright network documentation.
Puppeteer proxy with authentication
Chromium does not accept credentials inside the --proxy-server flag, so Puppeteer splits the job in two: the server goes in the launch arguments and the credentials go to page.authenticate(). Save this as check.mjs so top-level await works:
import puppeteer from 'puppeteer';
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');
console.log(await page.evaluate(() => document.body.innerText));
await browser.close();
Call authenticate() on every new page before the first navigation. Forgetting it on a second tab is the most common cause of a sudden 407.
Per-context proxies in Puppeteer
Recent Puppeteer versions accept a proxy per browser context:
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
const context = await browser.createBrowserContext({ proxyServer: 'http://HOST:PORT' });
const page = await context.newPage();
await page.authenticate({ username: 'USERNAME', password: 'PASSWORD' });
await page.emulateTimezone('Asia/Tokyo');
await page.goto('https://api.ipify.org?format=json');
console.log(await page.evaluate(() => document.body.innerText));
await browser.close();
Older code used createIncognitoBrowserContext(), which was renamed to createBrowserContext() in Puppeteer v22.
SOCKS5 and IP whitelists
Both tools accept socks5://HOST:PORT as the proxy server. The catch is authentication: Chromium does not support a username and password for SOCKS5 proxies, so a password-protected SOCKS5 proxy will fail in Puppeteer. You have two clean options:
- Keep the order on HTTP and use the credentials as shown above.
- Use SOCKS5 with an IP whitelist. Add the public IP of the machine running the browser (up to 20 addresses), and the whitelist replaces password access. Then the server string alone is enough:
socks5://HOST:PORT.
You can switch the protocol of an order in the console where the location supports it. The whitelist route also works well for CI runners or a VPS with a fixed IP.
Troubleshooting
ERR_PROXY_CONNECTION_FAILED: wrong host or port, a typo in the scheme, or the protocol in the console does not match the scheme in your code.407or an endless login prompt: missingpage.authenticate()in Puppeteer, wrong credentials, or the password was rotated in the console and the old one no longer works.- Works locally, fails in CI: the order uses a whitelist and the runner's IP is not on it.
- IP is right but content is still foreign: set
locale,timezoneId(oremulateTimezone) and theAccept-Languageheader to match the country.
Use automation within the rules
Browser automation through a proxy is powerful for testing your own sites, checking localized pages and running scheduled QA. On third-party sites, read the terms of service and robots.txt, keep request rates low, and prefer an official API where one exists. A proxy does not prevent blocks or bans; behaving like a reasonable visitor matters more. If you manage social or store accounts, read the platform-specific notes in our guides on TikTok proxies in the US, Facebook Ads proxies and e-commerce seller proxies, and follow each platform's rules on automation.
FAQ
Can I change the proxy of a running Playwright context?
No. The proxy is fixed when the context is created. Close it and create a new context with the new proxy.
Do I need one dedicated IP per context?
If each context represents a separate account or market, yes. Sharing an IP across contexts links them together from the website's point of view.
Does headless mode change proxy behaviour?
No. Headless and headed browsers use the proxy the same way.
Which protocol should I pick for Puppeteer?
HTTP with username and password, or SOCKS5 with an IP whitelist. Password-protected SOCKS5 does not work in Chromium.
Run it with your own IPs
Cloudj offers dedicated IPv4 proxies in seven countries, including United States proxies with a static ISP option; the full list is on the proxy overview. Order the number of IPs your contexts need in the console, and check live per-IP prices on the pricing page.