Short answer: pass a proxy object with server, username and password to browser.launch() to proxy the whole browser, or to browser.new_context() to proxy a single context. Keep credentials out of the server URL, and verify the exit IP before you run anything real.
This guide shows both patterns in Python and Node.js, how to save bandwidth on per-GB plans, how to test localized pages, and how to fix the most common errors. It is part of our guide to proxies for web scraping. If you work with plain HTTP clients instead, see Python requests proxy.
Use proxies for public data and legitimate testing only: market research, price monitoring, ad verification, SEO checks and QA. Respect each site’s terms and robots.txt.
Prerequisites
- Python:
pip install playwrightthenplaywright install chromium - Node.js:
npm init playwright@latestornpm i playwrightthennpx playwright install chromium - Proxy credentials from your dashboard: username, password, host and port. New to these terms? Read what a proxy host and port are.
Proxy the whole browser (Python)
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(
proxy={
"server": "http://{PROXY_HOST}:{PORT}",
"username": "USERNAME",
"password": "PASSWORD",
}
)
page = browser.new_page()
page.goto("https://httpbin.org/ip", timeout=60_000)
print(page.inner_text("body"))
browser.close()
The printed origin should be the proxy’s IP, not yours. For more ways to check a proxy, see how to test proxies.
Proxy the whole browser (Node.js)
import { chromium } from "playwright";
const browser = await chromium.launch({
proxy: {
server: "http://{PROXY_HOST}:{PORT}",
username: "USERNAME",
password: "PASSWORD",
},
});
const page = await browser.newPage();
await page.goto("https://httpbin.org/ip", { timeout: 60000 });
console.log(await page.innerText("body"));
await browser.close();
One proxy per context
A browser context is an isolated session with its own cookies and storage. You can give each context its own proxy, which is useful when you compare what different locations see, or when each task needs its own IP.
from playwright.sync_api import sync_playwright
PROXIES = [
{"server": "http://{PROXY_HOST}:{PORT}", "username": "USERNAME", "password": "PASSWORD"},
{"server": "http://{PROXY_HOST_2}:{PORT_2}", "username": "USERNAME", "password": "PASSWORD"},
]
with sync_playwright() as p:
browser = p.chromium.launch()
for proxy in PROXIES:
context = browser.new_context(proxy=proxy)
page = context.new_page()
page.goto("https://httpbin.org/ip", timeout=60_000)
print(page.inner_text("body"))
context.close()
browser.close()
Node.js is the same: browser.newContext({ proxy }). Very old Playwright versions required a placeholder proxy at launch for Chromium per-context proxies, so update to a recent release if the per-context proxy is ignored.
Bypass the proxy for some hosts
proxy={
"server": "http://{PROXY_HOST}:{PORT}",
"username": "USERNAME",
"password": "PASSWORD",
"bypass": "localhost,.internal.example.com",
}
Hosts matching bypass connect directly. This is handy for local test servers and internal APIs.
Save bandwidth on per-GB plans
A headless browser downloads everything a page references. On per-GB plans such as residential proxies, block what you do not need:
BLOCKED = {"image", "font", "media"}
def route_handler(route):
if route.request.resource_type in BLOCKED:
route.abort()
else:
route.continue_()
context = browser.new_context(proxy=proxy)
context.route("**/*", route_handler)
Only block what your task does not need. Some pages render differently, or break, without images or fonts. Measure the effect on a small sample first. With time-based plans such as USA 4G/5G mobile proxies (unlimited bandwidth), blocking is optional.
Geo-testing: match locale and timezone
For QA of localized content, set the context locale, timezone and geolocation to match the proxy’s location, so what you see reflects a real visitor from that place:
context = browser.new_context(
proxy=proxy,
locale="en-US",
timezone_id="America/New_York",
geolocation={"latitude": 40.7128, "longitude": -74.0060},
permissions=["geolocation"],
)
Use this for legitimate checks such as localized prices, ads and content. Also check that your browser is not exposing a different IP through WebRTC, see how to disable WebRTC in Chrome.
Rotation
If your plan gives you a rotating gateway, one proxy configuration is enough, and new IPs appear on the schedule or per connection that you set. USA 4G/5G proxies rotate every 5, 10 or 30 minutes depending on your setting. If you need the same IP across several page loads, use a sticky session or a static IP and keep it within one context. Check your dashboard for the exact options. For trade-offs, see rotating vs static residential proxies.
Handle failures
from playwright.sync_api import Error, TimeoutError as PWTimeout
import random
import time
def goto_with_retry(page, url, retries=3):
for attempt in range(retries):
try:
response = page.goto(url, timeout=60_000, wait_until="domcontentloaded")
if response and response.status in (429, 503):
time.sleep(2 ** attempt + random.random())
continue
return response
except (PWTimeout, Error) as exc:
print(f"attempt {attempt + 1} failed: {exc}")
time.sleep(2 ** attempt + random.random())
raise RuntimeError(f"Failed after {retries} attempts: {url}")
| Symptom | Likely cause | Fix |
|---|---|---|
net::ERR_PROXY_CONNECTION_FAILED | Wrong host or port, or proxy unreachable | Verify {PROXY_HOST} and {PORT}, test from the terminal |
net::ERR_TUNNEL_CONNECTION_FAILED | Proxy refused the tunnel, often bad credentials or a blocked destination | Check username and password and the whitelist, try another target |
| Navigation fails right after launch | Credentials in the server URL instead of separate fields | Use username and password keys |
TimeoutError | Slow target or heavy page | Raise the timeout, use domcontentloaded, block heavy resources, retry with backoff |
| HTTP 429 | Too many requests to the target | Lower concurrency, add delays, back off |
| Proxy ignored in a context | Old Playwright version | Update Playwright and browsers |
The exact error text for wrong credentials can vary between Chromium versions, so treat the table as a starting point and check the Playwright log output with DEBUG=pw:api (Node.js) or PWDEBUG=console if needed.
Next steps
Once one page loads through the proxy, scale gradually and watch your error rate and bandwidth. For choosing between residential, mobile and ISP proxies and estimating cost, read proxies for web scraping.
Need proxies to test with? Compare residential proxies (per GB), USA 4G/5G mobile proxies (per day, unlimited bandwidth) and static ISP proxies (30-day plans), or see the pricing page. Every plan comes with a 24-hour money-back guarantee.
FAQ
How do I set a proxy in Playwright?
Pass a proxy option with server, username and password to launch() for the whole browser, or to new_context() (newContext() in Node.js) for one context.
Can I use a different proxy for each Playwright context?
Yes. Create one context per proxy with browser.new_context(proxy=...). Each context also has isolated cookies and storage.
How do I use proxy authentication in Playwright?
Set the username and password fields of the proxy option instead of embedding them in the server URL.
Does Playwright support SOCKS proxies?
Yes, Chromium accepts a socks5://host:port server value. Chromium does not support SOCKS5 with username and password authentication, so use an HTTP proxy for authenticated setups or IP whitelisting for SOCKS.
How can I reduce proxy bandwidth in Playwright?
Block images, fonts and media with route(), wait for domcontentloaded instead of full load, and cache pages you have already fetched.
Why is my Playwright proxy not working?
Check host, port and credentials, make sure you are on a recent Playwright version, and confirm the exit IP with a simple page such as httpbin.org/ip before testing your real target.