Use Crawlhub proxies in Puppeteer.
Give a Puppeteer browser context an authenticated proxy and keep the connection separate from your scraping logic.
Go to setup guide
Where the proxy fits in Puppeteer
Puppeteer automates a browser from Node.js. This guide routes a browser context through a local bridge that authenticates to your assigned upstream proxy.
Context-level routing
Use createBrowserContext with proxyServer so the routing choice belongs to that context.
Proxy-only credentials
The local bridge handles upstream login, keeping proxy credentials out of the browser’s website authentication handler.
Real browser output
Read the rendered DOM, interact with controls or capture the page using the same browser session.
Predictable cleanup
Close contexts and the browser when the job finishes or throws an error.
Set up Puppeteer with Crawlhub
Start with one connection check. Add your workload after the route works.
const bridge = await createProxyBridge();
const context = await browser.createBrowserContext({
proxyServer: bridge.url,
});Before you start
An active Crawlhub proxy and its assigned host, port, username and password. PROXY_SERVER includes the protocol and port, but no username or password. Use the values from your account; these examples do not activate a proxy.
Node.js 22.12 or newer, the browser library and proxy-chain. This Chromium recipe uses a loopback-only bridge for authenticated HTTP(S) upstream proxies, so proxy credentials are not handed to website authentication challenges.
Install Puppeteer
Start a Node.js project and install Puppeteer. The full package manages its supported browser download; puppeteer-core requires you to supply a browser separately.
Run in your terminalnpm init -y npm install puppeteer proxy-chainSet the proxy environment
Keep the host and port in PROXY_SERVER and keep the login fields separate. Replace the example tokens with your assigned account values.
The example starts a loopback-only proxy bridge automatically. The bridge connects to the assigned upstream proxy; the browser receives only the local URL.
Run in your terminalexport PROXY_SERVER='http://YOUR_PROXY_HOST:YOUR_PORT' export PROXY_USERNAME='YOUR_PROXY_USERNAME' export PROXY_PASSWORD='YOUR_PROXY_PASSWORD' export TARGET_URL='https://example.com/'Create the context and fetch a page
Download both files below into the same project folder, then run puppeteer.mjs. The shared helper handles upstream authentication; the browser loads through its local endpoint.
The example checks the response status, prints the title and closes both the browser and bridge on completion or failure.
Run in your terminalnode puppeteer.mjsDownload proxy_bridge.mjsproxy_bridge.mjsimport { Server } from 'proxy-chain'; export async function createProxyBridge() { const { PROXY_SERVER, PROXY_USERNAME, PROXY_PASSWORD } = process.env; if (!PROXY_SERVER || !PROXY_USERNAME || !PROXY_PASSWORD) { throw new Error('Set PROXY_SERVER, PROXY_USERNAME and PROXY_PASSWORD.'); } const upstream = new URL(PROXY_SERVER); if (!['http:', 'https:'].includes(upstream.protocol) || upstream.username || upstream.password || upstream.search || upstream.hash || (upstream.pathname && upstream.pathname !== '/')) { throw new Error('Use an HTTP(S) proxy server URL without credentials, a path or a query.'); } upstream.username = PROXY_USERNAME; upstream.password = PROXY_PASSWORD; const server = new Server({ host: '127.0.0.1', port: 0, prepareRequestFunction: () => ({ upstreamProxyUrl: upstream.href }), }); await server.listen(); return { url: `http://127.0.0.1:${server.port}`, close: () => server.close(true), }; }Download puppeteer.mjspuppeteer.mjsimport puppeteer from 'puppeteer'; import { createProxyBridge } from './proxy_bridge.mjs'; const bridge = await createProxyBridge(); let browser; try { browser = await puppeteer.launch({ headless: true }); const context = await browser.createBrowserContext({ proxyServer: bridge.url }); const page = await context.newPage(); const response = await page.goto(process.env.TARGET_URL || 'https://example.com/', { waitUntil: 'domcontentloaded', timeout: 30000, }); if (!response || !response.ok()) { throw new Error(`Page returned HTTP ${response?.status() ?? 'unknown'}.`); } console.log(JSON.stringify({ status: response.status(), title: await page.title() })); await context.close(); } finally { try { await browser?.close(); } finally { await bridge.close(); } }Check the IP and retain session state
A successful request confirms that the page loaded, but it does not by itself prove the exit IP. Run the same code against an IP-check endpoint you trust, inspect the returned address, then switch to your target. Keep the requested location and session settings consistent when comparing results.
Use one context for a related sequence of pages. Use a new context when you need separate cookies. Request a different proxy session explicitly when your service requires one.
When the connection fails
Use the error to narrow down the next check.
Proxy authentication fails
Check the three proxy environment variables. Do not put the proxy password into page.authenticate; that handler can also answer destination-site challenges.
Requests use the wrong route
Create the page from the proxied context, not from an unrelated default context.
Target blocks a request
Inspect the response status and contents. A proxy connection alone does not guarantee that a website will accept a browser.
