Short answers
Where do I put the Puppeteer proxy setting?
Set proxyServer when creating a browser context. Pages created in that context use its proxy configuration. A separate Node.js HTTP request does not inherit that setting.
How do I authenticate a Puppeteer proxy?
Puppeteer documents page.authenticate() for HTTP authentication. Its version-pinned implementation is not limited to the proxy challenger. This example uses Chrome's authentication challenge information to supply gateway credentials only to the configured proxy.
Can I change proxies for each request?
Proxy configuration belongs to the context in this recipe. Create a separate context when you need a different gateway configuration. A new application request may reuse an existing connection, so it does not necessarily cause a new exit address.
Can I use authenticated SOCKS5 in this example?
Use the HTTP gateway and HTTP port here. Chromium's SOCKS5 support does not include SOCKS authentication. A SOCKS5 URL working in curl does not establish that Chrome can authenticate it.
This recipe uses Puppeteer with Chrome. Its API references were checked against Puppeteer 25.12.0. The example's syntax and credential-selection logic are checked separately; this preparation does not include a live Puppeteer proxy navigation. Test the complete script in your runtime before scheduling work.
Before the first navigation
Open the connection builder, choose HTTP, select your pool and an available country, then copy the complete username. Its pool, country and session tokens matter: the account prefix alone is not the generated connection.
Verify the same connection with the curl proxy check. Resolve a refused gateway, rejected password or unavailable route before adding browser automation.
Supply PROXY_USER and PROXY_PASSWORD through your runtime's secret configuration. Keep credentials out of source files, command history, screenshots and shared logs. PROXY_SERVER is optional and contains only a scheme, host and port; the default in the companion example is http://gw.portproof.org:7000.
Install Puppeteer in a scratch project with Node.js 22.12.0 or later, as required by this release:
npm install puppeteer@25.12.0The installation guide explains the browser download. puppeteer-core has a different installation contract and requires you to supply a browser. A container also needs the operating-system dependencies for its Chrome build. Keep the browser sandbox and certificate checks enabled.
The HTTP connection to the gateway is not encrypted. HTTPS protects the destination exchange inside its CONNECT tunnel; it does not protect the initial gateway connection or Basic proxy credentials on that connection. Use a trusted network path. Changing the URL to https:// only works if the gateway offers a verified HTTPS proxy endpoint. See Chromium’s proxy documentation.
Run one authenticated HTTPS check
Copy the two complete companion files below into the same directory in your scratch project. Save them as puppeteer-proxy-check.mjs and proxy-auth.mjs, then run:
node puppeteer-proxy-check.mjsThe browser-context option places the gateway at a clear scope:
const context = await browser.createBrowserContext({
proxyServer: server.origin,
});
const page = await context.newPage();The complete script creates a Chrome DevTools Protocol session for that page. Before navigation, it enables authentication events and handles both paused requests and authentication challenges. Each ordinary paused request must be continued; leaving it paused stalls the page.
The credential decision checks the challenge source and expected proxy origin:
if (event.authChallenge?.source !== 'Proxy' ||
attempted.has(event.requestId)) {
return { response: 'CancelAuth' };
}It also checks the challenger scheme, hostname and effective port before providing credentials. A website's HTTP authentication challenge, an unexpected proxy or a repeated challenge for the same request is cancelled. This avoids repeatedly sending a rejected password and prevents the gateway password being supplied to website HTTP authentication.
The script then navigates to a small IP echo endpoint with a navigation timeout. It requires a successful HTTP response and a valid IP address in the JSON, then closes the context and browser even on failure. The printed address is your own observed result. It is not a sample pool address, a country verification result or a promise about a later connection.
See the official Fetch authentication protocol for authRequired and continueWithAuth. The helper covers the single-page echo check. It does not install authentication handlers on popup, worker or separate cross-origin frame targets. A broader browser job needs handling and testing for each target it uses. Do not combine it with page.authenticate() or another interception system without reviewing how both handlers interact.
The companion files
Save both files together. Their syntax and credential selection were checked during preparation; the full browser navigation still requires a test in your runtime.
import puppeteer from 'puppeteer';
import { isIP } from 'node:net';
import { pathToFileURL } from 'node:url';
import { authResponse } from './proxy-auth.mjs';
function required(name) {
const value = process.env[name];
if (!value) throw new Error('Missing credential variable');
return value;
}
export async function main() {
const server = new URL(process.env.PROXY_SERVER ?? 'http://gw.portproof.org:7000');
if (server.protocol !== 'http:' || server.username || server.password ||
server.pathname !== '/' || server.search || server.hash) {
throw new Error('Use an HTTP proxy host and port with separate credentials');
}
const credentials = { username: required('PROXY_USER'), password: required('PROXY_PASSWORD') };
const browser = await puppeteer.launch({ headless: true, timeout: 30_000 });
try {
const context = await browser.createBrowserContext({ proxyServer: server.origin });
try {
const page = await context.newPage();
const session = await page.createCDPSession();
const attempted = new Set();
let protocolFailed = false;
const send = (method, params) => {
void session.send(method, params).catch(() => { protocolFailed = true; });
};
session.on('Fetch.requestPaused', event => {
send('Fetch.continueRequest', { requestId: event.requestId });
});
session.on('Fetch.authRequired', event => {
send('Fetch.continueWithAuth', {
requestId: event.requestId,
authChallengeResponse: authResponse(event, attempted, credentials, server),
});
});
await session.send('Fetch.enable', { handleAuthRequests: true });
const response = await page.goto('https://api.portproof.org/v1/echo-ip', {
waitUntil: 'domcontentloaded', timeout: 30_000,
});
if (protocolFailed || !response || !response.ok()) {
throw new Error('Navigation or authentication failed');
}
const payload = await response.json();
if (!payload || typeof payload.ip !== 'string' || !isIP(payload.ip)) {
throw new Error('Echo did not return an IP address');
}
console.log('HTTP', response.status(), 'exit', payload.ip);
} finally {
await context.close();
}
} finally {
await browser.close();
}
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
main().catch(error => {
console.error('Proxy check failed:', error.name);
process.exitCode = 1;
});
}The helper file is proxy-auth.mjs:
function port(url) {
return url.port || (url.protocol === 'https:' ? '443' : '80');
}
// Supply gateway credentials only to the expected proxy challenger, once per request.
export function authResponse(event, attempted, credentials, expectedProxy) {
if (event.authChallenge?.source !== 'Proxy' || attempted.has(event.requestId)) {
return { response: 'CancelAuth' };
}
let challenger;
try { challenger = new URL(event.authChallenge.origin); }
catch { return { response: 'CancelAuth' }; }
if (challenger.protocol !== expectedProxy.protocol ||
challenger.hostname !== expectedProxy.hostname || port(challenger) !== port(expectedProxy)) {
return { response: 'CancelAuth' };
}
attempted.add(event.requestId);
return { response: 'ProvideCredentials', ...credentials };
}Understand the authentication scope
The ordinary Puppeteer page.authenticate({ username, password }) pattern is convenient for a controlled HTTP-authentication test. However, its version-pinned implementation supplies configured credentials when it handles an authentication challenge without checking whether the challenger is the proxy or website, as shown in the Puppeteer 25.12.0 implementation. That distinction matters when visiting multiple origins or loading third-party resources.
The Page.authenticate reference also explains that authentication enables interception and may affect performance. The scoped example above is a connection check, not a browser throughput benchmark. Its credential selection is independently tested; a browser version, challenge origin or interception change still needs verification in the actual runtime.
Keep website login details separate. A form login, saved cookie state and an HTTP authentication challenge are different mechanisms. A fresh browser context isolates browser state; it does not by itself create a new proxy session.
Match the context and gateway session to the job
For a multi-step QA journey, keep its pages in one context and use the intended named sticky session from the connection builder. The sticky session selects the same device while it remains available; the IP can still change. Independent jobs may need separate contexts and session names.
Country routing does not set a site's storefront, currency, language or delivery region. Record those inputs separately. A result from an IP echo endpoint establishes a connection to that endpoint. It does not establish which market another website displayed.
For a price check, wait for a specific product element, then validate the product, variant, currency and delivery assumptions. domcontentloaded is a useful bounded navigation milestone, but does not promise that a JavaScript application has finished rendering. Use a bounded selector wait for the state the job needs.
Read rotating and sticky sessions for the gateway behaviour. For offer comparability and planning, use the price-monitoring workflow.
Diagnose the stage that failed
| Symptom | Next check |
|---|---|
| Proxy authentication or HTTP 407 | Check the complete generated username and password, the HTTP port and the challenge origin. Compare with the same credentials in curl. |
| Repeated authentication challenge | Correct the credentials or route. The example cancels a repeated challenge; it does not retry a password indefinitely. |
ERR_TUNNEL_CONNECTION_FAILED | Check proxy scheme, port, balance and country availability. Follow the CONNECT guide. |
| Connection refused or timeout | Test gateway reachability and the small echo endpoint before debugging a large page. |
| Page stays loading after adding interception | Check that each paused request is continued or deliberately failed, and that asynchronous handler failures are handled. |
| Website responds with 403 or 429 | Identify the responding server. Follow its access rules and request limits; respect a requested delay and stop refusals. |
| Successful navigation, wrong result | Validate application state. A 200 response can contain a sign-in screen, wrong offer or consent page. |
| Certificate error | Correct destination or machine trust problems. Keep HTTPS verification enabled. |
| Script fails only in CI | Check the browser installation, OS dependencies and availability of the process's credential variables. |
The script prints a generic error name so routine logs do not reproduce URLs or headers. Inspect detailed errors privately when diagnosing a failure, after checking that the output will not expose credentials. There are no automatic retries in this first check.
Measure the browser workflow before buying more traffic
A browser can load documents, scripts, images, fonts and background requests. Its transferred traffic can differ substantially from a simple HTTP fetch. Establish a valid result before removing resources, then compare the same task after each change.
For a visual QA test, removing fonts and images changes the test. For a price collector, removing a request that supplies the price produces a smaller run with no useful observation. The browser bandwidth guide covers resource filtering; keep that work separate from the first connection check.
Measure a representative sample, include partial failures and extra attempts, and compare the result with refreshed account usage. Client body sizes and the account meter have different boundaries. The bandwidth calculator helps turn consistently measured inputs into a GB estimate.
If you need proxy traffic for a permitted workflow, check current locations and pricing. Portproof's shared mobile and residential pools use one GB balance. The paid trial lets you evaluate the connection in your own application before choosing a larger order.
What is not allowed
Run browser checks only where you have permission. Follow destination terms, access controls and request limits, and protect credentials and saved browser state. See the acceptable-use policy.
Read next
Fix 407 Proxy Authentication Required
Read the gateway code, check credentials and balance, then separate a rejected HTTPS tunnel from an HTTP response.
Read the guide
Cut proxy bandwidth in a headless browser
Interception in both browser tools, what it breaks, and how to measure the saving.
Read the guide