By Rajesh Kannan, Co-Founder & CTO, Gramosoft · Last updated: · Lab results from our own local test on November 4, 2026 (Chromium 141, Python requests 2.34, httpx 0.28, curl 8.5)

Your proxy works. An IP checker shows the proxy's address, the country is right, and the pages load. Yet the site still ties your sessions back to one machine. Often the cause isn't the proxy at all but two paths that never touch it: WebRTC and DNS. This guide shows how each one leaks, what we measured in a local lab, how to close both, and where GcrawlAI fits.

TL;DR

  • WebRTC leak: in a browser, a page can start a WebRTC connection without a prompt. Its STUN request goes out over UDP, skips HTTP and SOCKS proxies, and returns your own public address to the page.
  • DNS leak: with socks5:// in Python requests or curl --socks5, your machine looks up the hostname before the proxy sees it. socks5h:// moves the lookup to the proxy.
  • What we measured: through both an HTTP and a SOCKS5 proxy, Chromium sent its STUN request directly. The --force-webrtc-ip-handling-policy flag changed nothing in Chromium 141; the same setting as a browser preference stopped it.
  • Plain HTTP clients have no WebRTC: requests, httpx and curl only need the DNS fix.
  • Or skip the problem: with GcrawlAI the browser, DNS and network traffic all run on its side, so nothing leaves from your machine.

Why does a proxy still expose your scraper? A proxy only carries the traffic that is sent to it. In a browser, WebRTC's STUN requests travel over UDP outside HTTP and SOCKS proxies, so a page can read your own public address. In scripts, a socks5:// proxy URL makes your machine resolve hostnames itself. Set WebRTC IP handling to disable_non_proxied_udp, use socks5h://, and test the exact setup you run, or fetch pages through GcrawlAI so nothing runs on your machine.

Which leak applies to you

Your setup WebRTC leak? DNS leak?
Python requests, httpx, curl, Scrapy No, there's no browser Only with a socks5:// URL that resolves locally
Playwright, Puppeteer or Selenium with a proxy Yes, by default Browsers resolve through HTTP and SOCKS5 proxies, but test it
Anti-detect or cloud browser profiles Depends on the profile's WebRTC setting Depends on the profile and proxy type
GcrawlAI Cloud API No, the browser runs on GcrawlAI No, names resolve at the exit

Nothing on your machine to leak: GcrawlAI

Every leak in this guide starts on the machine that runs the browser or the script. Closing them means browser preferences, proxy URL schemes and tests to repeat whenever something changes. GcrawlAI moves the whole fetch off your machine: the browser, its DNS lookups and its network traffic run on GcrawlAI's side, through the exit you choose.

GcrawlAI homepage with the logo and menu (Products, Playground, Extractors, Docs, Pricing, Blog, Use Cases, Custom Request): Transform any website into structured LLM-ready data, the open-source web scraping API for AI agents, RAG pipelines and data teams, with JavaScript rendering, anti-bot stealth and proxies included in every request and no credit system; General, Extractors and Offline Bundle modes, tabs for Scrape, Map, Crawl, Batch, Screenshot, Search and Smart Answer, and the Batch tab open with a URL list, Markdown format, country picker and a Start batch scraping button
The GcrawlAI homepage doubles as a playground: here the Batch tab queues a list of URLs as one job, with rendering, anti-bot stealth and proxies included in every request (desktop screenshot taken October 31, 2026; the phone view, from October 28, shows on small screens).

The homepage says it in one line: JavaScript rendering, anti-bot stealth and proxies are included in every request, with no credit system. Because the real browser runs on GcrawlAI's infrastructure, your own IP, resolver and WebRTC stack are never part of the request, and the page arrives as HTML or Markdown.

Boarding pass for one GcrawlAI request, from you to the site: browser, a real Chrome on GcrawlAI; DNS resolved at the exit; WebRTC and UDP not on your machine; exit IP residential in the country you pick; retries inside the same request; you get HTML and Markdown; a page with your fields counts as 1 request and a failed one costs $0
One request: the browser, DNS and exit all run on GcrawlAI's side.

Here's the client. It asks GcrawlAI for a public page through a German exit and keeps it only if the products you need are there:

# pip install gcrawl-sdk parsel
"""Let GcrawlAI fetch a public page you're allowed to collect. The browser, its DNS lookups and its
network traffic all run on GcrawlAI's side, through the exit IP for the country you pick, so nothing
leaves from your own machine. Keep the page only if your fields are in it."""
import os

