Why WebRTC Connections Fail, and How to Fix Them
By SRIFT maintainers · Published
Reviewed by SRIFT maintainers · last reviewed against the SRIFT 4.3.2 source · third-party facts checked 8 October 2026.
Short answer
A WebRTC connection fails when ICE, the procedure that tests pairs of network addresses, finds no pair that passes connectivity checks in both directions. The usual causes are a missing TURN server on networks where a direct path is impossible (UDP blocked, or address-dependent NAT on both sides), signaling bugs that lose or misorder candidates, and privacy or security software that hides addresses or blocks UDP.
To diagnose it, look at what each side gathered and which pairs were tried, in chrome://webrtc-internals or Firefox's about:webrtc. The reliable fix for restrictive networks is a TURN server reachable over TLS on port 443, or an application relay that does not depend on WebRTC.
- Failure signal
- iceConnectionState becomes "failed"
- Recoverable state
- "disconnected" can return to "connected" on its own
- Most reliable fix
- TURN, including TURN over TLS on port 443
- Diagnostic tools
- chrome://webrtc-internals, about:webrtc, the Trickle ICE sample
Key takeaways
- A connection fails when ICE finds no address pair that passes checks in both directions; “disconnected” can recover on its own, “failed” does not.
- The usual causes are a missing TURN server, blocked UDP, address-dependent NAT on both sides, lost or misordered candidates in signaling, and mDNS host names that cannot be resolved.
- chrome://webrtc-internals or about:webrtc shows which candidates each side gathered and which pairs were tried; no remote candidates means a signaling bug, not the network.
- The reliable fix for restrictive networks is TURN over TLS on port 443, or an application relay that does not depend on WebRTC.
- SRIFT treats the direct path as an optimisation: if it does not open within 3 seconds, the transfer runs over its end-to-end sealed relay.
A diagnosis checklist for any network. For networks with mandatory proxies, TLS inspection or only port 443 open, see WebRTC behind corporate firewalls and proxies.
Reading the ICE connection states
RTCPeerConnection.iceConnectionState moves through seven values defined by the W3C WebRTC specification. Where a connection stops tells you which part of the system to look at.
| State | Meaning | If it stays here or ends here |
|---|---|---|
| new | Gathering local candidates, or waiting for remote ones. | No remote candidates arrived. Suspect signaling: the offer, answer or candidates are not reaching the other side. |
| checking | Candidate pairs are being tested with STUN checks; none has succeeded yet. | Checks are sent but answers never come back: a firewall, NAT behaviour, or unusable candidates (for example .local names that cannot be resolved). |
| connected | A working pair exists for every component; checks may continue for a better one. | Healthy. If data still does not flow, look at DTLS or the data channel, not ICE. |
| completed | Gathering and checking are finished and a pair is in use. | Healthy. This value exists in iceConnectionState only, not in connectionState. |
| failed | Every pair has been checked and none works. | A real failure. Restart ICE (restartIce()) after fixing the network, add TURN, or fall back to another transport. |
| disconnected | Liveness checks on the selected pair are failing. | Often transient (Wi-Fi roaming, a brief outage). It may return to connected; it may also become failed. |
| closed | The connection was shut down by close(). | Your code, or the other side, ended the connection. |
Two related states are easy to confuse. connectionState aggregates ICE and DTLS, so a connection can show ICE connected and overall failed when the DTLS handshake fails, typically because the certificate fingerprint in the SDP does not match. And disconnected comes from consent freshness checks (RFC 7675): treating it as fatal the instant it appears tears down connections that would have recovered.
Why can't two browsers connect directly?
No TURN server configured
STUN tells a browser its public address so the two sides can attempt UDP hole punching. That works on many home networks, but not everywhere, and an application configured with STUN only has no path left when hole punching fails. TURN (RFC 8656) relays the traffic instead. A WebRTC deployment without TURN will fail for some share of users, and nothing in the browser can work around it. STUN vs TURN explains the candidate types.
UDP blocked
Guest, hotel, school and corporate networks often allow only outbound TCP to ports 80 and 443. STUN then returns nothing, no server-reflexive candidates appear, and only a TURN server on TCP or TLS can carry the connection. When an HTTP proxy is mandatory as well, only traffic that can pass through it works; a TLS-inspecting proxy can break even TURN over TLS. The corporate firewalls and proxies guide covers those networks.
Address-dependent NAT on both sides
Hole punching relies on a NAT reusing the same public mapping for a local socket whatever the destination (“endpoint-independent mapping” in RFC 4787 terms). NATs that create a new mapping per destination, often called symmetric NATs, give the STUN server one port and the peer another. One such NAT can sometimes still connect through peer-reflexive candidates; two of them, one on each side, generally cannot. Carrier-grade NAT on mobile networks adds another layer. The NAT traversal guide goes through the behaviours.
mDNS host candidates
To avoid exposing local IP addresses to every web page, browsers replace private host addresses in candidates with random names ending in .local, which the other peer must resolve with multicast DNS. That resolution fails when the two devices are on different VLANs or subnets, when the Wi-Fi blocks multicast (client isolation is common on guest networks), or when security software filters it. The two devices then cannot use the LAN path and depend on server-reflexive candidates (which need the router to support hairpinning) or on TURN. Granting camera or microphone permission makes Chrome expose real local addresses, which is why the Trickle ICE sample has a button for it.
Browser privacy settings and extensions
RFC 8828 defines how much address information a browser may expose, from all interfaces down to traffic forced through a proxy. Chrome extensions can choose among these with the webRTCIPHandlingPolicy setting (default, default_public_and_private_interfaces, default_public_interface_only, disable_non_proxied_udp), and some privacy extensions set the strictest one, which disables direct UDP entirely. In Firefox, setting media.peerconnection.enabled to false in about:config turns WebRTC off. If one user always fails, check their extensions first.
VPN and firewall software
Desktop firewalls may block UDP for the browser process, and VPN clients may block traffic outside the tunnel or restrict candidates to the tunnel interface. The VPN server's own NAT then decides whether a direct path is possible.
TURN misconfiguration and expired credentials
TURN servers normally use short-lived credentials generated by the application server. A page that caches them past their lifetime gets 401 errors and no relay candidates. Other common mistakes: turn: where turns: was meant, a missing ?transport=tcp, the wrong port, or a turns: hostname that does not match the server's certificate. Failures surface as icecandidateerror events: a STUN error code such as 401 from the server, or 701 when the server could not be reached at all.
Signaling bugs
With trickle ICE (RFC 8838) candidates are sent as they are found, so ordering matters. Classic bugs: calling addIceCandidate() before setRemoteDescription() (it throws unless candidates are queued), dropping candidate messages on a reconnecting signaling socket, sending candidates to the wrong peer in a multi-party room, and both sides creating offers at once (glare). These show up as a connection stuck in new or checking with an empty or short remote candidate list.
A diagnosis checklist
- Capture the failure. Open
chrome://webrtc-internals(Chrome, Edge) in a separate tab before connecting; Firefox users openabout:webrtc. Reproduce the failure, then find the peer connection. - Check signaling. In the event log, confirm that
setRemoteDescriptionsucceeded and thataddIceCandidatecalls arrived. No remote candidates means the problem is in your signaling, not the network. - Check local candidates. Look for
srflx(STUN reached, UDP works) andrelay(TURN allocation succeeded). Missingsrflxsuggests UDP is blocked; missingrelaymeans TURN is absent, unreachable or rejecting the credentials. - Check the pairs. The candidate-pair stats show each pair's state (
waiting,in-progress,failed,succeeded). If both sides havesrflxcandidates and every pair failed, the NATs do not allow hole punching and only TURN will work. If the only host candidates are.localnames and the devices share a network, suspect mDNS. - Test your ICE servers in isolation with the Trickle ICE sample page: enter the STUN or TURN URL and credentials and click “Gather candidates”. A
srflxrow proves STUN works; arelayrow proves TURN works. Setting IceTransports torelaytests TURN alone, and errors fromicecandidateerrorare listed above the table. - Repeat on the other side. Two endpoints mean two networks; the blocking one is often only one of them.
| Symptom | Likely cause | Fix |
|---|---|---|
| Stuck in new; no remote candidates | Signaling does not deliver the answer or candidates | Log every signaling message on both sides; queue candidates until the remote description is set. |
| No srflx candidate locally | UDP to STUN blocked, or STUN server unreachable | Try another network; add TURN over TCP/TLS on port 443. |
| No relay candidate; icecandidateerror 401 | Wrong or expired TURN credentials | Fetch fresh credentials before each connection. |
| No relay candidate; icecandidateerror 701 | TURN server unreachable on that URL and port | Check the URL, port and transport; offer turns: on 443. |
| srflx on both sides, all pairs failed | Address-dependent NAT on both sides, or carrier-grade NAT | Configure TURN; nothing else helps. |
| Same LAN, only .local host candidates, fails | mDNS blocked or devices on different subnets | Use TURN, or connect both to the same subnet without client isolation. |
| Fails for one user only | Extension forcing disable_non_proxied_udp, VPN, or local firewall | Test in a clean profile with the VPN off. |
| ICE connected, connectionState failed | DTLS handshake failed (fingerprint mismatch, altered SDP) | Ensure SDP is forwarded unmodified. |
| Connected, then disconnected, then failed | Network change, sleep, or path loss | Allow a grace period, then restartIce() or fall back to a relay. |
Checking the network from the command line
The SRIFT CLI includes a network check that runs its probes in parallel and reports each as working, warning, blocked or skipped, with a fix for anything that is not working:
srift doctor # DNS, HTTPS, WebSocket, UDP STUN, proxy, daemon and sandbox checks
srift doctor --deep # also creates an encrypted single-use 2 MB link and downloads it back
srift doctor --json # machine-readable resultAmong other things, it resolves DNS, fetches /compat.json over HTTPS (detecting captive portals and clock skew over five minutes), opens a WebSocket, sends a UDP STUN request to stun.cloudflare.com:3478, and checks proxy settings, the local daemon and whether it can start background processes. The verdict is ok, degraded or blocked, with the plan the CLI will use to serve files.
How SRIFT handles a failed connection
SRIFT treats a direct WebRTC connection as an optimisation, not a requirement. In a browser session:
- WebRTC is attempted only when it can work. A short probe checks whether a STUN server returns a server-reflexive candidate, and the browser fetches short-lived TURN credentials (TURN over TLS on port 443 is listed first). If neither UDP nor TURN is available, the transfer goes straight to the relay.
- A 3-second head start. If the direct data channel is not open within 3 seconds, the transfer runs over the WebSocket relay, with each chunk sealed with AES-256-GCM so the relay forwards ciphertext.
- A 6-second grace period on “disconnected”. The direct path is abandoned only if it has not recovered after 6 seconds;
failedorclosedend it at once. - Mid-transfer failure is not a restart. The relay continues from the bytes the receiver already has.
- Signaling survives blocked WebSockets. If WebSockets cannot open, the session connection switches to an HTTPS long-polling stream.
So in SRIFT a WebRTC failure usually means a relayed transfer rather than a failed one; see why P2P transfers are slow for what that does to speed. Command-line quick-share links do not use WebRTC at all: they need only outbound HTTPS on port 443.
Building WebRTC apps that fail less often
- Deploy TURN on UDP, TCP and TLS, with a
turns:URL on port 443 for networks that allow nothing else. - Mint TURN credentials per connection with a lifetime longer than the longest expected session, and never ship static credentials in a client bundle.
- Queue remote candidates until the remote description is set, and make signaling delivery reliable across reconnects.
- Treat disconnected as a warning. Wait a few seconds, then call
restartIce(), which needs a new offer and answer through signaling. - Record
getStats()on failure: candidate types gathered, pair states andicecandidateerrorcodes. Without them, user reports are unanswerable. - Have a fallback that is not WebRTC. A relay over HTTPS or WebSockets works on networks where even TURN is blocked by a proxy.
Frequently asked questions
Why does WebRTC work at home but fail on mobile data?
Mobile carriers commonly use carrier-grade NAT, and when its mapping is address-dependent, UDP hole punching fails. With a TURN server the connection still works through the relay; without one, it fails.
Does a VPN break WebRTC?
It can. Some VPN clients block UDP outside the tunnel or limit candidates to the tunnel interface, and the VPN provider's NAT then decides whether hole punching can work. TURN usually still connects, at the cost of an extra hop.
Is ICE failed the same as a firewall block?
Not necessarily. Failed only means no candidate pair passed connectivity checks. A firewall is one cause; others are incompatible NATs on both sides, unresolvable mDNS names, and candidates that never reached the other peer.
References
- RFC 8445: Interactive Connectivity Establishment (ICE) (IETF)
- RFC 8656: Traversal Using Relays around NAT (TURN) (IETF)
- RFC 8828: WebRTC IP Address Handling Requirements (IETF)
- RFC 8838: Trickle ICE (IETF)
- RFC 4787: NAT Behavioral Requirements for Unicast UDP (IETF)
- RTCPeerConnection: iceConnectionState (MDN)
- chrome.privacy API (webRTCIPHandlingPolicy) (Chrome for Developers)
Use SRIFT from a terminal
One command prints a download link that streams from your machine. Needs only outbound HTTPS; --encrypt keeps the key out of the relay.
npx -y srift-transfer quick-share ./build.tar.gz --encryptRelated resources
- STUN vs TURN: How WebRTC Gets Through NATWhat STUN and TURN servers do, how ICE picks between host, server-reflexive and relay candidates, and what a TURN relay can and cannot see.Explainer
- NAT Traversal Explained for Peer-to-Peer AppsWhy devices behind NAT cannot simply connect, how UDP hole punching works, which NAT behaviours defeat it, and why relays remain necessary.Explainer
- WebRTC Behind Corporate Firewalls and ProxiesWhy WebRTC fails on locked-down networks (UDP blocked, proxies, TLS inspection) and the fallbacks that still work over outbound port 443.Troubleshooting
- Why Is P2P File Transfer Slow? How to Find the BottleneckUpload bandwidth, TURN relaying, Wi-Fi, buffering and encryption overhead: how to find what is limiting a peer-to-peer transfer and fix it.Troubleshooting
