LoginToolsPricing
BirdProxies
BirdProxies
Entrar
Back to Blog
Guides

Playwright behind a proxy: the leaks the IP checker never shows

BirdProxiesSeptember 12, 202610 min read

An r/scrapy poster spent two days on a scrapy plus Playwright setup that "kept getting served different content on a site that was fine with a manual browser." The exit IP was right. "Everything looked clean on the usual IP checkers." Then they tested the browser itself and found three leaks: "WebRTC was exposing my real local address through a STUN request that bypassed the proxy entirely, DNS queries were resolving through my ISP instead of the proxy's resolver, and navigator.webdriver was set to true." On r/proxies, someone running Playwright with the stealth plugin was "trying to figure out if it's the proxy or my playwright config." Same question, other side.

This post is for both of them. A proxy sets the exit IP for the HTTP(S) traffic the browser sends through it. WebRTC, DNS, the automation protocol and the fingerprint are separate channels, and a clean residential IP closes none of them. Fix the channels first. Then the IP matters.

The three leaks, and the Playwright-side fix for each

Playwright's proxy option only covers the HTTP(S) connections the browser makes; WebRTC, DNS and the webdriver flag sit outside it, and each needs its own switch.

navigator.webdriver

Playwright launches Chromium with --enable-automation, and that is what sets navigator.webdriver to true. Drop the default argument, or tell Blink not to expose it. Either works:

const browser = await chromium.launch({
  ignoreDefaultArgs: ['--enable-automation'],
  args: ['--disable-blink-features=AutomationControlled'],
  proxy: { server: 'http://host:port', username: 'user', password: 'pass' },
})

WebRTC

WebRTC does not go through the proxy at all. It sends UDP packets to a STUN server to learn its own public address, and any page with a few lines of JavaScript can read the answer. page.route() cannot help; it only sees HTTP, and STUN is not HTTP. The fix is a Chromium switch that forbids WebRTC traffic that would bypass the proxy:

args: ['--force-webrtc-ip-handling-policy=disable_non_proxied_udp']

With that policy there is no STUN request from your real interface, so there is no candidate to leak. Calls will break; for scraping, good trade. In Firefox it is a pref, firefoxUserPrefs: { 'media.peerconnection.enabled': false } in the launch options.

DNS

This is the one the poster left open: "the resolver follows the system config regardless of the proxy setting in playwright." Whether that is true depends on the proxy type.

With an HTTP proxy, Chromium does not resolve the hostname at all. For an https:// page it opens a tunnel with CONNECT example.com:443, and the proxy resolves the name on its side, in its own country. For plain http:// the browser hands the full URL to the proxy. Nothing touches your ISP's resolver. SOCKS4 forces local resolution, because SOCKS4 only carries an IP. With SOCKS5, Chromium sends the hostname to the proxy, but much of the other software in a pipeline does not: Python's requests and curl resolve locally for socks5:// and only hand the name over for socks5h://. In a scrapy plus Playwright project, scrapy's own downloader is a separate client with its own proxy setting, and that is where the leak often lives.

So: give Playwright an HTTP proxy URL and the browser's DNS goes with it. Then check what is not the browser: any bypass hosts in the proxy option (those go direct and resolve locally), and every non-browser HTTP client in the pipeline. Verify by loading a DNS leak test page through the proxied browser and comparing the resolver's network with the exit IP from our IP lookup. If the resolver sits on your home ISP, something outside the browser is resolving.

The tell no proxy can cover: the protocol itself

An r/Playwright poster bisected a Chrome that had nothing wrong with it and still got flagged: "Nothing about the browser gave it away. The protocol did."

They ran Playwright MCP in --extension mode against their own daily Chrome. "Real profile, real logins, real fingerprint." A detector still flagged it on three signals out of 22, all CDP or timing. The culprit was one CDP command, Runtime.enable, which Playwright sends to pages and workers so it can evaluate scripts, and which has side effects a page can observe.

There is no IP for this; the detector was not looking at one. If a site runs that class of check, the fix lives in how you drive the browser. We sell addresses; we would rather say so than sell you a fifth one for a protocol problem.

Is it the proxy or the config? Run the split

The r/proxies poster with the stealth plugin was blocked "even on simple page loads" and could not tell whether the proxies they "grabbed" were bad or the Playwright side was. Two runs answer it.

First split: same script, two exits. Run it from your home connection with no proxy, then through the proxy. If home passes and the proxy fails, the IP is the problem (reputation, range, or country). If both fail, it is the browser.

Second split: same proxy, two clients. Fetch the page with curl through the proxy, then with Playwright through it. If curl gets the page and Playwright gets a block, the IP is fine and the browser is talking. If curl is blocked too, the IP is burned.

With "a few proxies I grabbed", the second split usually settles it; free lists are on every reputation feed. The longer checklist is in how to tell if it is the IP.

The 200 with missing fields is still a block

An r/AI_Agents poster had the two-sided version: "without a proxy, the request returns a 403 Forbidden error", and with a proxy "the response status is 200 OK, but the input fields on the page (like search boxes and form elements) just don't show up at all." Chromium and Firefox, same result.

A 200 that renders a stripped page is a soft block. Sites behind a bot-management vendor often serve the shell and withhold the app when the score is bad, or they serve a country variant for the proxy's exit. Three things to do. Screenshot both exits and diff the HTML, not the status code. Listen to every response: page.on('response', r => console.log(r.status(), r.url())) shows the script or XHR that came back 403 and left the form unhydrated. And read the source for a challenge script; if one is there, you are being scored, and the score is mostly about the browser. Our post on what a proxy fixes with captchas and JS challenges covers the scoring side.