from gcrawl_sdk import GcrawlClient, ScrapeOptions
from parsel import Selector

gc = GcrawlClient(api_key=os.getenv("GCRAWL_API_KEY", "gc-YOUR_API_KEY"))

page = gc.scrape(url="https://example.com/category/laptops",
                 options=ScrapeOptions(html_enabled=True, markdown_enabled=True, proxy_geo="DE"), wait=True)
items = Selector(page.html or "").css("li.product")
print(f"{len(items)} products" if len(items) >= 5 else "page came back without products: check before storing")
  • Rendering and access: a real browser through residential IPs in the country you choose, with retries and stealth escalation inside the request.
  • Output you can check: HTML, Markdown or JSON, plus extractors that return structured fields for common page types.
  • Scrape, crawl, screenshots and Search API: scrape single pages, crawl whole sites, capture screenshots, or use the Search API.
  • For developers and AI agents: a Python SDK, an MCP server for Claude, Cursor and other agents, the Playground to test a page before you write code, and the docs.

The MIT-licensed core also runs on your own servers: clone it from GitHub and the setup script in the code download starts the API with Redis and a Celery worker. Self-hosted, the leak checks in this guide apply to your servers.

Pricing is a free plan of 50 requests a month with no card, paid plans from $0.67 to $1.45 per 1,000 requests, and a self-hosted core that costs nothing. Failed requests are never charged. The full plan table is in the cost section.

Where it falls short: moving the fetch off your machine removes these leaks, but it doesn't make every page reachable, with GcrawlAI or anything else. Test your own targets on the free plan. It won't solve CAPTCHAs meant for humans or log in for you, and it doesn't make collecting a site's data acceptable when that site's terms say no.

"When a team tells us their residential proxies are burned, we run one WebRTC check first. More often than not the proxy is fine, and the browser is announcing the office IP on every page."

Rajesh Kannan, Co-Founder & CTO at Gramosoft

Three paths out of one machine

A proxy is a route, not a disguise. It carries the traffic your software sends to it and nothing else. A browser or script behind a proxy can still send traffic on other paths: WebRTC connection checks over UDP and hostname lookups through your own resolver. Each path can show a site a different network.

Airport X-ray style scan of what leaves a browser behind a proxy: lane 1, page requests over TCP, browser to proxy to site, nothing to fix; lane 2, WebRTC STUN over UDP, browser straight to the STUN server, fix with disable_non_proxied_udp; lane 3, hostname lookups through your own resolver with socks5:// in requests or curl, fix with socks5h:// or an HTTP proxy
Three paths out of one machine; only page requests use the proxy by default.

ScrapePass describes the same three channels as WebRTC, DNS and the application layer, meaning the browser's timezone and locale. Of the three, a leaked IP is the strongest signal, because it's an address rather than a statistical guess.

  • Page requests: HTTP and HTTPS go through the proxy. This is the only path a basic IP checker tests.
  • WebRTC: only in browsers, including headless Playwright, Puppeteer and Selenium sessions.
  • DNS: in any client, decided by the proxy type and the URL scheme you use.

How a WebRTC leak exposes your IP

WebRTC powers video calls in the browser. Before a call, the browser gathers ICE candidates, the network addresses another peer could reach it on. To learn its public address, it asks a STUN server over UDP. Any page can start this with a few lines of JavaScript, and no permission prompt appears.

The IETF's RFC 8828 on WebRTC IP handling says plainly that when a proxy is set but direct internet access is allowed, the STUN checks bypass the proxy and reveal the client's public address. Chromium's proxy documentation adds that its SOCKS5 support is TCP-only and can't relay UDP.

Candidate type What it holds A leak?
host An address on your own network card; modern Chrome and Firefox hide it behind a random .local name Usually no, when it shows as .local
srflx (server reflexive) The public address the STUN server saw Yes, if it differs from the proxy's exit IP
relay An address on a TURN relay No, it's the relay's address

A page script can send the srflx address to its server and compare it with the IP your HTTP requests came from. ScrapePass notes that vendors also compare that address across sessions, which links a rotating proxy pool back to one origin machine.

How a DNS leak happens

Before connecting, someone has to turn the site's hostname into an IP address. With an HTTP proxy, the client sends the hostname and the proxy resolves it. With SOCKS5, it depends on the client: some send the hostname, and some look it up on your own machine first and send only the IP.

A local lookup goes to your own resolver, usually your ISP's or your office's. The site's name servers then see a resolver in one place while the connection comes from a proxy somewhere else. Shifter lists this socks5 versus socks5h mix-up among the common mistakes behind detected residential proxies.

