Diagnosing ICE Failures with Firefox about:webrtc
When a connection negotiates cleanly but never reaches connected, Firefox’s about:webrtc report tells you precisely where ICE gave up. This guide is part of the Cross-Browser WebRTC Debugging guide, and it shows how to read the ICE stats table, interpret the candidate-pair rows, identify the nominated path, and save the log for post-mortem analysis — Firefox’s key advantage being that the report survives after the connection drops.
Context & Trade-offs
Unlike Chrome’s live graphs, about:webrtc is a flat HTML report you can open at any time, including after a failed call — Firefox retains the data until the tab navigates away. That makes it the right tool for post-mortems: you can reproduce a failure, then calmly read the candidate-pair table without racing a disappearing dashboard.
The trade-off is resolution. The report is a snapshot-plus-history rather than a continuously animated timeline, so you read state transitions from the ICE log section rather than watching a graph move. Firefox also enables IPv6 and mDNS host candidates aggressively, which means a dual-stack path mismatch surfaces here as a candidate pair that gathers but never nominates — a useful diagnostic signal rather than a Firefox defect.
Minimal Runnable Implementation
To make the about:webrtc table easy to correlate, log the same candidate-pair selection from your app, normalising Firefox’s historical field names so the output matches what Chrome and Safari produce.
// Normalise Firefox candidate-pair stats so logs match across engines.
// Older Firefox exposed `selected`; modern builds use `nominated` per spec.
async function logSelectedPair(pc) {
const report = await pc.getStats();
for (const s of report.values()) {
if (s.type !== 'candidate-pair') continue;
const isActive = s.nominated ?? s.selected; // Firefox legacy fallback
if (!isActive) continue;
const local = report.get(s.localCandidateId); // resolve the candidate detail
const remote = report.get(s.remoteCandidateId);
console.log('[ff-pair]', {
state: s.state, // 'succeeded' on the winning pair
localType: local?.candidateType, // host | srflx | relay
remoteType: remote?.candidateType,
rtt: s.currentRoundTripTime ?? s.mozRtt // legacy field fallback
});
}
}
setInterval(() => logSelectedPair(peerConnection), 1000);
Reproduction Steps & Debugging Log Patterns
- Reproduce the failing call, then open
about:webrtcin a new tab. Each connection appears as a section headed by its SDP and timestamps. - Scroll to the ICE Stats table. Each row is a candidate pair: local candidate, remote candidate, priority,
nominated, andselected. The columns are sortable — sort bynominatedto surface the winning pair instantly. - Read the nominated path. Exactly one row should show
nominated: trueandstate: succeeded; that local/remote candidate pair is the path media flows over. If no row is nominated, connectivity checks never succeeded and the connection is infailed. - Inspect candidate types on the nominated row. A
relay/relaypair means both peers fell back to TURN — connectivity works but you are paying relay latency, so revisit your TURN Server Configuration & Auth. A pair that gatheredsrflxcandidates but never nominated points at a symmetric-NAT or dual-stack mismatch. - Click Save Page at the top of
about:webrtcto persist the full report — SDP, candidate list, and RTP history — as a single HTML file for the bug report.
Expected log pattern for a healthy nominated path:
// [ff-pair] { state: 'succeeded', localType: 'srflx', remoteType: 'srflx', rtt: 0.041 }
A failing session instead shows every candidate-pair row with state: 'failed' or in-progress, none nominated, while iceConnectionState reports failed. When only host pairs appear and no srflx row was ever produced, your candidate gathering is the culprit — confirm against ICE Candidate Gathering & Filtering, because a STUN binding that refreshed in under 30 seconds on mobile can expire a candidate before nomination.
Common Implementation Mistakes
- Looking for live graphs.
about:webrtcis a table-and-log report, not an animated dashboard — read state transitions from the ICE log, not a moving line. - Assuming
nominatedis always present. Pre-117 Firefox surfacedselectedinstead; normalise with??or you will conclude no pair won when one did. - Misreading mDNS host candidates. Firefox replaces local IPs with
.localnames by default; an unresolved host candidate on the table is expected privacy behaviour, not the failure cause. - Ignoring IPv6 pairs. Firefox gathers IPv6 aggressively; a dual-stack mismatch shows as a gathered-but-never-nominated pair, which is the diagnosis, not noise.
- Not saving the page. The report persists only until the tab navigates; click Save Page before you close it so the post-mortem survives.
FAQ
No candidate pair is nominated — what does that mean?
Connectivity checks never produced a working path, so ICE is in failed. Check whether any srflx or relay candidates were gathered at all; if only host candidates exist, NAT traversal could not start and the fault is in gathering or your STUN/TURN reachability.
Can I read about:webrtc after the call already ended? Yes — that is its main advantage over Chrome. Firefox keeps the report until you navigate the tab, so you can reproduce a failure and analyse the table afterward at your own pace.
Related: this deep-dive sits under Cross-Browser WebRTC Debugging; compare it with reading chrome://webrtc-internals dumps and the trickle strategy in ICE Candidate Trickle vs Bulk Gathering.