Playwright Proxy Not Working: Causes, Fixes & Setup Guide (2026)

Playwright proxy issues fall into a handful of root causes — wrong configuration placement, missing authentication credentials, credential format errors, bypass rule syntax, or environment-specific network restrictions in Docker and CI. This guide covers every documented cause and fix, the correct proxy setup for both JavaScript and Python, how to verify the proxy is actually routing traffic, and how to integrate residential proxies for scraping at scale.

⚡ Key Takeaways

  • Playwright proxy must include the protocol prefixhttp://proxy:port not just proxy:port.
  • Proxy set at browser.launch() applies globally. Proxy set at browser.newContext() applies per browser context — and overrides the launch-level proxy.
  • For authenticated proxies, use the username and password fields — not credentials embedded in the server URL string.
  • Playwright adds <-loopback> to the bypass list by default — localhost and 127.0.0.1 always bypass the proxy unless you disable this behaviour.[1]
  • The fastest verification: navigate to https://ipinfo.io/ip immediately after launch — if the IP matches your proxy, it is working.
  • In Docker and CI, the proxy address must be reachable from inside the container — localhost in the proxy URL refers to the container, not the host machine.

Correct Playwright Proxy Configuration

Before debugging, confirm your proxy configuration matches Playwright's expected format exactly. The most common cause of "proxy not working" is a syntax error in the configuration itself — missing protocol, wrong field name, or credentials in the wrong place.[2]

JavaScript / TypeScript — Global (launch level)

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

const browser = await chromium.launch({
  proxy: {
    server:   'http://gate.nstproxy.io:8080',  // ✅ include protocol prefix
    username: 'your_username',               // ✅ separate field, not in URL
    password: 'your_password',               // ✅ separate field, not in URL
  }
});

const page = await browser.newPage();
await page.goto('https://ipinfo.io/ip');
console.log(await page.textContent('body')); // Should show proxy IP
await browser.close();

JavaScript — Per-Context (overrides launch proxy)

const browser = await chromium.launch(); // no proxy at launch

const context = await browser.newContext({
  proxy: {
    server:   'http://gate.nstproxy.io:8080',
    username: 'your_username',
    password: 'your_password',
  }
});

const page = await context.newPage();
await page.goto('https://ipinfo.io/ip');

playwright.config.ts — Global for all tests

// playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
  use: {
    proxy: {
      server:   'http://gate.nstproxy.io:8080',
      username: 'your_username',
      password: 'your_password',
    },
  },
});
⚠️ SOCKS5 proxies use a different prefix: socks5://gate.nstproxy.io:1080. HTTP proxies use http://. Mixing these up is a silent failure — Playwright will not throw an error but the proxy will not route traffic correctly.

Step 1: Verify the Proxy Is Actually Routing Traffic

Never assume the proxy is working based on the absence of errors. Always make an explicit IP check immediately after browser launch — before running any scraping logic:

// Verification pattern — add to every Playwright proxy setup
const page = await context.newPage();

// Method 1: text content of IP check service
await page.goto('https://ipinfo.io/ip');
const ip = (await page.textContent('body')).trim();
console.log('Exit IP:', ip); // Must match proxy IP, not your real IP

// Method 2: JSON response with full geo context
await page.goto('https://ipinfo.io/json');
const data = JSON.parse(await page.textContent('body'));
console.log('IP:', data.ip, '|  City:', data.city, '| Country:', data.country);

// Method 3: evaluate() for cleaner extraction
await page.goto('https://api.ipify.org?format=json');
const result = await page.evaluate(() => JSON.parse(document.body.textContent));
console.log('Proxy IP confirmed:', result.ip);

If the returned IP matches your real machine IP instead of the proxy, the proxy configuration is not being applied — the proxy is silently bypassed, not failing with an error.

Common Errors and Exact Fixes

ERR_PROXY_CONNECTION_FAILED

Playwright cannot connect to the proxy server

🔍 Cause: Wrong proxy host/port, proxy server is offline, firewall blocking the port, or the proxy address is unreachable from the current network.

Fix: Test the proxy endpoint manually with curl -x http://user:pass@host:port https://ipinfo.io/ip before running Playwright. If curl fails, the issue is network/proxy server availability — not Playwright configuration. In Docker, ensure the proxy host is reachable from inside the container.

407 Proxy Authentication Required

Proxy rejected credentials

🔍 Cause: Credentials are missing, incorrect, or provided in the wrong format (embedded in the server URL instead of separate fields). Playwright requires credentials in the username and password fields — not as http://user:pass@host:port in the server string.

