How to Use Proxies with Playwright in 2026: Complete Guide
Playwright has become the go-to browser automation framework for developers who need reliable, cross-browser testing and web scraping. But running Playwright without proxies is like driving without a seatbelt — you might be fine for a while, but eventually you will hit a wall of IP bans, CAPTCHAs, and geo-restrictions.
This guide covers everything you need to know about integrating proxies with Playwright in 2026, with real, copy-paste-ready code examples in both Node.js and Python.
Why Use Proxies with Playwright?
Playwright launches real browser instances (Chromium, Firefox, WebKit), which means target websites see a full browser fingerprint. However, they also see your IP address. Proxies solve several problems:
- IP rotation prevents rate limiting and bans during scraping
- Geo-targeting lets you access region-locked content
- Anonymity keeps your infrastructure IPs hidden
- Parallel scaling allows hundreds of concurrent sessions without IP conflicts
If you are unsure how much proxy bandwidth your project will consume, use our proxy cost calculator to estimate costs before committing to a provider.
Prerequisites
Before starting, make sure you have:
- Node.js 18+ or Python 3.9+ installed
- Playwright installed in your project
- Access to a proxy provider (HTTP, HTTPS, or SOCKS5)
Install Playwright (Node.js)
npm init -y
npm install playwright
npx playwright installInstall Playwright (Python)
pip install playwright
playwright installBasic Proxy Setup
Node.js — Browser-Level Proxy
The simplest way to use a proxy with Playwright is to pass it when launching the browser:
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({
proxy: {
server: 'http://proxy.example.com:8080'
}
});
const page = await browser.newPage();
await page.goto('https://httpbin.org/ip');
const content = await page.textContent('body');
console.log('Response:', content);
await browser.close();
})();Python — Browser-Level Proxy
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(proxy={
"server": "http://proxy.example.com:8080"
})
page = browser.new_page()
page.goto("https://httpbin.org/ip")
print(page.text_content("body"))
browser.close()Authenticated Proxy Setup
Most commercial proxy providers require username and password authentication.
Node.js — Authenticated Proxy
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({
proxy: {
server: 'http://proxy.example.com:8080',
username: 'your_username',
password: 'your_password'
}
});
const page = await browser.newPage();
await page.goto('https://httpbin.org/ip');
console.log(await page.textContent('body'));
await browser.close();
})();Python — Authenticated Proxy
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(proxy={
"server": "http://proxy.example.com:8080",
"username": "your_username",
"password": "your_password"
})
page = browser.new_page()
page.goto("https://httpbin.org/ip")
print(page.text_content("body"))
browser.close()SOCKS5 Proxy Support
Playwright supports SOCKS5 proxies natively. Simply change the protocol in the server URL:
const browser = await chromium.launch({
proxy: {
server: 'socks5://proxy.example.com:1080',
username: 'user',
password: 'pass'
}
});browser = p.chromium.launch(proxy={
"server": "socks5://proxy.example.com:1080",
"username": "user",
"password": "pass"
})Per-Context Proxy Configuration
One of Playwright’s most powerful features is browser contexts. You can assign a different proxy to each context, allowing multiple identities within a single browser instance.
Node.js — Proxy Per Context
const { chromium } = require('playwright');
(async () => {
// Launch browser with a per-context proxy server
// The browser must be launched with proxy config for contexts to use it
const browser = await chromium.launch({
proxy: {
server: 'per-context'
}
});
// Context 1: US proxy
const usContext = await browser.newContext({
proxy: {
server: 'http://us-proxy.example.com:8080',
username: 'user',
password: 'pass'
}
});
// Context 2: UK proxy
const ukContext = await browser.newContext({
proxy: {
server: 'http://uk-proxy.example.com:8080',
username: 'user',
password: 'pass'
}
});
const usPage = await usContext.newPage();
const ukPage = await ukContext.newPage();
await Promise.all([
usPage.goto('https://httpbin.org/ip'),
ukPage.goto('https://httpbin.org/ip')
]);
console.log('US IP:', await usPage.textContent('body'));
console.log('UK IP:', await ukPage.textContent('body'));
await browser.close();
})();Python — Proxy Per Context
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(proxy={"server": "per-context"})
us_context = browser.new_context(proxy={
"server": "http://us-proxy.example.com:8080",
"username": "user",
"password": "pass"
})
uk_context = browser.new_context(proxy={
"server": "http://uk-proxy.example.com:8080",
"username": "user",
"password": "pass"
})
us_page = us_context.new_page()
uk_page = uk_context.new_page()
us_page.goto("https://httpbin.org/ip")
uk_page.goto("https://httpbin.org/ip")
print("US IP:", us_page.text_content("body"))
print("UK IP:", uk_page.text_content("body"))
browser.close()Rotating Proxies with Playwright
For scraping at scale, you need proxy rotation. Here are two strategies.
Strategy 1: Rotate via New Contexts
Create a fresh context with a different proxy for each task:
const { chromium } = require('playwright');
const proxies = [
{ server: 'http://proxy1.example.com:8080', username: 'user', password: 'pass' },
{ server: 'http://proxy2.example.com:8080', username: 'user', password: 'pass' },
{ server: 'http://proxy3.example.com:8080', username: 'user', password: 'pass' },
];
(async () => {
const browser = await chromium.launch({ proxy: { server: 'per-context' } });
const urls = [
'https://example.com/page1',
'https://example.com/page2',
'https://example.com/page3',
];
for (let i = 0; i < urls.length; i++) {
const proxy = proxies[i % proxies.length];
const context = await browser.newContext({ proxy });
const page = await context.newPage();
try {
await page.goto(urls[i], { timeout: 30000 });
const title = await page.title();
console.log(`[${proxy.server}] ${urls[i]} -> ${title}`);
} catch (err) {
console.error(`Failed on ${urls[i]}: ${err.message}`);
} finally {
await context.close();
}
}
await browser.close();
})();Strategy 2: Use a Rotating Proxy Gateway
Most proxy providers offer a single gateway endpoint that automatically rotates IPs on each request. This is the simplest approach:
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({
proxy: {
server: 'http://gate.smartproxy.com:7777',
username: 'user-session-abc123',
password: 'password'
}
});
// Each new page request gets a different IP from the gateway
for (let i = 0; i < 5; i++) {
const context = await browser.newContext();
const page = await context.newPage();
await page.goto('https://httpbin.org/ip');
console.log(`Request ${i + 1}:`, await page.textContent('body'));
await context.close();
}
await browser.close();
})();Handling Proxy Failures and Retries
Proxies fail. Connections drop, proxies get banned, timeouts happen. You need retry logic:
const { chromium } = require('playwright');
async function fetchWithRetry(browser, url, proxies, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const proxy = proxies[attempt % proxies.length];
const context = await browser.newContext({ proxy });
const page = await context.newPage();
try {
const response = await page.goto(url, {
timeout: 20000,
waitUntil: 'domcontentloaded'
});
if (response && response.status() === 200) {
const content = await page.content();
await context.close();
return content;
}
console.warn(`Attempt ${attempt + 1}: HTTP ${response?.status()}`);
} catch (err) {
console.warn(`Attempt ${attempt + 1} failed: ${err.message}`);
} finally {
await context.close();
}
}
throw new Error(`All ${maxRetries} attempts failed for ${url}`);
}
// Usage
(async () => {
const proxies = [
{ server: 'http://proxy1.example.com:8080', username: 'user', password: 'pass' },
{ server: 'http://proxy2.example.com:8080', username: 'user', password: 'pass' },
{ server: 'http://proxy3.example.com:8080', username: 'user', password: 'pass' },
];
const browser = await chromium.launch({ proxy: { server: 'per-context' } });
try {
const html = await fetchWithRetry(browser, 'https://example.com', proxies);
console.log('Success, got', html.length, 'bytes');
} catch (err) {
console.error(err.message);
}
await browser.close();
})();Combining Playwright with Stealth Plugins
Websites use fingerprinting to detect automation. Playwright itself is more stealthy than Puppeteer out of the box, but you can improve it further.
Using playwright-extra and Stealth Plugin (Node.js)
npm install playwright-extra puppeteer-extra-plugin-stealthconst { chromium } = require('playwright-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
chromium.use(StealthPlugin());
(async () => {
const browser = await chromium.launch({
proxy: {
server: 'http://proxy.example.com:8080',
username: 'user',
password: 'pass'
},
headless: true
});
const context = await browser.newContext({
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
viewport: { width: 1920, height: 1080 },
locale: 'en-US',
timezoneId: 'America/New_York'
});
const page = await context.newPage();
// Block unnecessary resources to save bandwidth
await page.route('**/*.{png,jpg,jpeg,gif,svg,woff,woff2,ttf}', route => route.abort());
await page.goto('https://bot.sannysoft.com');
await page.screenshot({ path: 'stealth-test.png', fullPage: true });
await browser.close();
})();To verify your browser fingerprint setup is effective, test it against our browser fingerprint tester.
Manual Stealth Tweaks (Python)
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(
proxy={
"server": "http://proxy.example.com:8080",
"username": "user",
"password": "pass"
},
headless=True
)
context = browser.new_context(
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
viewport={"width": 1920, "height": 1080},
locale="en-US",
timezone_id="America/New_York",
permissions=["geolocation"],
geolocation={"latitude": 40.7128, "longitude": -74.0060},
)
page = context.new_page()
# Override webdriver property
page.add_init_script("""
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
});
""")
page.goto("https://httpbin.org/ip")
print(page.text_content("body"))
browser.close()Performance Tips
1. Reuse Browser Instances
Launching a browser is expensive. Launch once and create multiple contexts:
// Good: One browser, many contexts
const browser = await chromium.launch({ proxy: { server: 'per-context' } });
for (const task of tasks) {
const context = await browser.newContext({ proxy: task.proxy });
// ... do work ...
await context.close(); // Close context, not browser
}
await browser.close(); // Close browser when all done2. Block Unnecessary Resources
Save proxy bandwidth by blocking images, fonts, and tracking scripts:
await page.route('**/*', (route) => {
const resourceType = route.request().resourceType();
if (['image', 'font', 'media', 'stylesheet'].includes(resourceType)) {
return route.abort();
}
return route.continue();
});3. Connection Pooling with Concurrency Control
const { chromium } = require('playwright');
async function scrapeWithPool(urls, proxies, concurrency = 5) {
const browser = await chromium.launch({ proxy: { server: 'per-context' } });
const results = [];
let index = 0;
async function worker() {
while (index < urls.length) {
const currentIndex = index++;
const url = urls[currentIndex];
const proxy = proxies[currentIndex % proxies.length];
const context = await browser.newContext({ proxy });
const page = await context.newPage();
try {
await page.goto(url, { timeout: 20000 });
results[currentIndex] = {
url,
title: await page.title(),
status: 'success'
};
} catch (err) {
results[currentIndex] = { url, error: err.message, status: 'failed' };
} finally {
await context.close();
}
}
}
const workers = Array.from({ length: concurrency }, () => worker());
await Promise.all(workers);
await browser.close();
return results;
}4. Use Persistent Contexts for Session Stickiness
When you need to maintain cookies and session data across proxy reconnections:
const context = await chromium.launchPersistentContext('/tmp/playwright-session', {
proxy: {
server: 'http://sticky-proxy.example.com:8080',
username: 'user-session-fixed123',
password: 'pass'
}
});Troubleshooting Common Issues
Error: net::ERR_PROXY_CONNECTION_FAILED
Cause: The proxy server is unreachable or the address is wrong.
Fix: Verify the proxy is online by testing with cURL first:
curl -x http://proxy.example.com:8080 https://httpbin.org/ipError: net::ERR_TUNNEL_CONNECTION_FAILED
Cause: The proxy does not support HTTPS tunneling (CONNECT method).
Fix: Use an HTTPS-capable proxy or switch to SOCKS5.
Error: Timeout waiting for page to load
Cause: Slow proxy or proxy bandwidth throttling.
Fix: Increase the timeout or switch to a faster proxy:
await page.goto(url, { timeout: 60000, waitUntil: 'domcontentloaded' });Proxy Authentication Popup Appears in Headed Mode
Cause: Some proxies trigger a browser auth dialog instead of using the configured credentials.
Fix: Ensure you pass username and password in the proxy config. If using headed mode for debugging, Playwright handles this automatically when credentials are provided.
WebSocket Connections Bypass Proxy
Cause: By default, Playwright routes WebSocket traffic through the proxy, but some configurations may not.
Fix: Make sure your proxy supports WebSocket tunneling, or use page.route() to intercept and redirect WebSocket requests.
Summary
Playwright’s built-in proxy support is one of the best among browser automation frameworks. The key features that make it stand out:
- Native proxy configuration at both browser and context levels
- Per-context proxy switching without relaunching the browser
- SOCKS5 support out of the box
- Authentication handling without browser extensions
For any serious scraping or automation project, combine Playwright with rotating residential proxies, stealth plugins, and proper error handling. Use our proxy cost calculator to estimate your bandwidth needs, and test your setup with the browser fingerprint tester before going to production.
- How to Use Proxies with cURL in 2026: Complete Guide
- How to Use Proxies with Node.js Axios in 2026: Complete Guide
- Best Proxies for Amazon 2026: Complete Guide
- Best Proxies for Discord 2026: Bot Hosting & Account Management
- Best Proxies for eBay 2026: Complete Guide
- Best Proxies for Netflix 2026: Geo-Unblocking & Catalog Access
- How to Use Proxies with cURL in 2026: Complete Guide
- How to Use Proxies with Node.js Axios in 2026: Complete Guide
- Best Proxies for Amazon 2026: Complete Guide
- Best Proxies for Discord 2026: Bot Hosting & Account Management
- Best Proxies for eBay 2026: Complete Guide
- Best Proxies for Netflix 2026: Geo-Unblocking & Catalog Access
- How to Use Proxies with cURL in 2026: Complete Guide
- How to Use Proxies with Node.js Axios in 2026: Complete Guide
- Best Proxies for Amazon 2026: Complete Guide
- Best Proxies for Discord 2026: Bot Hosting & Account Management
- Best Proxies for eBay 2026: Complete Guide
- Best Proxies for Netflix 2026: Geo-Unblocking & Catalog Access
Related Reading
- How to Use Proxies with cURL in 2026: Complete Guide
- How to Use Proxies with Node.js Axios in 2026: Complete Guide
- Best Proxies for Amazon 2026: Complete Guide
- Best Proxies for Discord 2026: Bot Hosting & Account Management
- Best Proxies for eBay 2026: Complete Guide
- Best Proxies for Netflix 2026: Geo-Unblocking & Catalog Access