Client and proxy setting Who resolves the hostname
Python requests with socks5:// Your machine (leaks)
Python requests with socks5h:// The proxy
httpx with socks5:// The proxy (in our test it sent the hostname)
curl --socks5 or socks5:// Your machine (leaks)
curl --socks5-hostname or socks5h:// The proxy
Any client with an HTTP proxy The proxy

Browsers add one more case: a browser using DNS over HTTPS can send lookups to its own provider, depending on its settings. ScrapePass advises verifying rather than assuming, because DNS over HTTPS can override how a browser resolves through a proxy.

What we measured in a local leak lab

We built a small lab on one machine: a test site, our own SOCKS5 and HTTP proxies that log what they receive, and a STUN server that logs who contacts it. No outside site was involved. Then we sent the same requests through each proxy with common clients and Chromium setups. The lab scripts are in the code download.

Litmus test strips from our November 4, 2026 local lab. DNS: requests socks5:// leaked with 1 local lookup and an IP sent to the proxy; requests socks5h://, httpx socks5://, curl --socks5-hostname and an HTTP proxy kept the lookup on the proxy; curl --socks5 leaked. WebRTC in Chromium 141 through HTTP and SOCKS5 proxies: defaults leaked, both command-line flags changed nothing, the default_public_interface_only preference still exposed srflx, and only the disable_non_proxied_udp preference stopped STUN and all candidates
Our lab: which clients and Chromium settings kept DNS and WebRTC on the proxy.

On the DNS side, Python requests with socks5:// made one lookup on our machine and the proxy received an IP address. Switching to socks5h:// made zero local lookups and the proxy received the hostname. httpx sent the hostname even with socks5://, and curl followed the same split as requests.

On the WebRTC side, Chromium sent its STUN request straight to our STUN server through both the HTTP and the SOCKS5 proxy, and the page saw an srflx candidate with the machine's own address. Host candidates showed only as .local names.

  • The command-line flag did nothing: --force-webrtc-ip-handling-policy=disable_non_proxied_udp left the STUN request and the srflx candidate unchanged in Chromium 141, headless and headed.
  • The preference worked: the same value set as the browser's webrtc.ip_handling_policy preference, which Chrome's policy also controls, produced no candidates and no STUN traffic.
  • default_public_interface_only isn't a fix: it hid the host candidate but still sent STUN directly and exposed the srflx address.

"The flag result surprised us. Plenty of guides recommend it, and it changed nothing in our build. That's the reason to test the browser you actually run instead of trusting a setting you read about."

Rajesh Kannan, Co-Founder & CTO at Gramosoft

How to check your own browser setup

Test the exact browser, proxy and profile you run in production, not your personal browser. The script below opens Chromium through your proxy, reads the exit IP the web sees, gathers WebRTC candidates, and flags any public address that differs. It runs twice: with defaults, then with the fix applied.

# pip install playwright && playwright install chromium
"""Does WebRTC show sites an address other than your proxy's? Opens Chromium through PROXY, reads the
exit IP the web sees, gathers WebRTC candidates, and reports any public (srflx) address that differs.
Runs twice: Chromium defaults, then with the WebRTC IP handling preference set to
disable_non_proxied_udp (the same setting as Chrome's WebRtcIPHandling policy)."""
import json, os, tempfile
from pathlib import Path
from playwright.sync_api import sync_playwright

PROXY = os.getenv("PROXY", "http://127.0.0.1:8899")              # your proxy: http://user:pass@host:port
EXIT_IP_URL = os.getenv("EXIT_IP_URL", "https://api.ipify.org")
STUN = os.getenv("STUN", "stun:stun.l.google.com:19302")
GATHER = """async (stun) => { const pc = new RTCPeerConnection({iceServers: [{urls: stun}]}); pc.createDataChannel("x");
  const out = []; pc.onicecandidate = e => e.candidate && out.push([e.candidate.type, e.candidate.address]);
  await pc.setLocalDescription(await pc.createOffer()); await new Promise(r => setTimeout(r, 3000)); return out; }"""