Fix: Move credentials out of the URL and into the dedicated fields. If your proxy uses IP whitelisting instead of username/password, ensure your current machine's IP is whitelisted in the proxy provider dashboard.

Proxy silently bypassed (IP unchanged)

No error but the proxy is not routing traffic

🔍 Cause 1: Missing protocol prefix. proxy: { server: 'host:port' } is invalid — must be 'http://host:port'.
Cause 2: Context-level proxy overrides launch-level proxy with an empty/different config.
Cause 3: The target URL is on the bypass list. Playwright adds <-loopback> by default — localhost always bypasses.
Cause 4: Environment variable HTTP_PROXY / HTTPS_PROXY is conflicting with the Playwright proxy config.

Fix: Add the http:// prefix. Check for context-level proxy config that might be clearing the launch config. Review the bypass field. Unset conflicting environment variables or explicitly set them to match your proxy.

net::ERR_TIMED_OUT

Page load times out through the proxy

🔍 Cause: The proxy is reachable but the target website is slow to respond, or the proxy itself is overloaded. Default Playwright timeout is 30 seconds.

Fix: Increase the timeout: page.goto(url, { timeout: 60000 }). Switch to a lower-latency proxy endpoint. Use residential proxies with health monitoring to avoid degraded IPs that add unnecessary latency.

net::ERR_CERT_AUTHORITY_INVALID

SSL certificate error through proxy

🔍 Cause: The proxy performs SSL interception with a self-signed certificate, or the proxy's certificate chain is incomplete. Common with corporate proxies.

Fix: For testing environments only: chromium.launch({ ignoreHTTPSErrors: true }) or browser.newContext({ ignoreHTTPSErrors: true }). For production, install the proxy's root certificate in the system trust store.

launch() vs newContext(): Proxy Scope Explained

This is the most common source of confusion. Playwright supports proxy configuration at two levels, and they interact in a non-obvious way:

Level Method Applies To Overridden By
Browser-level browser.launch({ proxy: {...} }) All contexts and pages unless overridden Context-level proxy config
Context-level browser.newContext({ proxy: {...} }) Only pages created in that context Nothing — this is the final word for that context
⚠️ Critical: If you set a proxy at browser.launch() and then call browser.newContext({ proxy: { server: 'per-context' } }) without proper credentials, the context-level config overrides the launch-level config entirely — and the proxy may stop working for that context. Always ensure context-level proxy configs are complete.
// ✅ Correct: per-context proxies for multi-account scraping
const browser = await chromium.launch();

const context1 = await browser.newContext({
  proxy: { server: 'http://gate.nstproxy.io:8080', username: 'user1', password: 'pass1' }
});
const context2 = await browser.newContext({
  proxy: { server: 'http://gate.nstproxy.io:8081', username: 'user2', password: 'pass2' }
});

// Each context uses a different proxy — they never share sessions
const [page1, page2] = await Promise.all([
  context1.newPage(), context2.newPage()
]);

Authentication Issues: Credentials Format

Playwright's proxy authentication requires credentials in the username and password fields of the proxy object. Embedding credentials in the server URL string (http://user:pass@host:port) does not work reliably across all Playwright versions and browsers.[3]

// ❌ WRONG — credentials in URL (unreliable)
proxy: {
  server: 'http://username:password@gate.nstproxy.io:8080'
}

// ✅ CORRECT — credentials in separate fields
proxy: {
  server:   'http://gate.nstproxy.io:8080',
  username: 'username',
  password: 'password',
}

// ✅ ALSO CORRECT — IP whitelisting (no credentials needed)
// Register your server IP in the Nstproxy dashboard whitelist first
proxy: {
  server: 'http://gate.nstproxy.io:8080'
}

Proxy Bypass Rules

Playwright's bypass field accepts a comma-separated list of hosts that should skip the proxy. There are two important behaviours to understand:[1]

  • Loopback bypass by default: Playwright adds <-loopback> to Chromium's --proxy-bypass-list automatically. This means localhost and 127.0.0.1 always bypass the proxy unless you explicitly disable this with the environment variable PLAYWRIGHT_DISABLE_FORCED_CHROMIUM_PROXIED_LOOPBACK=1.
  • Bypass syntax: Use dot-prefixed domains (.example.com) for subdomain matching. IP ranges require CIDR notation. Spaces after commas in the bypass string cause parsing issues in some versions.
// Bypass syntax examples
proxy: {
  server:  'http://gate.nstproxy.io:8080',
  bypass:  '*.internal.company.com,.local,192.168.0.0/24',
  // ⚠️ No spaces after commas — some versions parse incorrectly with spaces
}

