Troubleshooting

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.

ICE connection states and what it means when a connection stalls or ends there.
StateMeaningIf it stays here or ends here
newGathering local candidates, or waiting for remote ones.No remote candidates arrived. Suspect signaling: the offer, answer or candidates are not reaching the other side.
checkingCandidate 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).
connectedA 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.
completedGathering and checking are finished and a pair is in use.Healthy. This value exists in iceConnectionState only, not in connectionState.
failedEvery 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.
disconnectedLiveness 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.
closedThe 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

  1. Capture the failure. Open chrome://webrtc-internals (Chrome, Edge) in a separate tab before connecting; Firefox users open about:webrtc. Reproduce the failure, then find the peer connection.
  2. Check signaling. In the event log, confirm that setRemoteDescription succeeded and that addIceCandidate calls arrived. No remote candidates means the problem is in your signaling, not the network.
  3. Check local candidates. Look for srflx (STUN reached, UDP works) and relay (TURN allocation succeeded). Missing srflx suggests UDP is blocked; missing relay means TURN is absent, unreachable or rejecting the credentials.
  4. Check the pairs. The candidate-pair stats show each pair's state (waiting, in-progress, failed, succeeded). If both sides have srflx candidates and every pair failed, the NATs do not allow hole punching and only TURN will work. If the only host candidates are .local names and the devices share a network, suspect mDNS.
  5. 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 srflx row proves STUN works; a relay row proves TURN works. Setting IceTransports to relay tests TURN alone, and errors from icecandidateerror are listed above the table.
  6. Repeat on the other side. Two endpoints mean two networks; the blocking one is often only one of them.
Symptoms, likely causes and fixes for WebRTC connection failures.
SymptomLikely causeFix
Stuck in new; no remote candidatesSignaling does not deliver the answer or candidatesLog every signaling message on both sides; queue candidates until the remote description is set.
No srflx candidate locallyUDP to STUN blocked, or STUN server unreachableTry another network; add TURN over TCP/TLS on port 443.
No relay candidate; icecandidateerror 401Wrong or expired TURN credentialsFetch fresh credentials before each connection.
No relay candidate; icecandidateerror 701TURN server unreachable on that URL and portCheck the URL, port and transport; offer turns: on 443.
srflx on both sides, all pairs failedAddress-dependent NAT on both sides, or carrier-grade NATConfigure TURN; nothing else helps.
Same LAN, only .local host candidates, failsmDNS blocked or devices on different subnetsUse TURN, or connect both to the same subnet without client isolation.
Fails for one user onlyExtension forcing disable_non_proxied_udp, VPN, or local firewallTest in a clean profile with the VPN off.
ICE connected, connectionState failedDTLS handshake failed (fingerprint mismatch, altered SDP)Ensure SDP is forwarded unmodified.
Connected, then disconnected, then failedNetwork change, sleep, or path lossAllow 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:

Terminal
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 result

Among 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; failed or closed end 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 and icecandidateerror codes. 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

  1. RFC 8445: Interactive Connectivity Establishment (ICE) (IETF)
  2. RFC 8656: Traversal Using Relays around NAT (TURN) (IETF)
  3. RFC 8828: WebRTC IP Address Handling Requirements (IETF)
  4. RFC 8838: Trickle ICE (IETF)
  5. RFC 4787: NAT Behavioral Requirements for Unicast UDP (IETF)
  6. RTCPeerConnection: iceConnectionState (MDN)
  7. 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 --encrypt