Puppeteer and Playwright Proxy Setup: Auth and Sessions
Run Puppeteer or Playwright through an authenticated HTTP proxy, prove the exit and page content in one script, and keep logins on one exit.
This guide runs Chromium, from Puppeteer or from Playwright, through an authenticated HTTP proxy and checks three things in one script: the browser authenticated with the proxy, it left through the exit you expected, and the target served the content your job needs rather than a challenge page. It then sets session boundaries so a login never changes exits halfway through.
You need Node.js, an HTTP proxy endpoint with a username and password, and permission to automate the target. The proxy controls the network route and nothing else. Browser fingerprint, account history and request behavior are separate signals the site still evaluates, and this setup does not hide automation from it.
The auth mechanics alone (page.authenticate, Playwright's proxy option, per-context proxies, DNS and WebRTC leaks) are in proxy authentication in Puppeteer and Playwright, and Puppeteer rotating proxy setup compares rotating per browser, per context and at the gateway; this workflow is the end-to-end verification and session plan that sits on top of both.
TL;DR
- For puppeteer playwright proxy setup, verify transport and authentication before running browser automation.
- Node4 fits developers and data teams that need proxies for browser automation.
- Playwright exposes proxy credentials in configuration; Puppeteer uses launch arguments and page authentication.
- Keep one exit identity through a stateful workflow. Rotate between independent jobs, not during login.
Why this matters
A proxy changes the network path of a request. It does not remove browser fingerprints or change account permissions, and it does not guarantee that a website accepts automation. Treat routing, session state, and application behavior as separate systems.
A successful browser launch proves little. You need evidence that the browser used the intended exit, authenticated successfully, and reached the expected content rather than a challenge page. Start with proxies for Puppeteer and Playwright scraping if you are still choosing the connection type.
Verify the proxy before adding retries, parallel workers, or session rotation. This guide uses Chromium in both libraries so you can compare configuration without changing the browser engine. Use it for authorized testing and data collection, within the target site's rules.
Before you start
- Install a supported Node.js release and npm. Have a proxy server address, its authentication details, and permission to use the target application. The examples use a POSIX-compatible terminal.
- Prepare an HTTPS diagnostic endpoint that returns a JSON object with an
ipfield containing the caller's address. Use your own endpoint or an approved diagnostic service. Also prepare a target URL and a selector that identifies successful page content. - Use an HTTP proxy endpoint for the authenticated examples below. Chromium does not support username/password authentication for SOCKS5 proxies through these browser settings. An HTTPS destination does not require a SOCKS5 endpoint; an HTTP proxy can tunnel HTTPS with CONNECT.
Keep credentials out of source files, screenshots, and shared logs. The commands prompt for credentials rather than putting their values directly into shell history. Environment variables still require protection: processes and diagnostic tooling with sufficient access can read them.
Runtime and input configuration
Install the browser libraries
- Create a separate directory for the test. Install both libraries if you want to compare them; a production project needs only the library it uses.
- Install Chromium for Playwright. Puppeteer's normal package installation downloads its supported browser unless download behavior has been changed.
- Enter your actual endpoint and target details when prompted.
PROXY_SERVERmust contain the scheme, host, and port, without credentials.
mkdir browser-proxy-check
cd browser-proxy-check
npm init -y
npm install playwright puppeteer
npx playwright install chromium
read -r -p 'HTTP proxy URL: ' PROXY_SERVER
read -r -p 'Proxy username: ' PROXY_USERNAME
read -r -s -p 'Proxy password: ' PROXY_PASSWORD
printf '\n'
read -r -p 'HTTPS IP-check URL: ' TEST_URL
read -r -p 'Authorized target URL: ' TARGET_URL
read -r -p 'Success CSS selector: ' TARGET_SELECTOR
export PROXY_SERVER PROXY_USERNAME PROXY_PASSWORD
export TEST_URL TARGET_URL TARGET_SELECTORUse a specific success selector, not simply body. An account dashboard marker, a product result container, or a fixture element from your own test application distinguishes useful content from a generic error document. Avoid selectors that depend on generated class names when your application provides a stable attribute.
Expected result: the packages are installed, and the current terminal has the inputs needed for both browser runs. Keep that terminal open; a different shell does not automatically inherit these variables.
For a repeatable deployment, commit the package lockfile and record the browser version used by your job. Revalidate the proxy check when either changes. Do not assume that a successful local run proves the same container image has the browser binaries and operating-system dependencies it needs.
Proxy transport verification
Test the connection before launching Chromium
- Request the diagnostic endpoint without a proxy. Save the returned address as your direct-network baseline.
- Request the same endpoint through the proxy, with authentication supplied separately.
- Inspect the response and curl's exit status. Do not proceed after a connection or authentication failure.
curl --fail --silent --show-error \
--max-time 15 \
"$TEST_URL"
printf '\n'
curl --fail --silent --show-error \
--max-time 15 \
--proxy "$PROXY_SERVER" \
--proxy-user "$PROXY_USERNAME:$PROXY_PASSWORD" \
"$TEST_URL"
printf '\n'The 15-second timeout is a chosen diagnostic limit, not a proxy performance claim. Increase it deliberately if your network requires a different limit. Keep the limit explicit so a failed connection does not leave a test hanging indefinitely.
For this test, the proxied response should show an address different from your direct-network baseline. A rotating endpoint can produce different addresses across separate requests. That alone is not an authentication failure.
Compare the observed address with the endpoint's expected behavior. A country label is not proof that the address serves the location your application requires. Test the application's own location-sensitive response when geography affects the result.
Expected result: curl reaches the HTTPS diagnostic endpoint through the proxy and returns a valid JSON response. If curl fails, fix the endpoint, credentials, or network access before investigating browser code.
Browser configuration and validation
Choose the configuration surface
Both libraries can automate Chromium through an HTTP proxy. Their authentication configuration differs, so copying settings from one into the other does not work.
| Library | Best for | Proxy configuration | Advantage | Limitation |
|---|---|---|---|---|
| Playwright | Tests that need isolated browser contexts | proxy.server, proxy.username, proxy.password | Proxy credentials sit together in a configuration object | Context isolation does not guarantee a stable upstream exit |
| Puppeteer | Existing Chromium automation projects | --proxy-server and page.authenticate() | Fits established page-based workflows | Authentication must be configured before page navigation |
Choose the library your application already uses. Switching libraries is not a substitute for fixing authentication, exit selection, or target-side rejection, and neither library changes how a site judges automated traffic.
Run the same check with either library
- Save the following code as
browser-check.mjs. - Run it with
ENGINE=playwright, then withENGINE=puppeteerif you need the comparison. - Check the diagnostic address, target response status, and success selector. A resolved navigation promise is not enough.
import { chromium } from 'playwright';
import puppeteer from 'puppeteer';
function required(name) {
const value = process.env[name];
if (!value) throw new Error(`Missing ${name}`);
return value;
}
const server = required('PROXY_SERVER');
const username = required('PROXY_USERNAME');
const password = required('PROXY_PASSWORD');
const testUrl = required('TEST_URL');
const targetUrl = required('TARGET_URL');
const targetSelector = required('TARGET_SELECTOR');
const engine = process.env.ENGINE || 'playwright';
if (!['playwright', 'puppeteer'].includes(engine)) {
throw new Error('ENGINE must be playwright or puppeteer');
}
if (new URL(server).protocol !== 'http:') {
throw new Error('This example requires an HTTP proxy endpoint');
}
if (new URL(testUrl).protocol !== 'https:') {
throw new Error('Use an HTTPS diagnostic endpoint');
}
let browser;
try {
let page;
if (engine === 'playwright') {
browser = await chromium.launch({
headless: true,
proxy: { server, username, password }
});
const context = await browser.newContext();
page = await context.newPage();
} else {
browser = await puppeteer.launch({
headless: true,
args: [`--proxy-server=${server}`]
});
page = await browser.newPage();
await page.authenticate({ username, password });
}
page.setDefaultNavigationTimeout(30000);
page.setDefaultTimeout(30000);
const diagnostic = await page.goto(testUrl, {
waitUntil: 'domcontentloaded'
});
if (!diagnostic || diagnostic.status() >= 400) {
throw new Error('Diagnostic endpoint rejected the request');
}
const text = await page.evaluate(() => document.body.innerText);
const data = JSON.parse(text);
if (typeof data.ip !== 'string' || !data.ip) {
throw new Error('Diagnostic response needs an ip field');
}
console.log({ engine, exitIp: data.ip });
const response = await page.goto(targetUrl, {
waitUntil: 'domcontentloaded'
});
if (!response || response.status() >= 400) {
throw new Error('Target navigation failed');
}
await page.waitForSelector(targetSelector, { visible: true });
console.log({ engine, targetStatus: response.status(), verified: true });
} finally {
if (browser) await browser.close();
}ENGINE=playwright node browser-check.mjs
ENGINE=puppeteer node browser-check.mjsThe example sets 30-second navigation and selector timeouts. These are test settings, not recommended limits for every workload. The browser closes on both success and failure, preventing an abandoned test from leaving its browser process running.
The exit check and target navigation are separate requests to different hosts, so Chromium opens a separate proxy connection for each. A rotating gateway picks the exit per connection, which means the two need not share an exit address. Use provider-supported sticky routing when your task requires the diagnostic result to describe the same identity used throughout the target session.
Expected result: the script prints an exit address, verifies the target's expected content, and closes the browser. If the selector never appears, inspect the document rather than treating the run as a successful scrape.
Session boundaries and job execution
Keep state and routing aligned
- Start with 1 worker while validating your configuration. This isolates failures without claiming that one worker is the correct production capacity.
- Assign each independent job its own browser context or browser instance. Do not share cookies between unrelated accounts or clients.
- Keep the same proxy identity through a stateful transaction, including login and subsequent requests.
- Close the session when the job completes. Create the next session only after assigning its intended proxy configuration.
A fresh context separates browser storage. It does not rotate an IP by itself. Conversely, an upstream IP change does not clear cookies or local storage. Your job scheduler must manage both boundaries explicitly.
On Node4's rotating gateway, a sticky session holds one exit for up to 30 minutes from first assignment, not from the latest request. A job that requires a longer unchanged identity needs a different session design. Do not attempt to extend that window by periodically sending requests. Residential sessions hold for up to 30 minutes on a best-effort basis: if the device behind the exit goes offline first, that session fails until you switch to a new session ID.
Expected result: each job has an explicit identity boundary, and a login workflow does not deliberately switch exits halfway through. Record the selected endpoint and session identifier without exposing credentials.
Variant: rotate between independent collection jobs
Use rotation when each authorized collection job is independent. Keep sticky routing when a workflow depends on login state or an identity that persists across pages. The rotating proxies for web scraping guide covers the routing choice in more detail.
For Playwright, assign the next endpoint through a new context's proxy configuration when using context-level proxy settings. For Puppeteer, the launch-level --proxy-server setting belongs to that browser instance; launch another browser when changing that setting in this workflow. Do not modify a shared browser's configuration while other jobs use it.
An upstream rotating gateway can change exits while keeping the gateway address unchanged. Follow its documented session controls rather than inventing a username suffix. Gateway configuration and browser-context creation are different operations.
Re-run the diagnostic check at each new job boundary. Use bounded retries only for transient failures, and preserve the job's identity when retrying a stateful operation. For rate-limited responses, honor the server's retry instructions instead of adding workers.
Troubleshooting
HTTP 407: proxy authentication required
Check the username and password against the actual endpoint. In Puppeteer, call page.authenticate() before page.goto(); in Playwright, put credentials in the proxy object. Confirm that curl succeeds with the same credentials before changing browser settings.
Proxy connection or tunnel errors
Check the server scheme, port, network reachability, and any source-IP authorization requirement. Run the curl test from the same container or host as the browser. A successful laptop test does not establish that a production firewall permits the connection.
HTTP 403, HTTP 429, or a challenge document
Separate target rejection from proxy authentication. Inspect the response and expected selector, respect rate limits, and stop when access is not authorized. Changing libraries or adding fingerprint patches does not guarantee acceptance.
Login succeeds, then the session breaks
Check whether the exit changed or the sticky assignment expired. Keep browser state and routing together for the entire transaction. Do not replay a state-changing request blindly after a timeout; first check whether the server completed it.
JSON parsing fails at the diagnostic endpoint
Inspect the returned content without publishing credentials or sensitive headers. The endpoint must return JSON with an ip field for this script. An HTML error page, redirect destination, or differently shaped response requires an endpoint fix or a deliberate parser change.
Customize your workflow
Turn the diagnostic into a deployment smoke test. Run it after changing credentials, proxy configuration, browser packages, or the execution environment. Keep the test separate from production collection so a routing failure is visible before a large job starts.
Add a queue, a concurrency ceiling, and per-job timeouts next. Measure completed tasks and target responses, not just launched browsers. Remove stored authentication state when a client or account changes; browser context isolation does not make exported state files safe to share.
Build retries around the operation's consequences. Repeating a read differs from repeating a purchase, form submission, or account change. Your automation should know which operations are safe to retry before a network failure occurs.
Node4's proxies for Puppeteer, Playwright and Selenium take a session id in the username on the rotating gateway; residential sessions are best effort, with a 30-minute ceiling; see pricing for the rotating and residential rates.
FAQ
How do I configure a proxy in Playwright?
Set proxy.server, proxy.username, and proxy.password in Playwright's browser launch configuration for an authenticated HTTP proxy. Verify the exit through an HTTPS diagnostic endpoint before navigating to your target.
How do I authenticate a proxy in Puppeteer?
Launch Chromium with --proxy-server and call page.authenticate() before navigation. Keep credentials out of the proxy URL and source code.
Does a proxy make Puppeteer or Playwright automation harder to detect?
No. A proxy changes network routing, but websites also evaluate browser behavior, session history, and application-level signals. Choosing Puppeteer or Playwright does not change that.
Can I use an authenticated SOCKS5 proxy with these examples?
No. Chromium does not support SOCKS5 username/password authentication through the proxy settings used here. Use an HTTP endpoint for this workflow or an appropriately secured intermediary designed for your environment.
Should I rotate the proxy after every request?
Keep one identity through a stateful workflow and rotate between independent jobs when rotation fits the task. Browser cookies and exit selection must be managed separately.
Why does my browser receive a successful status but no useful data?
A successful HTTP status does not prove that the expected application content loaded. Check a specific success selector and inspect challenge or error documents separately.
How long does a rotating sticky session last?
On the rotating gateway, a sticky session holds one exit for up to 30 minutes from its first assignment. Later requests do not restart that window, so plan job boundaries around the original assignment time. A residential sticky session has a 30-minute ceiling on a best-effort basis, and fails rather than moves if its device goes offline first.
One last thing
A challenge page can return HTTP 200. That is why the selector assertion belongs in your proxy check, not only in the scraper that follows it. Verify the content you need, not merely the status your browser received.