Roundproxies Logo
knowledgebase

How to use Playwright Proxy in 2026: Full setup guide

Web scraping and browser automation face constant IP blocking challenges. Proxies in Playwright route your browser traffic through intermediary servers, masking your real IP address.

This prevents rate limiting and access restrictions. You'll bypass geo-blocks and scale your scraping operations safely.

What a proxy actually does in Playwright

A proxy sits between your browser instance and the target site. With a Playwright proxy setup in place, every request the page makes leaves through a remote server, and the site logs that server's IP rather than yours. Responses travel back the same way. That is the whole trick: your automation keeps running locally, but the traffic appears to originate somewhere else.

Playwright accepts HTTP, HTTPS and SOCKS5 proxies, and you can attach one in two places: at browser launch, or on an individual browser context. That choice decides which pages are covered, and it is where most setups go wrong.

Launch level vs context level

A proxy passed to chromium.launch() applies to every context and every page that browser creates. A proxy passed to browser.newContext() applies only to pages opened from that context. I default to context level. One browser process can then hold several contexts, each with its own exit IP, cookies and local storage, which is what you want for parallel scraping or for checking whether a checkout flow behaves differently for a visitor in Germany than for one in the US.

The most common complaint about Playwright ignoring a proxy has a single cause. browser.newPage() opens a page in the browser's default context, so a proxied context created afterwards never covers it. Build the context first, then call newPage() on that context. If the page came from the browser object, it is going out over your own IP no matter what the proxy config says.

Chromium on Windows adds a wrinkle: the browser process needs a proxy at launch before per-context proxies take effect. If every context sets its own proxy, the launch value is never used for real traffic, so a placeholder string such as per-context is enough.

Authentication

Paid pools almost always require a login. Playwright expects the host and port in server and the credentials in separate username and password fields. Pasting a full http://user:pass@host:port string into server does not authenticate you. Neither does setExtraHTTPHeaders(), because those headers are sent to the website, not to the proxy tunnel. Keep the credentials in environment variables so they stay out of your repo.

Playwright also takes a bypass list, a set of hosts that skip the proxy entirely. Handy when your scraper talks to a local API or an internal staging box while all external traffic goes out through the pool.

What a proxy does not fix

Request interception through page.route() is a different tool. It blocks or rewrites requests inside the browser, which is useful for killing images and CSS to save bandwidth, but it does not change the network path and your IP stays the same. A proxy changes where the traffic comes from. It does nothing about the browser fingerprint the site reads once the request arrives, so a clean residential IP paired with an obviously automated browser will still get flagged.

Why Use Proxies in Playwright?

Testing geographically restricted content requires local IPs. Proxies provide IPs from specific countries or cities.

Rate limiting kicks in after repeated requests. Rotating proxies distribute traffic across multiple IP addresses, preventing blocks.

Web scraping at scale needs IP diversity. A single IP making thousands of requests triggers anti-bot measures immediately.

Setting Up a Basic Proxy in Playwright

The simplest approach configures proxies during browser launch. This applies to all pages and contexts.

JavaScript/Node.js Setup

const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch({
    proxy: {
      server: 'http://proxy-server.com:8080'
    }
  });
  
  const page = await browser.newPage();
  await page.goto('https://httpbin.org/ip');
  
  console.log(await page.content());
  await browser.close();
})();

The server parameter accepts the proxy URL and port. Playwright routes all traffic through this server automatically.

Python Setup

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(
        proxy={
            'server': 'http://proxy-server.com:8080'
        }
    )
    page = browser.new_page()
    page.goto('https://httpbin.org/ip')
    
    print(page.content())
    browser.close()

The Python API mirrors JavaScript. Both versions support Chromium, Firefox, and WebKit browsers.

HTTPBin returns your outgoing IP. This verifies proxy functionality instantly without complex testing.

Configuring Authenticated Proxies

Premium proxies require authentication. Username and password credentials prevent unauthorized access.

Adding Proxy Credentials

const browser = await chromium.launch({
  proxy: {
    server: 'http://proxy-server.com:8080',
    username: 'your_username',
    password: 'your_password'
  }
});

Credentials authenticate your connection. The proxy server validates these before allowing traffic through.

Python Authentication

browser = p.chromium.launch(
    proxy={
        'server': 'http://proxy-server.com:8080',
        'username': 'your_username',
        'password': 'your_password'
    }
)

Both languages handle HTTP Basic Authentication automatically. No manual header configuration needed.

Using Environment Variables for Proxy Configuration

Hardcoding credentials creates security risks. Environment variables separate sensitive data from code.

JavaScript Environment Setup

require('dotenv').config();

function convertProxyToPlaywrightFormat(proxyUrl) {
    const url = new URL(proxyUrl);
    return {
        server: `${url.protocol}//${url.host}`,
        username: url.username,
        password: url.password
    };
}

const proxyOptions = convertProxyToPlaywrightFormat(process.env.PROXY_URL);

const browser = await chromium.launch({
    proxy: proxyOptions
});

Create a .env file with: PROXY_URL=http://user:password@proxy-host:8080

