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)

  1. 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 with chrome://webrtc-internals / about:webrtc that srflx candidates appear.
  2. 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”.
  3. UDP blocked — some networks drop UDP entirely. Configure TURN to offer TCP or TLS (turn:?transport=tcp, turns: on 443) so media can relay.
  4. Candidates never exchanged — trickle ICE: send every onicecandidate over signalling; call addIceCandidate on the remote. Ending too early (null candidate ignored, or signalling closed) leaves one side with an incomplete set.
  5. mDNS / privacy host candidates — browsers may hide LAN IPs behind .local names. Fine same-machine; across networks you still need STUN/TURN.
  6. One-way media / wrong direction — transceiver direction, muted tracks, or forgetting addTrack before createOffer. ICE can show connected while you still see a black tile — check tracks separately.
  7. 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
StateMeaning
checkingProbing candidate pairs
connected / completedPath found
disconnectedTemporary loss; may recover
failedAll pairs exhausted — fix servers / signalling / firewall
closedYou called close()

Signalling vs media

Not the same as

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 .

Try it

Related guides

Newest guides