WebRTC ICE failed / ICE connection failed
The browser collected local addresses, talked to a STUN/TURN server, and still could not build a working path to the other peer. Media never starts (or dies right after connecting).
What ICE is doing
Interactive Connectivity Establishment (ICE) gathers address candidates (host, server-reflexive via STUN, relay via TURN), exchanges them through your signalling channel, then probes pairs until one works. When every pair fails you see:
ICE failed
// Chrome: iceConnectionState → "failed"
// Firefox: ICE failed, see about:webrtc
// Safari: WebRTC: ICE failed — check console / Web Inspector
Offer/answer may succeed while ICE still fails: SDP negotiated codecs, but packets never arrive.
Checklist (most common first)
- No STUN / bad STUN —
iceServers: []only works on the same LAN. Use at least one public STUN (e.g. your own or a known public server). Verify withchrome://webrtc-internals/about:webrtcthat srflx candidates appear. - Symmetric NAT / corporate firewall — host + STUN is not enough. You need a TURN server with credentials (
urls: turn:…/turns:…). Coturn and managed TURN products are the usual fix for “works at home, fails on guest Wi‑Fi / mobile data”. - UDP blocked — some networks drop UDP entirely. Configure TURN to offer TCP or TLS (
turn:?transport=tcp,turns:on 443) so media can relay. - Candidates never exchanged — trickle ICE: send every
onicecandidateover signalling; calladdIceCandidateon the remote. Ending too early (nullcandidate ignored, or signalling closed) leaves one side with an incomplete set. - mDNS / privacy host candidates — browsers may hide LAN IPs behind
.localnames. Fine same-machine; across networks you still need STUN/TURN. - One-way media / wrong direction — transceiver direction, muted tracks, or forgetting
addTrackbefore createOffer. ICE can show connected while you still see a black tile — check tracks separately. - VPN / dual-stack surprises — IPv6-only vs IPv4-only peers, or a VPN that black-holes UDP. Try without VPN; prefer TURN when paths differ.
Debug in two minutes
pc.oniceconnectionstatechange = () => console.log(pc.iceConnectionState);
pc.onconnectionstatechange = () => console.log(pc.connectionState);
pc.onicecandidateerror = (e) => console.warn(e.errorCode, e.url, e.errorText);
// Chrome: chrome://webrtc-internals → select the PeerConnection → IceCandidate pair
// Firefox: about:webrtc
| State | Meaning |
|---|---|
| checking | Probing candidate pairs |
| connected / completed | Path found |
| disconnected | Temporary loss; may recover |
| failed | All pairs exhausted — fix servers / signalling / firewall |
| closed | You called close() |
Signalling vs media
- Signalling (WebSocket, SSE, HTTP) only carries SDP and candidates. A healthy socket with ICE failed still means the media path is broken.
- Media usually uses UDP to STUN/TURN/peer. Packet loss on a thin pipe makes the call blocky or frozen — see the free lesson why video calls go blurry (bitrate / compression), separate from ICE failure.
Not the same as
- NotAllowedError getUserMedia — permission / HTTPS / device, before ICE matters.
- WebSocket connection failed — signalling never connected.
- TypeError: Failed to fetch — ordinary HTTP fetch, not PeerConnection.
- ECONNRESET / ETIMEDOUT — server-side TCP errors; different stack.
Related
WebSocket connection failed · NotAllowedError getUserMedia · Failed to fetch · ERR_NAME_NOT_RESOLVED · CORS error explained · Wirebook: video-call compression
Questions
What does WebRTC ICE failed mean?
The peers exchanged session descriptions but could not find a working network path. Add STUN/TURN, exchange all candidates, and check UDP/firewall.
Why does it work on the same Wi-Fi but fail on mobile data?
Carrier-grade NAT and strict firewalls block direct UDP. A TURN relay is almost always required for mobile ↔ home calls.
Do I need TURN if I already have STUN?
STUN discovers your public address; TURN relays media when direct paths fail. Production apps that must work on guest Wi-Fi and mobile need TURN.
Offer/answer succeeded — why is video black?
ICE or tracks failed afterward. Check iceConnectionState, whether addTrack ran, and mute/device state. Black tile ≠ always ICE failed.
How do I see candidate types?
chrome://webrtc-internals or about:webrtc lists host / srflx / relay. Missing srflx → STUN problem; missing relay when needed → TURN problem.
Is ICE failed caused by low bandwidth?
Not directly. Low bandwidth causes blur, freezes, and packet-loss concealment after a connection exists. ICE failed means no path at all.
Published .