# Playwright proxy setup with rotating residential IPs (Python and Node.js)

> Playwright proxy setup in Python and Node.js: authenticated residential proxies per browser or per context, sticky sessions, lighter pages.

- URL: https://crawlrelay.com/docs/playwright/
- Last updated: 2026-10-13
- Publisher: CrawlRelay (https://crawlrelay.com)

Give the proxy at launch for one exit per browser, or per context for one country or one sticky session per context. Then cut traffic by blocking what you do not need.

**Quick answer — How do I use an authenticated proxy in Playwright?**

Pass a `proxy` object with `server`, `username` and `password` to `launch()` or to `new_context()`: `server` is `http://gw.crawlrelay.com:7777`, and the credentials go in their own fields, never in the server URL. With **CrawlRelay** residential proxies, put the country and a session ID in the username to give each context its own exit. Traffic costs $0.50/GB from 50 GB. Paid in crypto, with no KYC.

## Playwright setup at a glance

| Attribute | Value |
| --- | --- |
| Proxy option | `{"server": "http://gw.crawlrelay.com:7777", "username": ..., "password": ...}` |
| Where | `launch(proxy=...)` for the whole browser, or `new_context(proxy=...)` per context |
| Port | HTTP port 7777; HTTPS sites go through CONNECT |
| SOCKS5 | Chromium does not support SOCKS5 authentication: use the HTTP port |
| One IP per context | `-session-<id>-ttl-<minutes>` in the context's username |
| Typical page cost | About 2 MB with every resource; a few hundred KB with images, fonts and media blocked |

## How do I give each browser context its own country or session?

Create one context per country or per sticky session. Contexts are cheap compared with browsers, and each keeps its own cookies, storage and proxy credentials. Match the context's locale to the exit country so the site sees a consistent visitor.

*Python (sync API): one context per country*

```python
from playwright.sync_api import sync_playwright

GATEWAY = "http://gw.crawlrelay.com:7777"
LOCALES = {"us": "en-US", "de": "de-DE", "jp": "ja-JP"}

with sync_playwright() as p:
    browser = p.chromium.launch()
    for country, locale in LOCALES.items():
        context = browser.new_context(
            proxy={"server": GATEWAY, "username": "USERNAME-country-%s" % country, "password": "PASSWORD"},
            locale=locale,
        )
        page = context.new_page()
        page.goto("https://api.ipify.org", wait_until="domcontentloaded")
        print(country, page.inner_text("body"))
        context.close()
    browser.close()
```

## How do I set the proxy in Playwright for Node.js and cut traffic?

The option has the same shape in Node.js. Most of a rendered page's weight is images, fonts and media: the median page weighed 2,164 KB on mobile in the HTTP Archive Web Almanac 2025. If you only need text and links, abort those requests in a route handler.

*Node.js: sticky session at launch, heavy resources blocked*

```javascript
import { chromium } from "playwright";

const browser = await chromium.launch({
  proxy: {
    server: "http://gw.crawlrelay.com:7777",
    username: "USERNAME-country-us-session-pw1-ttl-30",
    password: "PASSWORD",
  },
});
const context = await browser.newContext();
// Images, fonts and media are most of a page's bytes
await context.route("**/*", (route) =>
  ["image", "font", "media"].includes(route.request().resourceType()) ? route.abort() : route.continue()
);
const page = await context.newPage();
const response = await page.goto("https://example.com/", { waitUntil: "domcontentloaded" });
console.log(response.status(), await page.title());
await browser.close();
```

## How do I run many pages in parallel with asyncio?

Use the async API with a fixed pool of contexts. Each context keeps one sticky session for its lifetime, so a site sees a few stable visitors rather than one IP per request. Recycle a context, with a new session ID, after a set number of pages or after an error.

*Python (async API): a pool of sticky contexts*

```python
import asyncio
import uuid

from playwright.async_api import async_playwright

GATEWAY = "http://gw.crawlrelay.com:7777"
WORKERS = 4


async def worker(browser, queue):
    session = uuid.uuid4().hex[:10]
    context = await browser.new_context(proxy={
        "server": GATEWAY,
        "username": "USERNAME-country-us-session-%s-ttl-30" % session,
        "password": "PASSWORD",
    })
    page = await context.new_page()
    while not queue.empty():
        url = await queue.get()
        try:
            response = await page.goto(url, wait_until="domcontentloaded", timeout=45000)
            print(session, url, response.status if response else None)
        except Exception as exc:  # timeouts and network errors: log and move on
            print(session, url, type(exc).__name__)
        finally:
            queue.task_done()
    await context.close()


async def main(urls):
    queue = asyncio.Queue()
    for url in urls:
        queue.put_nowait(url)
    async with async_playwright() as p:
        browser = await p.chromium.launch()
        await asyncio.gather(*(worker(browser, queue) for _ in range(WORKERS)))
        await browser.close()


asyncio.run(main(["https://example.com/", "https://example.org/"]))
```

## Why do I get ERR_TUNNEL_CONNECTION_FAILED or ERR_PROXY_CONNECTION_FAILED?

- **net::ERR_TUNNEL_CONNECTION_FAILED** means the proxy did not accept the CONNECT request. Most often the credentials were written inside `server` instead of `username` and `password`, so Chromium sent none. See [the tunnel error](https://crawlrelay.com/docs/errors/err-tunnel-connection-failed/).
- **net::ERR_PROXY_CONNECTION_FAILED** means the browser never reached the gateway: a typo in the host or port, or a network that blocks the port. See [the proxy connection error](https://crawlrelay.com/docs/errors/err-proxy-connection-failed/).
- **Timeout 30000ms exceeded** on `goto` usually means you wait for `load` or `networkidle` on a page that keeps loading trackers. Wait for `domcontentloaded` and then for the selector you need.

## What should a Playwright scraper not do?

Use Playwright to render public pages that need JavaScript, not to impersonate people. CrawlRelay's [Acceptable Use Policy](https://crawlrelay.com/acceptable-use/) forbids using browsers through our gateway to create accounts in bulk, take over accounts, solve CAPTCHAs automatically or defeat a site's deliberate blocks. We do not document stealth plugins or fingerprint spoofing. If a site challenges your browser, slow down, check its robots.txt and terms, and ask for access or use its API.

## Playwright and proxies: questions

### Can I use a different proxy for each Playwright context?

Yes. Pass `proxy` to `browser.new_context()` in Python or `browser.newContext()` in Node.js. Each context then has its own exit, cookies and storage. Very old Playwright releases on Windows also required a proxy at launch; if you see that error, launch with any placeholder proxy and set the real one per context.

### Does Playwright support SOCKS5 proxies with authentication?

Not in Chromium, which does not support SOCKS5 username and password authentication. Use the gateway's HTTP port (7777): it handles HTTPS sites through CONNECT tunnels and supports username and password in every browser engine Playwright drives.

### How do I check which IP and country a Playwright page uses?

Open `https://api.ipify.org` in the same context before your target and read the body text, as in the first example. Keep in mind that a sticky session keeps that IP only while its residential device stays online; log the IP at the start of each context to spot changes.

### How much traffic does a Playwright page use through a proxy?

All of it is billed: HTML, scripts, styles, images, fonts and API calls. A full page is about 2 MB at the median; blocking images, fonts and media often cuts that to a few hundred kilobytes. Measure your real pages by summing response sizes in a `response` handler for 50 pages.

### Should I use headless or headed browsers for scraping?

Headless is the normal choice for servers and uses fewer resources. Use a headed browser only to debug what a page shows. Neither changes the rules: render public pages at a polite pace and respect the site's robots.txt and terms.

**Render public pages through residential IPs** $5.94 for 10 GB, $0.50/GB from 50 GB. [See pricing](https://crawlrelay.com/pricing/) · [Puppeteer guide](https://crawlrelay.com/docs/puppeteer/)

---

CrawlRelay: CrawlRelay is a developer-first rotating residential and 4G/5G mobile proxy service for scrapers and AI data pipelines: non-expiring pay-as-you-go traffic from $0.50/GB, paid in crypto, with no ID upload. No KYC — no ID, no selfie, no documents. Pay in crypto.
