WebRTC ICE failed: causes and how to diagnose it
Why WebRTC calls fail to connect, what iceConnectionState failed really means, and how to read the candidate pairs to find out why.
Sometimes, WebRTC fails to connect a sesion or a call. When that happens, internally what you will notice is that iceConnectionState goes to failed, or it connects and then drops to disconnected and then failed.
No media ever flows, or it flows for a moment and dies. Lets see what ICE failure is, what causes it, and how to read the candidate pairs in a dump to find out why.
What ICE failure actually is
Before any media flows, WebRTC has to find a network path between the two peers. ICE gathers candidate addresses (your local address, your public address via STUN, and a relayed address via TURN), then runs connectivity checks across every pairing until one works. iceConnectionState reports how that search is going.
failed means no candidate pair ever passed its connectivity checks: ICE tried every combination and none of them could carry traffic. disconnected then failed means a pair was working and then stopped answering, so the connection was lost mid-call.
iceConnectionState itself comes from the connection, not from getStats(). What getStats() gives you is the evidence: the candidate pairs, which one was selected, and whether the connectivity checks (STUN requests and responses) were getting answered.
What causes it
In rough order of how often it is the culprit:
- A symmetric NAT or strict firewall on one or both ends, so the only path that can work is a relay, and there is no reachable relay.
- No TURN server, or a misconfigured one. When direct and reflexive paths are blocked, TURN is the fallback. If it is missing, wrong, or its credentials are stale, ICE has nothing left to try.
- UDP blocked entirely on a corporate network, with no TURN-over-TCP or TURN-over-TLS fallback configured.
- Expired or wrong TURN credentials, so the relay refuses the allocation.
- Consent freshness lost mid-call: the path stopped answering periodic checks, which turns a working call into
disconnectedthenfailed.
How to diagnose it
Look at the candidate pairs and the connectivity checks, not just the final state.
| What to check | Healthy | Trouble |
|---|---|---|
| A selected candidate pair exists | one pair nominated and in use | no pair ever nominated |
| Candidate types in the working pair | host or srflx when direct works | only relay works, or nothing works |
| STUN requests versus responses | responses coming back | requests sent, no responses |
| Consent checks mid-call | steady, answered | stop being answered before the drop |
Read it like this:
- No nominated pair at all: the two sides never found a path. Almost always NAT or firewall plus a missing or broken relay. Confirm a TURN candidate was even gathered.
- Only relay pairs work: direct connectivity is blocked, which is fine as long as TURN is healthy. If relay is also failing, the problem is the TURN server or its credentials.
- Requests sent but no responses: the checks are not getting through. Something between the peers is dropping STUN, commonly UDP being blocked with no TCP/TLS fallback.
- Worked, then consent checks went unanswered: the path died mid-call (a network change, a NAT binding timing out, strict firewall rules kicking it, the relay dropping the allocation).
How rtcStats surfaces it
Reading candidate pairs and STUN check timings by hand in webrtc-internals is slow and easy to misread. rtcStats reads the dump for you: it flags the failed or dropped connection as an Observation, points at which candidate pairs were tried and where the checks stopped as a Deduction (no relay, blocked UDP, lost consent), and reflects that the call never carried media in the Experience Score. The AI Summary gives you the one-paragraph version to paste into your bug thread.
Related
- transport.selectedCandidatePairId
- transport.selectedCandidatePairChanges
- candidate-pair.requestsSent
- candidate-pair.responsesReceived
- candidate-pair.consentRequestsSent
Upload a webrtc-internals or rtcstats dump and rtcStats tells you why ICE never connected, free.
Was this page helpful?