The conversion function parses the URL format. It extracts the individual components Playwright needs.

Python Environment Variables

import os
from urllib.parse import urlparse

def parse_proxy_url(proxy_url):
    parsed = urlparse(proxy_url)
    return {
        'server': f'{parsed.scheme}://{parsed.hostname}:{parsed.port}',
        'username': parsed.username,
        'password': parsed.password
    }

proxy_config = parse_proxy_url(os.environ['PROXY_URL'])

browser = p.chromium.launch(proxy=proxy_config)

URL parsing extracts credentials safely. Your proxy details stay outside version control systems.

Proxy Configuration at Context Level

Browser contexts isolate cookies, storage, and cache. Different contexts can use different proxies simultaneously.

Multiple Proxies in One Browser

const browser = await chromium.launch();

const context1 = await browser.newContext({
    proxy: {
        server: 'http://proxy1.com:8080',
        username: 'user1',
        password: 'pass1'
    }
});

const context2 = await browser.newContext({
    proxy: {
        server: 'http://proxy2.com:8080',
        username: 'user2',
        password: 'pass2'
    }
});

const page1 = await context1.newPage();
const page2 = await context2.newPage();

Each context operates independently. Page1 and page2 have completely separate sessions and proxies.

This cuts memory usage. One browser instance handles multiple proxy connections efficiently.

Python Context Proxies

browser = p.chromium.launch()

context1 = browser.new_context(
    proxy={'server': 'http://proxy1.com:8080'}
)

context2 = browser.new_context(
    proxy={'server': 'http://proxy2.com:8080'}
)

page1 = context1.new_page()
page2 = context2.new_page()

Context isolation prevents cookie leakage. Testing multiple accounts simultaneously becomes straightforward.

Implementing Proxy Rotation

Manual proxy rotation prevents IP bans. Randomly selecting proxies distributes requests across multiple addresses.

JavaScript Proxy Pool

const proxyList = [
    { server: 'http://proxy1.com:8080', username: 'user1', password: 'pass1' },
    { server: 'http://proxy2.com:8080', username: 'user2', password: 'pass2' },
    { server: 'http://proxy3.com:8080', username: 'user3', password: 'pass3' }
];

function getRandomProxy() {
    return proxyList[Math.floor(Math.random() * proxyList.length)];
}

const browser = await chromium.launch({
    proxy: getRandomProxy()
});

Each launch selects a different proxy randomly. This simple rotation avoids sequential patterns websites detect.

Python Rotation Implementation

import random

proxy_list = [
    {'server': 'http://proxy1.com:8080', 'username': 'user1', 'password': 'pass1'},
    {'server': 'http://proxy2.com:8080', 'username': 'user2', 'password': 'pass2'},
    {'server': 'http://proxy3.com:8080', 'username': 'user3', 'password': 'pass3'}
]

browser = p.chromium.launch(
    proxy=random.choice(proxy_list)
)

Random selection provides basic rotation. For production, implement round-robin or weighted selection algorithms.

Using SOCKS5 Proxies

SOCKS5 proxies handle any traffic type. Unlike HTTP proxies, they support UDP and provide better anonymity.

SOCKS5 Configuration

const browser = await chromium.launch({
    proxy: {
        server: 'socks5://proxy-server.com:1080',
        username: 'your_username',
        password: 'your_password'
    }
});

Notice the socks5:// protocol prefix. This tells Playwright to use SOCKS5 instead of HTTP/HTTPS.

Python SOCKS5

browser = p.chromium.launch(
    proxy={
        'server': 'socks5://proxy-server.com:1080',
        'username': 'your_username',
        'password': 'your_password'
    }
)

SOCKS5 doesn't modify traffic headers. This makes detection harder compared to HTTP proxies.

Bypassing Proxy for Specific Domains

Local resources shouldn't route through proxies. The bypass parameter excludes specific domains.

Adding Bypass Rules

const browser = await chromium.launch({
    proxy: {
        server: 'http://proxy-server.com:8080',
        bypass: 'localhost,127.0.0.1,.local'
    }
});

Comma-separated patterns define bypass rules. Wildcards work: *.example.com bypasses all subdomains.

Python Bypass Configuration

browser = p.chromium.launch(
    proxy={
        'server': 'http://proxy-server.com:8080',
        'bypass': 'localhost,127.0.0.1,.local'
    }
)

This prevents proxy overhead for local development servers. API endpoints on localhost connect directly.

Handling Proxy Errors and Retries

Proxies fail unpredictably. Timeouts, connection refusals, and authentication errors require retry logic.

Implementing Retry Strategy

async function tryNavigate(page, url, maxRetries = 3) {
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
        try {
            await page.goto(url, { timeout: 30000 });
            return;
        } catch (error) {
            console.log(`Attempt ${attempt} failed: ${error.message}`);
            
            if (attempt === maxRetries) {
                throw error;
            }
            
            await page.waitForTimeout(2000);
        }
    }
}

This function retries navigation up to three times. Exponential backoff prevents hammering failed proxies.

Python Retry Implementation