// Allow localhost through proxy (disable automatic loopback bypass)
// Set environment variable before launching:
// PLAYWRIGHT_DISABLE_FORCED_CHROMIUM_PROXIED_LOOPBACK=1

Docker and CI Environment Issues

Playwright proxy issues in Docker and CI pipelines have different root causes than local development failures. The most common patterns:

🐳

Proxy Unreachable Inside Docker Container

If your proxy is running on the host machine and you reference it as localhost in the Playwright config, it will fail inside a Docker container — localhost inside the container refers to the container itself, not the host. Use host.docker.internal (Docker Desktop on Mac/Windows) or the host's bridge IP (172.17.0.1 on Linux) instead.

🔧

Corporate Proxy in CI (GitLab, GitHub Actions)

CI environments often have environment variables HTTP_PROXY, HTTPS_PROXY, and NO_PROXY set at the system level. These can conflict with or override Playwright's proxy configuration. Check with printenv | grep -i proxy in your CI pipeline. Either align Playwright's proxy config with the system proxy or unset the conflicting environment variables for the Playwright step.

🔥

Firewall Blocking Proxy Port in CI Runner

CI runner machines often have restrictive outbound firewall rules. Ports like 8080, 1080, and 3128 may be blocked. Test reachability from the CI environment itself: add a pre-step that runs curl -x http://proxy:port https://ipinfo.io/ip to confirm the proxy is accessible before Playwright tests run.

# docker-compose.yml — correct proxy reference from container
# services:
#   playwright:
#     environment:
#       - PROXY_HOST=host.docker.internal  # Mac/Windows
#       - PROXY_HOST=172.17.0.1            # Linux bridge

// playwright.config.ts — read proxy from environment
export default defineConfig({
  use: {
    proxy: {
      server:   `http://${process.env.PROXY_HOST || 'gate.nstproxy.io'}:8080`,
      username: process.env.PROXY_USER,
      password: process.env.PROXY_PASS,
    },
  },
});

IP Rotation in Playwright

A single proxy IP will eventually get flagged by heavily protected targets. The correct rotation pattern in Playwright is one proxy per browser context — each context maintains its own session, cookies, and IP identity:

const proxies = [
  { server: 'http://gate.nstproxy.io:8080', username: 'user1', password: 'pass1' },
  { server: 'http://gate.nstproxy.io:8081', username: 'user2', password: 'pass2' },
  // Add more — or use a rotating gateway that changes IP per connection
];

let proxyIndex = 0;

async function scrapeUrl(browser, url) {
  const proxy   = proxies[proxyIndex % proxies.length];
  proxyIndex++;

  const context = await browser.newContext({ proxy });
  const page    = await context.newPage();

  try {
    await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 });
    return await page.content();
  } finally {
    await context.close(); // Close context after each URL — releases the IP
  }
}

// Run 10 pages concurrently, each with a different proxy
const browser = await chromium.launch();
const results = await Promise.all(urls.map(url => scrapeUrl(browser, url)));
await browser.close();
💡 Rotating gateway tip: With Nstproxy's residential rotating gateway, each new connection from the same credentials gets a different IP automatically. Set the same server URL for all contexts and the gateway handles rotation — no need to manage a proxy list manually.

Using Residential Proxies with Playwright

Datacenter IPs are the most common cause of Playwright scraping being blocked — even when the proxy itself is configured correctly. If the proxy is working (IP check passes) but target sites still block or CAPTCHA you, the issue is IP reputation, not configuration.

Nstproxy's residential proxies provide ISP-assigned IPs that pass the IP reputation checks anti-bot systems use as their first filter. Integration with Playwright is identical to any HTTP proxy:

// Playwright + Nstproxy residential proxy — full production setup
import { chromium } from 'playwright';

const browser = await chromium.launch({
  headless: 'new',
  proxy: {
    server:   'http://gate.nstproxy.io:8080',
    username: 'YOUR_USERNAME',
    password: 'YOUR_PASSWORD',
  }
});

const context = await browser.newContext({
  userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/124.0 Safari/537.36',
  viewport:  { width: 1366, height: 768 },
  locale:    'en-US',
});

const page = await context.newPage();

// Confirm residential IP is active
await page.goto('https://ipinfo.io/json');
const info = JSON.parse(await page.textContent('body'));
console.log(`Routing through: ${info.ip} (${info.city}, ${info.country})`);

