Connect Playwright to Crawlhub proxies.
Add an authenticated HTTP proxy to your browser automation, then check one page before running a larger job.
Go to setup guide
What does Playwright handle?
Playwright controls Chromium, Firefox and WebKit. Your proxy chooses the network route; your browser context owns cookies, local storage and other session state.
Browser or context scope
Set a proxy when launching the browser, or when creating an individual browser context.
Proxy-only credentials
The local bridge handles upstream login, keeping proxy credentials out of the browser’s website authentication handler.
Browser rendering
Use the browser when your data depends on JavaScript or page interaction.
Explicit session state
Reuse a context for a related sequence. Changing contexts does not automatically request a new proxy IP.
Set up Playwright with Crawlhub
Start with one connection check. Add your workload after the route works.
const bridge = await createProxyBridge();
const browser = await chromium.launch({
proxy: { server: 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 the browser library
Create a project folder and install Playwright. Its browser download is separate from installing the JavaScript package.
Run in your terminalnpm init -y npm install playwright proxy-chain npx playwright install chromiumAdd your connection details
Set the variables in the shell that will run the script. A proxy server URL such as http://HOST:PORT can carry HTTPS destination traffic using CONNECT. Pick the proxy protocol assigned to your service.
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/'Run a page request
Download both files below into the same project folder, then run playwright.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 playwright.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 playwright.mjsplaywright.mjsimport { chromium } from 'playwright'; import { createProxyBridge } from './proxy_bridge.mjs'; const bridge = await createProxyBridge(); let browser; try { browser = await chromium.launch({ headless: true, proxy: { server: bridge.url }, }); const context = await browser.newContext(); 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(); } }Verify the route and grow the job
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.
For a multi-step session, keep the same browser context and use the sticky-session username assigned by your service. Set concurrency and navigation timeouts deliberately rather than launching an unlimited number of browsers.
When the connection fails
Use the error to narrow down the next check.
407 authentication error
Check the upstream login and username parameters in the bridge environment. Website httpCredentials are a separate mechanism and should not contain proxy credentials.
Navigation timeout
Test the proxy with one public page first. Distinguish connection failure from a page that keeps network activity open.
Different data between runs
Record the exit location and session parameters. Browser context state and proxy IP state are separate.
