WebRTC IP leaks: root cause and real fixes
Why WebRTC reveals IP information, what STUN and TURN have to do with it, and how to fix the leak without hand-waving.
WebRTC leaks because it is doing its job.
That is the part most "WebRTC leak fix" articles skip. They talk as if the browser accidentally spilled your IP because of some embarrassing bug. Usually it did not. WebRTC was built to discover working peer-to-peer paths quickly, and that discovery process requires learning which addresses might work.
If you understand that one sentence, the rest of the fixes stop sounding magical.
The leak is ICE candidate gathering
WebRTC connectivity is built around ICE, the framework that tries multiple possible network paths between peers. MDN's WebRTC connectivity guide describes ICE candidates as the available methods a peer can use to communicate, either directly or through a relay.
Those candidates are the privacy surface.
In practice, the candidate types that matter are:
- host: a direct local interface address
- srflx: a server-reflexive address learned through STUN
- relay: a TURN-relayed path
If the browser gathers host candidates, it may expose local addressing information. If it gathers srflx candidates, it may expose the public address your NAT presents to the outside world. If it uses only relay candidates, the remote side learns about the TURN relay instead of your direct path.
That is the whole leak model in one paragraph.
What STUN reveals
STUN exists so a client can discover what address it appears to have from the outside. MDN's protocol guide is explicit about this: STUN is used to discover public reachability and NAT behavior.
That is good for connectivity and bad for secrecy.
A server-reflexive candidate is not some weird edge case. It is WebRTC asking, "what public address do other parties see for me?" If the answer is your real public IP instead of a VPN or relay address, the leak is not mysterious. The system asked the network who you are, and the network answered honestly.
This is why STUN-only setups are a bad default for privacy-sensitive applications. They optimize for directness, not concealment.
What TURN hides
TURN is the branch where privacy starts getting better and latency starts getting worse.
Instead of trying to establish the most direct peer-to-peer path, TURN relays traffic through an intermediate server. That means the remote peer sees the relay path rather than your direct address. MDN's documentation calls this out directly, and the RTCIceCandidate.address security notes go even further: if you want to prevent the remote side from learning your IP addresses, restrict candidates to relay candidates only.
That is the cleanest fix because it changes the mechanism, not the cosmetics.
In other words:
- STUN discovers you
- TURN stands in for you
There is no extension or privacy checkbox more fundamental than that distinction.
What a leak test is actually showing you
Sites like browserleaks.com/webrtc are useful, but only if you know what the result means.
You are not looking for "WebRTC present" or "WebRTC absent." You are looking for whether candidate gathering reveals addresses you did not intend to expose.
The worrying cases are usually:
- a local host candidate that gives away interface details you expected to stay hidden
- a server-reflexive candidate that matches your non-tunneled public network identity
- different behavior before and after VPN connection, Wi-Fi change, or browser update
The test is diagnostic, not authoritative. It tells you what the browser made visible during ICE gathering. That is enough to drive fixes.
Fixes for application developers
If you are building the WebRTC application, you have the most leverage and the least excuse.
The best control is to force relay-only behavior when privacy matters more than raw path efficiency:
const pc = new RTCPeerConnection({
iceServers: [
{ urls: "turn:turn.example.com:3478", username: "user", credential: "pass" }
],
iceTransportPolicy: "relay",
});
That tells ICE to use TURN-mediated candidates rather than exposing direct host or server-reflexive paths.
For debugging, log what is actually being gathered:
pc.onicecandidate = (event) => {
if (event.candidate) console.log(event.candidate.type, event.candidate.address);
};
That little snippet often resolves arguments instantly. You stop debating "whether WebRTC leaks" in the abstract and start looking at the concrete candidate types your app is emitting.
The operator stance here should be pretty blunt:
- if the application is privacy-sensitive, make relay-only a deliberate default
- if you allow direct candidates, admit that you are making a privacy-versus-performance tradeoff
- if you do neither and hope the browser picks something nice, you are delegating your threat model to accident
Fixes for users
Users do not control ICE at the same level, but they still have real levers.
The practical loop is:
1. Connect VPN
2. Open a WebRTC leak test page
3. Check for host or public candidates that match your non-tunneled network
Then act on what you find.
Useful mitigations include:
- prefer browsers and settings that reduce candidate exposure
- disable WebRTC when the application does not need it
- retest after every VPN, browser, or network change
- pair this with /blog/ipv6-leak-prevention, because dual-stack surprises make leak results much noisier
What I would not over-trust is the cargo-cult pile of random extensions that promise to "fix WebRTC." Some help in specific browsers. Many simply reshuffle symptoms. The mechanism is still ICE candidate policy.
Likewise, local-address masking features can help with host-candidate exposure, but they do not solve the public-IP side of the problem if STUN is still handing the remote side a server-reflexive candidate that points at your real network identity.
That is why this issue belongs next to /blog/browser-fingerprint-hardening, not in a generic "VPN tips" bucket. The leak is metadata exposure created by normal browser behavior.
The wrong mental model
The wrong model is "WebRTC ignores my VPN."
Sometimes the better description is "WebRTC is discovering every path it is allowed to discover, and your VPN/browser/app stack did not constrain those paths tightly enough."
That framing matters because it changes the fix:
- not "install more privacy theater"
- but "limit candidate types, prefer relays, and verify results"
It also explains why the same user can test clean on one browser and leak on another. Candidate policy is implementation behavior, not magic.
A practical opinion
If you care about minimizing IP disclosure, direct peer-to-peer path discovery is the wrong default. That does not mean WebRTC is bad. It means WebRTC is optimized for connectivity, and privacy-sensitive applications need to override that default explicitly.
Use TURN. Use relay-only where it matters. Re-test after every meaningful change.
And if your use case really cannot tolerate IP exposure, be honest about that too. Sometimes the right answer is not "better WebRTC settings." Sometimes it is "this workflow should not depend on peer-to-peer networking at all."
That is also why /blog/tor-technical-users-guide and WebRTC rarely fit together cleanly. One system tries to hide path identity. The other is built to discover working paths as aggressively as possible.
Clarity beats wishful thinking here. WebRTC leaks are understandable once you stop treating them like spooky browser behavior and start treating them like normal ICE behavior with privacy consequences.