// Now scrape the actual target
await page.goto('https://target-site.com', { waitUntil: 'networkidle' });
await browser.close();

Python: Playwright Proxy Setup and Troubleshooting

# Python — correct proxy setup with verification
from playwright.sync_api import sync_playwright
import json

def scrape_with_proxy(url: str, proxy_config: dict) -> str:
    with sync_playwright() as p:
        browser = p.chromium.launch(
            headless=True,
            proxy=proxy_config
        )
        context = browser.new_context(
            user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
        )
        page = context.new_page()

        # Step 1: verify proxy is routing
        page.goto("https://ipinfo.io/json")
        info = json.loads(page.text_content("body"))
        print(f"Exit IP: {info['ip']} ({info.get('city')}, {info.get('country')})")

        # Step 2: scrape actual target
        page.goto(url, wait_until="domcontentloaded", timeout=30000)
        html = page.content()

        browser.close()
        return html

proxy_config = {
    "server":   "http://gate.nstproxy.io:8080",
    "username": "YOUR_USERNAME",
    "password": "YOUR_PASSWORD",
}

html = scrape_with_proxy("https://example.com", proxy_config)

Debugging Checklist: 10-Step Fix Process

  1. Verify proxy reachability — run curl -x http://user:pass@host:port https://ipinfo.io/ip before Playwright. If curl fails, the issue is network/proxy server, not Playwright.
  2. Check protocol prefix — ensure server starts with http:// or socks5://. Missing prefix = silent failure.
  3. Move credentials to fields — use username and password fields, not embedded in the server URL string.
  4. Run IP verification — navigate to https://ipinfo.io/ip immediately after launch to confirm the proxy IP is active.
  5. Disable headless temporarily — run headless: false to visually inspect what the browser is doing during navigation.
  6. Check for context-level proxy override — if you set proxy at launch AND at context level, context wins. Ensure context config is complete.
  7. Review bypass list — confirm the target URL is not in the bypass list and the loopback bypass is not interfering.
  8. Test in the actual runtime — in Docker or CI, run the curl test from inside the container/runner, not your local machine.
  9. Check environment variable conflicts — run printenv | grep -i proxy to find system proxy variables that may override your config.
  10. Increase timeout — if the proxy works on curl but times out in Playwright, add timeout: 60000 to page.goto().

Eliminate IP-Based Blocks with Nstproxy Residential Proxies

Playwright proxy configured correctly but still getting blocked? The issue is IP reputation — not configuration. Nstproxy's 110M+ residential IPs pass anti-bot checks that reject datacenter IPs on sight.

Try Nstproxy for Free →

FAQ

Q: Why is my Playwright proxy not working even though curl works?

The most common cause is missing protocol prefix (http://) in the server string, credentials embedded in the URL instead of separate fields, or a context-level proxy config overriding the launch-level config. Run the IP verification check (page.goto('https://ipinfo.io/ip')) immediately after launch to confirm whether the proxy is routing traffic at all, then use the debugging checklist above to narrow the cause.

Q: How do I set a proxy per browser context in Playwright?

Pass the proxy config to browser.newContext({ proxy: {...} }) instead of or in addition to browser.launch({ proxy: {...} }). The context-level proxy overrides the launch-level proxy for all pages in that context. This is the correct pattern for running multiple browser sessions with different proxy IPs simultaneously — one context per proxy.

Q: Why does Playwright bypass localhost even with a proxy configured?

Playwright automatically adds <-loopback> to Chromium's proxy bypass list, meaning localhost and 127.0.0.1 always bypass the proxy by default. To disable this behaviour, set the environment variable PLAYWRIGHT_DISABLE_FORCED_CHROMIUM_PROXIED_LOOPBACK=1 before running Playwright.

Q: How do I rotate proxies in Playwright?

Create a new browser context for each URL or session, passing a different proxy config to each context. Each context maintains its own cookies, storage, and IP identity. Close the context after each session to release the IP. For simpler rotation, use a provider like Nstproxy with a rotating residential gateway — each new TCP connection from the same credentials gets a different IP automatically, without managing a proxy list in your code.

Q: Playwright proxy works locally but not in Docker/CI — how do I fix it?

Check three things: (1) the proxy address — localhost inside a container refers to the container, not the host machine; use host.docker.internal or the bridge IP 172.17.0.1 instead; (2) run printenv | grep -i proxy in the CI pipeline to find conflicting system proxy variables; (3) test proxy reachability from inside the container with curl -x http://proxy:port https://ipinfo.io/ip as a pre-step to confirm network accessibility.

Further Reading