for label, policy in [("Chromium defaults", None), ("disable_non_proxied_udp", "disable_non_proxied_udp")]:
    profile = Path(tempfile.mkdtemp()); (profile / "Default").mkdir()
    if policy:
        (profile / "Default" / "Preferences").write_text(json.dumps({"webrtc": {"ip_handling_policy": policy}}))
    with sync_playwright() as p:
        ctx = p.chromium.launch_persistent_context(str(profile), proxy={"server": PROXY})
        page = ctx.new_page()
        exit_ip = page.goto(EXIT_IP_URL).text().strip()
        cands = page.evaluate(GATHER, STUN); ctx.close()
    leaks = sorted({addr for kind, addr in cands if kind == "srflx" and addr != exit_ip})
    print(f"{label:<24} exit IP {exit_ip:<15} candidates {len(cands)}  ->  "
          + (f"LEAK: WebRTC shows {', '.join(leaks)}" if leaks else "no other public address"))

In our lab it reported a leak with defaults and none with the preference set, through both proxies. Against the internet, set PROXY to your proxy and it uses a public IP echo and Google's public STUN server.

  • Browser test pages: BrowserLeaks lists ICE candidates, and Scrapfly's testing-tools guide covers WebRTC and DNS leak tests alongside other layers.
  • A timeout isn't a pass: Scrapfly notes a WebRTC test that times out may mean UDP is blocked, not that the setup is clean.
  • For DNS in scripts: check the proxy URL scheme. socks5h:// or an HTTP proxy keeps the lookup on the proxy.

How to close both leaks

Both fixes are settings, not tricks. The goal is that every path your software uses goes through the route you chose, so the site sees one consistent network. Make one change at a time and re-run the same test after each one, so you know which change made the difference.

  • Chrome and Chromium: set WebRTC IP handling to disable_non_proxied_udp, through the WebRtcIPHandling policy on managed browsers or the profile preference in automation. Google describes it as allowing UDP only through a proxy that supports it.
  • Firefox: media.peerconnection.ice.proxy_only_if_behind_proxy set to true, which Lokesh Kapoor's proxy testing guide recommends over proxy_only, which breaks WebRTC when no proxy is set.
  • Scripts: use socks5h:// instead of socks5:// in requests and curl, or an HTTP proxy.
  • Timezone, locale and IPv6: match the browser's timezone and language to the exit country, and make sure an IPv4-only proxy isn't sitting next to a direct IPv6 route.

Turning WebRTC off entirely also stops the leak, but AntiBrow points out that a browser with no WebRTC at all is unusual in itself. The policy above keeps WebRTC working and keeps its traffic on the proxy route, falling back to TCP when the proxy can't carry UDP.

What leak-free pages cost

Pages that need a real browser also need rendering and usually premium IPs, so compare providers on their protected-page price, not their headline rate. For 100,000 successful protected pages a month, credit-based APIs cost $249 to $399 at list price and GcrawlAI costs $101. For plain pages, they're cheaper.

Option, 100,000 successful pages a month Plain HTML pages Protected pages (JS rendering + premium proxies) Are failed requests billed?
GcrawlAI (every request rendered, residential IPs included) $101 (Growth $79 + 20,000 overage at $1.10 per 1,000) $101 (same request, no multiplier) No: failed requests are never charged
ScraperAPI $49 (Hobby, 100,000 credits) $299 (Business; 25 credits per page = 2.5M credits) Charges only 200 and 404 responses
ZenRows $39 (Build B3, 120,000 credits) $399 (Growth G3; 25 credits per page = 2.5M credits) Failed requests never charged; 404 and 410 count as successful
ScrapingBee $49 (Freelance, 250,000 credits; JS off) $249 (Business; 25 credits per page = 2.5M credits) Charges only 200, 404 and 410

List-price arithmetic, not a benchmark. Credit costs and billing rules come from ScraperAPI's credit docs, ZenRows' pricing docs and ScrapingBee's FAQ, checked November 1, 2026. The honest read: for plain HTML, ZenRows, ScraperAPI and ScrapingBee cost less than GcrawlAI. Whatever you use, count cost per page with your fields in it, not per response.

GcrawlAI plans (GcrawlAI pricing, checked October 28, 2026):

Plan Price Requests/month Concurrent Per 1,000 Overage per 1,000
Free $0 forever, no card 50 2 – –
Starter $29/month 20,000 10 $1.45 $1.60
Growth (most popular) $79/month 80,000 35 $0.99 $1.10
Pro $199/month 250,000 50 $0.80 $0.90
Scale $499/month 750,000 100 $0.67 $0.75
Enterprise Volume pricing 2M+ Custom – –
Self-hosted $0 (MIT) Your servers Your servers Infrastructure only –

Every plan, Free included, has scrape, crawl, the Search API, JavaScript rendering, residential IP rotation and stealth escalation in each request, and each feature counts as one request. Failed requests are never charged. Annual billing charges 10 months for 12, unused requests on paid plans roll over once, and Indian customers are billed in INR with a GST invoice.

