How it works
WebRTC Leak Tests: What They Actually Measure
A leak test page prints a list of IP addresses and calls the result pass or fail. Most confusion about WebRTC starts there: the list holds several addresses found by different mechanisms, and the row people panic about is the harmless one. WebRTC has no single “your IP” value to leak — it has ICE candidates.
Three candidate types, three different addresses
Before two browsers exchange media directly, each side gathers candidates: transport addresses at which a peer might be reachable. A test page reads them out of the SDP your browser generates. Each type is discovered differently and means something different.
- host — an address on one of your own device’s interfaces: LAN, link-local, virtual adapter. Found by asking the operating system which interfaces exist; no network request involved.
- srflx (server-reflexive) — the address a STUN server saw the request arrive from: the outside-facing address of the path the packet actually took, after NAT. This is where a remote party sees you on the internet.
- relay — an address on a TURN server that forwards media for you. It identifies the TURN operator’s server, not you.
Only one of them answers the question a leak test claims to answer: the server-reflexive row. Host rows describe the inside of your own machine.
What mDNS changed, and what it did not
Chrome began replacing host candidates with randomly generated .local hostnames in 2019, turning it on for everyone in version 76 that August; Firefox followed in 72, and Safari does the same. Instead of 192.168.1.42 the candidate carries a random UUID ending in .local: registered over multicast DNS, resolvable only on the same link, regenerated so it cannot serve as a cross-site identifier.
That fixed one problem: pages harvesting your internal network layout for LAN scanning and fingerprinting. It fixed nothing else. Obfuscation covers host candidates the browser judges unsafe to expose — private and link-local addresses. Server-reflexive and relay candidates stay plain IP addresses, because their whole purpose is to be somewhere a remote peer can send packets.
The reflexive candidate follows your routing table
RFC 8828, which defines how browsers handle IP addresses in WebRTC, sets the no-consent default to a mode that follows the kernel routing table, so media packets take the same route as the page’s own HTTP traffic. That sentence explains most confusing test results.
A full-device tunnel — a system VPN client, a WireGuard or OpenVPN interface, any TUN/utun adapter holding the default route — carries the browser’s STUN packets like everything else. The STUN server sees them arrive from the tunnel’s exit address, so the server-reflexive candidate is that exit address; WebRTC never sees your ISP-assigned address on that path.
A browser extension calling itself a VPN is not a tunnel. It points the browser at a proxy, and HTTP proxies carry TCP; STUN and WebRTC media are UDP, which such a proxy will not forward. Unless the extension also switches the browser’s WebRTC IP-handling policy to force proxying, those packets leave over the physical interface and the reflexive candidate is your ISP address — while pages still load through the proxy. Same browser, same page, opposite verdict, and the difference is the installation method.
Reading a result correctly
- 1Establish the expected answer first, from something you control: your VPN client’s status display, or the address your provider states for that node. Without it, a leak test means nothing.
- 2Read the candidate type on each row, not just the numbers. Anything ending in
.local, or in 10.x, 172.16–31.x, 192.168.x, fe80::, is a host candidate. Set it aside. - 3Compare the remaining public addresses against your expected exit address.
- 4If they all match, there is no leak, whatever the verdict says. Otherwise note whether the odd one is IPv4 or IPv6 — that narrows the cause to a case below.
.local hostname is not a privacy failure — that address is not routable from the internet and identifies nothing about you. Pages print it because it is visible, not because it matters.When it is a real leak
- Split tunneling or per-app rules that exclude the browser. RFC 8828 names this case: with a split-tunnel VPN, WebRTC can discover both the public address of the VPN and the ISP address the VPN runs over.
- An IPv6 path outside the tunnel. With native IPv6 and an IPv4-only tunnel, v6 traffic exits through the physical interface, and a globally routable v6 address is not hidden by mDNS. Fix it at the network layer: carry IPv6 inside the tunnel, or turn it off on that interface.
- Extension-only proxying, as above — the configuration the classic warning was written about.
- Interface enumeration after camera or microphone consent. RFC 8828 permits a broader mode that probes every interface once the user consents to media capture, and Chrome and Safari stop obfuscating host candidates for a site holding that permission. Whether anything escapes depends on the client: one that blocks non-tunnel egress drops it, one that only installs routes may not.
Why “disable WebRTC” costs more than it fixes
On a full-device tunnel the reflexive candidate is already the tunnel’s address, so disabling WebRTC suppresses an address that was never yours. On a split tunnel or an unprotected IPv6 path it hides one symptom while the routing that caused it keeps sending other traffic outside the tunnel.
What it reliably breaks: browser video and voice calls, screen sharing, peer-to-peer file transfer, dashboards that stream live video. Firefox has a real switch — media.peerconnection.enabled in about:config — and it kills all of that until you flip it back. Chrome has none in its own preferences; extensions that seem to add one are setting the IP-handling policy through an extensions API, whose strictest value forces UDP through a proxy and breaks WebRTC outright when none is configured. Fix the path, leave the API alone.
A check that needs no test page
- 1Open
chrome://webrtc-internalsin Chrome, orabout:webrtcin Firefox, then start any real call in another tab. Candidates are listed with their types, straight from the engine. - 2Or paste the line below into the DevTools console on any page.
- 3Read only the lines whose type is
srflx. Those addresses, and no others, are what a remote peer sees.
const pc = new RTCPeerConnection({iceServers:[{urls:'stun:YOUR-STUN-HOST:3478'}]}); pc.onicecandidate = e => e.candidate && console.log(e.candidate.type, e.candidate.address); pc.createDataChannel('probe'); pc.createOffer().then(o => pc.setLocalDescription(o));
Substitute a STUN server you already trust — the one your conferencing tool uses, or one you run. The output is type/address pairs: what a test page reads, minus the verdict.
Does WebRTC leak my real IP address when I use a VPN?
It depends on how the VPN is installed. A full-device tunnel carries the browser’s STUN packets, so the reflexive candidate is the tunnel’s exit address. A browser extension that only proxies HTTP traffic does not carry UDP, so STUN leaves over the physical interface and reports the ISP address.
Does mDNS obfuscation hide my public IP address?
No. It replaces host candidates — local interface addresses such as 192.168.x.x — with a random .local hostname that resolves only on the same network segment. Server-reflexive and relay candidates still carry plain IP addresses, because a remote peer must be able to send packets to them.
A leak test shows my 192.168 address. Is that a problem?
By itself, no. It is not routable from the internet and is shared by an enormous number of home networks, so it identifies nothing. The .local hostnames exist to stop pages mapping your internal network.
Should I disable WebRTC in Chrome?
Chrome has no such switch in its own settings; extensions that seem to offer one change the browser’s IP-handling policy through an extensions API. Check first whether the reflexive candidate already matches your tunnel’s exit address — if it does, the extension has nothing to fix and costs you video calls.
Why does a test page show two different public addresses?
Usually one of three: a split-tunneling or per-app rule excludes the browser from the tunnel; native IPv6 runs outside an IPv4-only tunnel; or an extension proxies page traffic while UDP goes direct. The address family narrows it immediately.
Did this help?
Nothing follows from the answer; only we see it.