What "static residential for automation" should mean, and what to compare

Static residential is the marketing name for an ISP proxy: a dedicated IP on a consumer-ISP range, hosted in a datacenter, fixed for the whole rental. The r/proxies poster setting up Playwright tests found "some proxies I tested have random disconnects which makes testing frustrating" and asked what to compare. Five things.

Dedicated or shared: a shared static IP carries someone else's traffic, and their bursts become your disconnects. Whether the IP is actually fixed: some products sold as static are long sticky sessions on a rotating pool, so ask if the address survives a week. Idle timeout: random drops on a long run are often the proxy closing quiet tunnels. Concurrency per IP: one browser page opens dozens of connections. Protocol: HTTP CONNECT with a username and password, because Chromium has no SOCKS5 authentication.

The naming trap is in one static residential IP, what to buy and why it gets flagged; our ISP proxies are the dedicated kind, in US, GB, DE, FR, JP and HK.

Two asides. Someone on r/automation needed a site "now only reachable from within the UAE" while developing from India. A proxy in the UAE does exactly what it says, the site sees a UAE address and loads; we do not stock UAE static IPs, so check a country list before you build on one. And an r/datascience poster running Playwright with static residential IPs asked "how do you typically handle proxy rotation at scale?" For logged-in work, you do not. One profile, one context, one IP, for the life of the session. Per-request rotation is for stateless fetches; the rotating versus sticky guide draws that line.

A reference layout: contexts, sessions, workers

The cleanest design in the pile came from r/webdev, from a poster asking whether their "architecture and failure-handling approach look sound". It does. Three workers, each with a page range and "one proxy per worker (sticky for the run)", starts staggered by 20 to 90 seconds "to reduce bot-like bursts", and "subnet diversity so no two workers share the same /24". One headless Chromium page per worker, images, fonts and styles blocked.

In Playwright terms: one browser.newContext({ proxy }) per worker, the same IP for the whole run, cookies living in that context and nowhere else. The blocking matters twice on per-GB residential, speed and bill:

await context.route('**/*', route => {
  const t = route.request().resourceType()
  return ['image', 'font', 'stylesheet', 'media'].includes(t) ? route.abort() : route.continue()
})

The r/AI_Agents poster who was "burning IPs in maybe 400 pages" on a Playwright fleet went further and replayed the JSON endpoint the page hydrated from, with "the right headers, the right cookie, the right accept-language". That works and cuts a per-GB bill to a fraction. Two caveats. The cookies were earned by a browser on a particular IP, and many sites bind them to it, so the replay has to use the same sticky session. And when the token expires you need the browser again, so keep one context alive per IP.

FAQ

Does Playwright leak my real IP through WebRTC?

Yes, by default. WebRTC sends UDP STUN requests outside the proxy, and a page can read the resulting candidate to learn your real address. Launch Chromium with --force-webrtc-ip-handling-policy=disable_non_proxied_udp, or in Firefox set media.peerconnection.enabled to false through firefoxUserPrefs.

Why does my Playwright proxy work but the site still blocks me?

Because the proxy only changes the exit IP, and the site is reading something else: navigator.webdriver, a WebRTC or DNS leak, the CDP protocol itself, or a fingerprint that does not match the IP's country. Run the split test: home IP against proxy, then curl against Playwright through the same proxy.

How do I stop DNS leaks in Playwright with a proxy?

Use an HTTP proxy URL in Playwright's proxy.server. The browser then sends the hostname to the proxy inside CONNECT and never resolves it locally. Leaks come from SOCKS4, from socks5:// in non-browser clients like requests or curl (use socks5h://), from bypass hosts, and from WebRTC.

Should I use SOCKS5 or HTTP proxies in Playwright?

HTTP. Playwright passes a username and password to HTTP proxies at launch or per context, Chromium has no SOCKS5 authentication, and HTTP CONNECT leaves DNS resolution to the proxy. SOCKS5 buys a browser nothing.

Does navigator.webdriver give Playwright away?

It is one signal, and it is on by default. Pass ignoreDefaultArgs: ['--enable-automation'] or args: ['--disable-blink-features=AutomationControlled'] to the launch. That closes the flag, not the protocol: a detector watching for Runtime.enable side effects still sees automation.

Why does the page return 200 through a proxy but the form fields are missing?

A 200 with a stripped page is a soft block or a geo variant, not a success. Log every response status with page.on('response') to find the script or XHR that failed, screenshot both exits, and diff the HTML. A challenge script in the source means the site is scoring your browser.

Get started with BirdProxies

Put this into practice with fast, reliable proxies built for social media, scraping, and automation.

Residential ProxiesReal home IPs across 195+ countries for maximum trust.ISP ProxiesDatacenter speed with residential legitimacy.

On this page

BirdProxies
BirdProxies

Fast, secure, reliable proxies. ISP, Residential, and Mobile, ready when you are.

Products

  • ISP Proxies
  • Residential Proxies
  • Sneaker Proxies
  • Ticket Proxies
  • Crypto Proxies
  • Social Media Proxies
  • Betting Proxies

Company

  • Pricing
  • Partners
  • Imprint
  • Terms

Resources

  • Blog
  • Docs
  • Glossary
  • Integration Guides
  • Compare Providers
  • FAQ
  • Changelog
  • Brand Assets

Connect

  • Dashboard
  • Sign Up
  • Contact

© 2026 BirdProxies. All rights reserved.

PrivacyCookiesRefunds