Which route fits your project

The right route depends on what runs on your machine. A Python script behind a proxy only needs the right URL scheme. A browser fleet needs WebRTC settings, DNS checks and tests after every change. A managed API removes your machine from the request. The table shows where GcrawlAI fits in each case.

Your situation Route Where GcrawlAI fits
Python requests, httpx or Scrapy through a proxy Use socks5h:// or an HTTP proxy Move to GcrawlAI when pages need a browser
A few Playwright or Puppeteer sessions Set the WebRTC policy, test each profile Use GcrawlAI for the pages you'd rather not run yourself
A browser fleet on cloud servers Managed browser API GcrawlAI runs the browser, DNS and exit IP, one request per page
You need full control of the stack Self-host The MIT GcrawlAI core on your servers, with these checks
AI agents need the page A tool the agent can call GcrawlAI MCP server with scrape, crawl and Search API
The site offers an API or feed Use it Not needed; use GcrawlAI for your other sources

When your own Python is enough

If your scraper is a Python script with requests, httpx or Scrapy, it has no WebRTC at all, and the only leak is DNS. Changing socks5:// to socks5h://, or using an HTTP proxy, closes it. That fix is free, takes seconds, and covers most server-side scrapers.

When pages need a real browser and you'd rather not maintain browser profiles, WebRTC settings and leak tests, move the fetching to GcrawlAI and keep your field checks. Its free Cloud plan's 50 requests a month are enough to test your real targets, the Playground shows the result without code, and the MIT core self-hosts for free. For the build-or-buy trade-off, read web scraping with Python vs a scraping API.

Using proxies responsibly

Closing leaks makes your setup consistent; it doesn't change what you're allowed to collect. Read the site's terms and robots.txt, prefer an official API or data licence, stay on public pages, keep request rates low, and don't collect personal data you have no basis to hold. A proxy is not permission.

Leave logins, checkouts and account pages alone, and don't use rotating identities to get around a block a site has put on you. For the rules a site publishes, read how to check if a website allows scraping, and for the legal background, what web scraping is and where it's legal. Get advice for your own case.

Frequently asked questions

What is a WebRTC leak?

A browser behind a proxy exposes a different public address through WebRTC. To gather connection candidates, the browser sends a STUN request over UDP, which skips HTTP and SOCKS proxies, and the reply holds your own public IP. Any page can read it without a permission prompt.

What is a DNS leak with a proxy?

Your own machine resolves the site's hostname instead of the proxy, so your resolver, usually your ISP's or your office's, handles the lookup. In our lab, Python requests with socks5:// did this; socks5h:// sent the hostname to the proxy instead.

What is the difference between socks5 and socks5h?

With socks5://, requests and curl look up the hostname on your machine and send the proxy an IP address. With socks5h://, they send the hostname and the proxy resolves it. Use socks5h:// to keep DNS on the proxy.

Do Python scrapers need to worry about WebRTC?

No. requests, httpx, curl and Scrapy don't run WebRTC, so they can't leak through it. WebRTC matters in browsers, including headless Playwright, Puppeteer and Selenium. For scripts, check the DNS path instead.

How do I stop WebRTC leaks in Playwright?

Set Chromium's WebRTC IP handling to disable_non_proxied_udp. In our test with Chromium 141, the command-line flag had no effect, while the same value set as the profile preference stopped the leak. Run a leak check with your own browser version to confirm.

Does a residential proxy hide WebRTC and DNS leaks?

No. A proxy only carries the traffic sent to it. The WebRTC STUN request and a local DNS lookup leave on their own paths, whatever kind of IP the proxy has. You fix them in the browser and in the proxy URL.

Is GcrawlAI open source, and can I use it commercially?

Yes. The GcrawlAI core is MIT licensed on GitHub, so you can self-host it and use it commercially. The managed Cloud API adds residential IPs and stealth escalation, with a Python SDK and MCP for AI agents, a free plan of 50 requests a month and paid plans from $0.67 to $1.45 per 1,000 requests.

RK

Rajesh Kannan M

Rajesh Kannan M is Co-Founder & CTO of Gramosoft in Chennai, where he leads the team building GcrawlAI, an open-source web scraping, crawling and Search API platform for developers and AI agents. LinkedIn

Your proxy is only one of three paths

Start with 50 free requests a month, no card. Send the public pages you're allowed to collect through GcrawlAI, let the browser and DNS run on its side, and pay only for requests that succeed.

Try the Playground free See pricing