15% off your first purchase with code WELCOME15, up to 25 GB.

See pricing

Integration · Browser automation

How to use a proxy with Playwright

Playwright takes a proxy as an object with server, username and password, either on launch() for the whole browser or on new_context() for one isolated context. Point server at the ARC gateway, put the pool, country and session in the username, and every context can leave from its own country or hold its own sticky session.

Reviewed 30 Oct 2026

your proxy, as a URL
http://USERNAME_pool-arcfast_country-de:PASSWORD@proxy.arcproxies.io:8000

The short answer

  • Credentials go in username and password, never inside the server URL.
  • Use one browser context per task, each with its own _session- handle: contexts are cheap, and each one gets its own exit IP and cookie jar.
  • In a browser, sticky beats rotating: one page load opens many connections, and a rotating username can spread them over several exits.
  • Check the exit IP once before a long run by loading an IP echo service through the same proxy settings.

Setup

Set up Playwright with a proxy, step by step

  1. 01

    Install Playwright and a browser

    Both the Python and the Node packages ship the same proxy option. Install the library, then the browser build it drives.

    shell
    # Pythonpip install playwrightplaywright install chromium# Nodenpm i playwrightnpx playwright install chromium
  2. 02

    Launch through the gateway and check the exit IP (Python)

    The server takes scheme, host and port; the username carries the targeting. Loading an IP echo service shows the address the target will see, which should be a German residential IP for this username, not your own.

    check_exit.py
    from playwright.sync_api import sync_playwrightwith sync_playwright() as p:    browser = p.chromium.launch(proxy={        "server": "http://proxy.arcproxies.io:8000",        "username": "USERNAME_pool-arcfast_country-de_session-k3f9a1x2_sesstime-10",        "password": "PASSWORD",    })    page = browser.new_page()    page.goto("https://api.ipify.org?format=json")    print(page.text_content("body"))   # {"ip":"…"}: the exit, not your machine    browser.close()
  3. 03

    The same in Node

    Identical option names in JavaScript and TypeScript. The test runner takes the same object under use.proxy in playwright.config.ts.

    check-exit.mjs
    import { chromium } from 'playwright';const browser = await chromium.launch({  proxy: {    server: 'http://proxy.arcproxies.io:8000',    username: 'USERNAME_pool-arcfast_country-de_session-k3f9a1x2_sesstime-10',    password: 'PASSWORD',  },});const page = await browser.newPage();await page.goto('https://api.ipify.org?format=json');console.log(await page.textContent('body'));await browser.close();
  4. 04

    One context per task, one session handle per context

    For concurrent work, launch the browser once without a proxy and give each new_context() its own proxy object. A fresh _session- handle means a fresh exit IP that then stays put for the task; _sesstime-10 holds it for up to ten minutes. A semaphore caps how many contexts are open at once.

    contexts.py
    import asyncioimport secretsfrom playwright.async_api import async_playwrightSERVER = "http://proxy.arcproxies.io:8000"BASE = "USERNAME_pool-arcfast_country-de"   # pool + country; the session is added per taskasync def run_task(browser, url):    handle = secrets.token_hex(4)                  # new handle, new exit IP    context = await browser.new_context(proxy={        "server": SERVER,        "username": f"{BASE}_session-{handle}_sesstime-10",        "password": "PASSWORD",    })    try:        page = await context.new_page()        await page.goto(url, wait_until="domcontentloaded")        return await page.title()    finally:        await context.close()async def main(urls):    async with async_playwright() as p:        browser = await p.chromium.launch()        limit = asyncio.Semaphore(5)               # contexts open at once        async def guarded(url):            async with limit:                return await run_task(browser, url)        print(await asyncio.gather(*(guarded(u) for u in urls)))        await browser.close()asyncio.run(main(["https://example.com/a", "https://example.com/b"]))
  5. 05

    Target a country or city per context

    Geo-targeting is the username, so a context per market is a different username per context. _country- takes a lowercase ISO code and _city- a city from the published list, lowercased with spaces removed. Setting locale and timezone_id to match makes the page render the way a local visitor sees it.

    markets.py
    MARKETS = {    # label:       (username,                                         locale,  timezone)    "de-berlin":   ("USERNAME_pool-arcfast_country-de_city-berlin",                           "de-DE", "Europe/Berlin"),    "fr-paris":    ("USERNAME_pool-arcfast_country-fr_city-paris",    "fr-FR", "Europe/Paris"),    "us-newyork":  ("USERNAME_pool-arcfast_country-us_city-newyork",  "en-US", "America/New_York"),}for label, (user, locale, tz) in MARKETS.items():    context = browser.new_context(        proxy={"server": "http://proxy.arcproxies.io:8000", "username": user, "password": "PASSWORD"},        locale=locale,        timezone_id=tz,    )    page = context.new_page()    page.goto("https://example.com/product/123")    print(label, page.locator("[data-price]").first.text_content())    context.close()
  6. 06

    Handle proxy errors and retry on a new exit

    Proxy failures do not come back as a response: goto() raises a net::ERR_… error or a timeout. Rate limits do come back as a response. Retry both on a fresh session handle with backoff, and stop after a few attempts. A wrong password fails every attempt the same way, so run the exit-IP check above first.

    retry.py
    import secretsimport timefrom playwright.sync_api import Error as PlaywrightErrorBASE = "USERNAME_pool-arcfast_country-de"def fetch_html(browser, url, attempts=3):    for attempt in range(attempts):        context = browser.new_context(proxy={            "server": "http://proxy.arcproxies.io:8000",            "username": f"{BASE}_session-{secrets.token_hex(4)}_sesstime-10",            "password": "PASSWORD",        })        try:            page = context.new_page()            response = page.goto(url, timeout=30_000)            if response and (response.status == 429 or response.status >= 500):                time.sleep(5 * 2 ** attempt)        # back off, then a new exit                continue            return page.content()        except PlaywrightError as err:              # TimeoutError is a subclass            print(f"attempt {attempt + 1}: {err.message.splitlines()[0]}")            time.sleep(2 ** attempt)        finally:            context.close()    raise RuntimeError(f"gave up on {url}")

Launch-level or context-level proxy in Playwright?

Both take the same object; the difference is scope. Pick by how many identities one browser process has to carry.

Behaviourlaunch(proxy=…)new_context(proxy=…)
ScopeEvery page in the browserPages in that context only
Identities per browserOneOne per context, as many as you open
Typical useA single agent or script, one task at a timeParallel tasks, one market or session each
Changing exit mid-runRelaunch the browserClose the context, open a new one with a new handle

Contexts do not share cookies or storage, so a context per sticky session keeps the cookie jar and the IP aligned: the site sees one visitor at one address for the length of the task.

Rotating or sticky sessions in a headless browser

A rotating username (no _session- token) lets each new connection leave from a different exit. That suits a plain HTTP client fetching independent URLs. A headless browser is different: one page load opens connections for the document, scripts, images and API calls, and a rotating username can spread those across several IPs inside one visit.

So in Playwright, default to sticky: one handle per context, _sesstime- long enough to finish the task, and a new handle for the next task. Rotating vs sticky sessions covers the trade-off in more depth. Sticky holds are best effort: an exit can drop before the time is up, so treat a changed IP as something to retry, not as a bug.

SOCKS5, HTTPS targets and CONNECT

  • Use `http://` for the server. The gateway speaks HTTP and SOCKS5 on the same port with the same credentials, but Playwright documents username and password for HTTP proxies only. http:// is the scheme that is documented to authenticate.
  • HTTPS sites still work over an `http://` proxy. The browser opens an HTTP CONNECT tunnel and runs TLS end to end with the site; the gateway sees the hostname, not the content.
  • `bypass` takes a comma-separated list of hosts that should not go through the proxy, such as an internal API the page also calls.

What a browser costs in bandwidth

A rendered page usually transfers far more than its HTML: scripts, fonts, images and third-party tags all count against a per-GB balance. Blocking images, media and fonts with context.route() is the largest single saving, but routing turns off Playwright's HTTP cache, so measure a multi-page walk both ways. Reducing proxy bandwidth has the full checklist, and Crawlee adds a crawl queue and session pool on top of Playwright if you need one.

The problem

Common pitfalls

Credentials inside the server URL

server is scheme, host and port. Put the username and password in their own fields; credentials pasted into the URL are not how the option is documented to work.

A rotating username for a multi-step task

Logged-out flows like search, filter, paginate can break when the IP changes between steps. Add _session-…_sesstime-… and keep the handle for the whole task.

Sharing one handle across parallel contexts

Two contexts with the same handle share one exit IP, which concentrates their traffic on one address and its rate limit. Mint a handle per context.

Background requests from branded Chrome

With a branded Chrome (channel="chrome"), the browser's own update and account checks can also go through the proxy and count as traffic. In our tests they appeared within seconds of launch.

FAQ

Questions

Something not covered? Write to contact@arcproxies.io.

How do I add proxy authentication in Playwright?

Pass proxy={"server": "http://host:port", "username": "…", "password": "…"} to chromium.launch() or to browser.new_context(). Playwright answers the proxy's authentication challenge itself, so no extension or extra code is needed.

Can each Playwright context use a different proxy?

Yes. browser.new_context(proxy={...}) sets the proxy for that context only. With ARC, give each context a different _session- handle in the username and each one gets its own exit IP, or a different _country- token and each one leaves from a different country.

How do I check which IP Playwright is using?

Open an IP echo service such as https://api.ipify.org?format=json in a page that uses the same proxy settings and read the body. With a sticky username, the address stays the same for the length of _sesstime-, so the check is valid for the task that follows.

Does Playwright work with a SOCKS5 proxy?

Playwright accepts socks5:// servers, but documents username and password authentication for HTTP proxies only. Because the ARC gateway serves HTTP and SOCKS5 on the same port with the same credentials, use http:// in Playwright and nothing is lost.

Why does my Playwright script fail with net::ERR errors through a proxy?

Proxy-level failures, such as rejected credentials or a dropped tunnel, surface as net::ERR_… exceptions from goto() rather than as an HTTP status. Check the credentials once with an IP echo page, then retry other failures on a new session handle with backoff.

Sources & review

Last reviewed by the ARC Proxies team. Prices are read from the live catalog at build time.

  1. [1]Playwright: Network: HTTP proxy
  2. [2]Playwright: BrowserType.launch: proxy option (Python)
  3. [3]Playwright: Browser.new_context: proxy option (Python)

Next

What people build with it

Use caseProxies for web scraping and data collectionA second provider that just works — Scrapy, requests, Crawlee or Playwright, self-serve, no sales call, bandwidth that never expires.Use caseResidential proxies for AI agentsBring your own residential IPs to browser-use, Playwright, Crawl4AI, n8n and any MCP tool — one sticky session per task, billed per GB.Use caseProxies for QA, app and localisation testingSee your own site the way a user in Tokyo, São Paulo or Munich sees it — currency, language, consent banner, CDN edge and all — before they report it.GlossarySticky sessionA sticky session is a setting on a rotating proxy that keeps the same exit IP address for a chosen period, usually by attaching a session identifier to the connection, so a multi-step task appears to come from one address from start to finish.GlossaryHeadless browserA headless browser is a real web browser engine running without a visible window and controlled by code instead of a person; it loads pages, executes JavaScript and builds the DOM like a normal browser, which makes it the tool for pages whose content only appears after scripts run.GuideRotating vs sticky sessionsRotating proxies change exit IP per connection; sticky sessions hold one IP for a set time. How each works, when to use which, and how to set them up.GuideHow to reduce proxy bandwidthCut proxy bandwidth by fetching HTML or JSON instead of rendering, blocking images and fonts, using compression, conditional requests and local caching.IntegrationCrawleeConfigure Crawlee's ProxyConfiguration with an authenticated gateway: rotating URLs, a sticky IP per session, geo per request, retries and concurrency.Use caseProxies for web scraping and data collectionA second provider that just works — Scrapy, requests, Crawlee or Playwright, self-serve, no sales call, bandwidth that never expires.

Get started

Get credentials and paste them in.

Sign up, buy a gigabyte, generate a username in the dashboard. Code WELCOME15 takes 15% off your first purchase; bandwidth never expires.