def try_navigate(page, url, max_retries=3):
    for attempt in range(1, max_retries + 1):
        try:
            page.goto(url, timeout=30000)
            return
        except Exception as e:
            print(f'Attempt {attempt} failed: {str(e)}')
            
            if attempt == max_retries:
                raise
            
            page.wait_for_timeout(2000)

Retry logic noticeably improves reliability. Even flaky proxies work consistently with proper error handling.

Troubleshooting Common Proxy Issues

Authentication Failures

Double-check credentials match your proxy provider's dashboard. Case sensitivity matters for usernames and passwords.

Verify HTTP Basic Authentication support. Some providers use IP whitelisting instead of credentials.

Connection Timeouts

Increase timeout values: page.goto(url, { timeout: 60000 }) extends to 60 seconds.

Test proxy connectivity separately. Use curl to verify the proxy responds: curl -x http://proxy:port http://httpbin.org/ip

Headless Mode Issues

Some proxies block headless browsers. Launch with headless: false for testing:

const browser = await chromium.launch({
    headless: false,
    proxy: { server: 'http://proxy-server.com:8080' }
});

Headless detection bypasses can help. Setting a user agent sometimes resolves this.

SSL Certificate Errors

Ignore HTTPS errors during development:

const context = await browser.newContext({
    ignoreHTTPSErrors: true
});

Production environments should use valid SSL proxies. Certificate errors indicate security issues.

Proxy Performance Optimization

Resource blocking reduces bandwidth usage. Images and fonts aren't needed for scraping.

Blocking Unnecessary Resources

await context.route('**/*', route => {
    const type = route.request().resourceType();
    
    if (['image', 'stylesheet', 'font'].includes(type)) {
        route.abort();
    } else {
        route.continue();
    }
});

This blocks images, CSS, and fonts. Page load times decrease by 50-70% typically.

Python Resource Blocking

def handle_route(route):
    if route.request.resource_type in ['image', 'stylesheet', 'font']:
        route.abort()
    else:
        route.continue_()

context.route('**/*', handle_route)

Proxy bandwidth costs drop. You pay for data transferred through proxies.

Real Production Example

Here's a complete production-ready implementation with error handling, rotation, and retries:

require('dotenv').config();
const { chromium } = require('playwright');

const proxyList = [
    process.env.PROXY1_URL,
    process.env.PROXY2_URL,
    process.env.PROXY3_URL
].map(url => {
    const parsed = new URL(url);
    return {
        server: `${parsed.protocol}//${parsed.host}`,
        username: parsed.username,
        password: parsed.password
    };
});

async function scrapeWithProxy(url) {
    const proxy = proxyList[Math.floor(Math.random() * proxyList.length)];
    
    const browser = await chromium.launch({ proxy });
    
    try {
        const context = await browser.newContext({
            ignoreHTTPSErrors: true
        });
        
        await context.route('**/*', route => {
            const type = route.request().resourceType();
            if (['image', 'stylesheet', 'font'].includes(type)) {
                return route.abort();
            }
            route.continue();
        });
        
        const page = await context.newPage();
        
        for (let attempt = 1; attempt <= 3; attempt++) {
            try {
                await page.goto(url, { timeout: 30000 });
                const content = await page.content();
                return content;
            } catch (error) {
                if (attempt === 3) throw error;
                await page.waitForTimeout(2000 * attempt);
            }
        }
    } finally {
        await browser.close();
    }
}

(async () => {
    const result = await scrapeWithProxy('https://example.com');
    console.log('Scraped successfully');
})();

This implementation combines rotation, retries, resource blocking, and error handling, ready for production.

Choosing the Right Proxy Type

Free proxies fail 80% of the time. They're overcrowded and blacklisted by most sites.

Datacenter proxies cost $1-5 per IP monthly. They're fast but easier to detect.

Residential proxies range from $5-15 per GB. They're slower but nearly undetectable.

Rotating residential proxies handle rotation automatically. You get a new IP per request without manual management.

Conclusion

Setting up proxies in Playwright requires understanding browser launch configuration and context isolation. Authentication adds security for premium proxies.

Rotation distributes traffic across multiple IPs. This prevents rate limiting and blocks effectively.

Error handling with retries makes proxy usage reliable. Production systems need comprehensive retry logic.

Context-level proxy configuration maximizes efficiency. One browser handles multiple proxy connections simultaneously.

FAQ

Can I use free proxies with Playwright?

Yes, but free proxies have 70-90% failure rates. They're suitable only for learning and testing, not production scraping.

What's the difference between browser-level and context-level proxies?

Browser-level proxies apply to all contexts and pages. Context-level proxies allow different IPs per isolated session within one browser.

Do proxies work in headless mode?

Most proxies work in headless mode. Some providers block headless browsers, requiring headless: false configuration.

How many proxies should I rotate between?

For basic scraping, 5-10 proxies suffice. Large-scale operations need 50-100+ proxies to handle request volume safely.

Can I change proxies mid-session?

No. Proxies are set during browser or context creation. Create a new browser or context to switch proxies.