A WebRTC leak happens when a browser behind a proxy reveals its real public IP address through WebRTC, which sends UDP traffic that most proxies do not carry. DNS lookups and the browser's timezone can leak the same way, so a proxy alone does not hide where a session comes from.
TL;DR
- An HTTP or SOCKS proxy carries TCP. WebRTC uses UDP, which most browsers will not route through a SOCKS proxy, so ICE candidates can expose the machine's public IP.
- DNS resolution can happen locally even when traffic goes through the proxy, which leaks the resolver's network to the target's authoritative DNS.
- A timezone, locale or IP geolocation API result that disagrees with the proxy's country is a leak at the application layer.
- The fixes are policy, not cleverness: disable non-proxied UDP in the browser, resolve DNS through the proxy, and align the environment with the exit location.
- Test every proxy and browser combination before trusting it. The matrix below takes ten minutes.
- Checked October 2026: no browser changed its WebRTC proxy behavior in 2025 or 2026. The mDNS obfuscation of host candidates and the non-proxied UDP policy described below are still current. What did change is that a leaked address is now also fed to agent and proxy-pool reputation models, so the cost of one leak is higher.
Three paths out of the machine
flowchart LR
accTitle: Three ways a real IP escapes a proxy
accDescr: HTTP goes through the proxy, but the WebRTC STUN request, local DNS lookups and the browser's timezone and locale reach the target by other paths.
B[Browser] -->|HTTP over TCP| P[Proxy] --> T[Target site]
B -->|STUN over UDP<br/>not proxied| S[STUN server] -.->|srflx candidate = real IP| T
B -->|DNS lookup<br/>local resolver| R[Your ISP resolver] -.->|resolver country| T
B -->|timezone, locale| T
class B soft
class P ok
class S,R bad
class T key
style S stroke-dasharray: 5 5
style R stroke-dasharray: 5 5
linkStyle 0,1 stroke:#22c55e,stroke-width:2px
linkStyle 2,3,4,5,6 stroke:#ef4444,stroke-width:2px
The proxy is on one path. WebRTC's STUN request, local DNS resolution and the browser's own clock and locale take the others, and the page or its DNS logs can read each of them.
Three channels
WebRTC. A page can create an RTCPeerConnection and gather ICE candidates without any permission prompt. Host candidates are now obfuscated as mDNS names in Chrome and Firefox, so the local LAN address no longer leaks. Server reflexive candidates come from a STUN request, which is UDP. If the browser sends that STUN packet directly instead of through the proxy, the STUN server replies with the machine's real public address, and the candidate carries it back into the page. Chrome does not tunnel UDP through SOCKS proxies. A vendor script that gathers candidates and compares the reflexive address with the HTTP connection's address has caught the proxy.
DNS. With an HTTP CONNECT proxy, the hostname is sent to the proxy, which resolves it. With SOCKS5 and a client that resolves locally (SOCKS5 without the remote DNS option, or socks5:// versus socks5h:// in curl and Python), the machine's own resolver performs the lookup. The target does not see your IP in that case, but it can see which resolver asked, and a resolver in a different country from the HTTP connection is a contradiction. In the browser, DNS over HTTPS settings can bypass the proxy entirely depending on configuration.
Application layer. The page can read Intl.DateTimeFormat().resolvedOptions().timeZone, navigator.languages, the Date offset, and call a geolocation API. A Vietnamese timezone with a German residential exit is a mismatch no proxy can fix. Vendors also compare the IP's geolocation with the Accept-Language header and with the timezone offset the JavaScript reports.
Fixes that work
For Chrome, the enterprise policy WebRtcIPHandling (or the equivalent flag through the privacy settings) set to disable_non_proxied_udp prevents candidates from leaving through anything but the proxy; since the proxy will not carry UDP, no reflexive candidate is produced. The policy WebRtcUdpPortRange and extension APIs expose the same control. Firefox has media.peerconnection.enabled and media.peerconnection.ice.proxy_only. Some stealth browsers set these by default.
For DNS, use a proxy mode that resolves remotely: HTTP CONNECT, or socks5h. In the browser, Chrome resolves through SOCKS5 proxies remotely by default; verify rather than assume, because DNS over HTTPS can override it.
For the application layer, set the timezone and locale of the browser process to match the exit country. Playwright exposes timezoneId and locale as context options; Chrome respects the TZ environment variable on Linux and CDP's Emulation.setTimezoneOverride.
Demonstration: the leak matrix
Run each browser configuration through the same proxy and record what each test page reports. BrowserLeaks and similar sites have WebRTC, DNS and IP pages; the WebRTC one lists ICE candidates, the DNS one shows which resolver reached its authoritative server.
For a local check that needs no third party, this page gathers candidates and prints any non-mDNS address:
<pre id="out"></pre>
<script>
(async () => {
const pc = new RTCPeerConnection({ iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] });
pc.createDataChannel('x');
const found = new Set();
pc.onicecandidate = e => {
if (!e.candidate) {
document.getElementById('out').textContent =
found.size ? [...found].join('\n') : 'no public candidate (good when behind a proxy)';
return;
}
const m = e.candidate.candidate.match(/ (\d+\.\d+\.\d+\.\d+|[0-9a-f:]+) \d+ typ (\w+)/);
if (m && m[2] === 'srflx') found.add(`${m[2]} ${m[1]}`);
};
await pc.setLocalDescription(await pc.createOffer());
})();
</script>
Fill in this table for your own setup:
| Browser and proxy | srflx candidate | DNS resolver country | timezone matches exit |
|---|---|---|---|
| Chrome, SOCKS5, default settings | real IP (leak) | proxy country | depends on TZ |
Chrome, SOCKS5, disable_non_proxied_udp |
none | proxy country | depends on TZ |
| Chrome, HTTP CONNECT proxy | real IP unless UDP disabled | proxy country | depends on TZ |
Firefox, ice.proxy_only true |
none | proxy country | depends on TZ |
Python requests via socks5:// |
n/a | local resolver (leak) | n/a |
Python requests via socks5h:// |
n/a | proxy country | n/a |
| curl_cffi with proxy | n/a | depends on scheme | n/a |
The first row is the one most people are running without knowing it. The expected output of the snippet in that row is a line such as srflx 203.0.113.42, which is the machine's real address, visible to any page that asks.
Read more: how a vendor uses the leak
The vendor script gathers candidates in the background, extracts any public reflexive address, and sends it with the fingerprint payload. Server side, the address is compared with the HTTP connection's source. A mismatch is strong evidence of a proxy, and the real address's ASN is scored on its own: if it is a datacenter, the session is treated as a datacenter session regardless of the residential exit. Many vendors also compare the reflexive address across sessions to link proxy rotations back to one origin machine, which turns a rotating pool into a single identity.
Read more: the leak that is not a leak, shape mismatch
A 2026 benchmark run from a residential IP made a point worth repeating here: a Linux host behind a residential proxy still exposes Linux-shaped signals at the TLS, HTTP/2 and JavaScript layers. No address leaked; the operating system did. The proxy changes the geography of the connection and nothing else.
If the exit is a Windows home connection and the TLS stack, fonts and navigator.platform say Linux server, the vendor does not need WebRTC to find the contradiction. Run the leak matrix, then run the fingerprint anatomy snippet, and make both agree with the exit.
Read more: IPv6 and the dual-stack leak
A machine with IPv6 connectivity may reach a dual-stack target over IPv6 directly while the proxy configuration only covers IPv4, or the reverse. Check that the proxy carries both families or disable the one it does not carry at the OS level. The same applies to DNS: an AAAA lookup can go out a different path than an A lookup. The matrix above should be run once with IPv6 enabled and once with it disabled on the host.
Frequently asked questions
What is a WebRTC leak?
WebRTC gathers network candidates with a STUN request over UDP. Chrome does not send UDP through SOCKS proxies, so the STUN server sees the machine's real address and returns it to the page. Any script can read that address without a permission prompt.
How do I stop WebRTC leaks in Chrome?
Set Chrome's WebRtcIPHandling policy to disable_non_proxied_udp, or use an extension that applies the same setting. In Firefox, set media.peerconnection.ice.proxy_only to true. Then run a leak test again to confirm that no public candidate appears.
What is a DNS leak with a proxy?
A DNS leak happens when hostnames are resolved by your local resolver instead of through the proxy, for example with socks5 instead of socks5h in curl or Python. The target's DNS logs then see a resolver in a different country from your connection.
Does a residential proxy hide my operating system?
No. A proxy changes only the network path. The TLS handshake, HTTP/2 settings, fonts and navigator properties still describe the real machine, so a Linux server behind a residential proxy still looks like a Linux server.