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

  1. Reproduce the failing call, then open about:webrtc in a new tab. Each connection appears as a section headed by its SDP and timestamps.
  2. Scroll to the ICE Stats table. Each row is a candidate pair: local candidate, remote candidate, priority, nominated, and selected. The columns are sortable — sort by nominated to surface the winning pair instantly.
  3. Read the nominated path. Exactly one row should show nominated: true and state: 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 in failed.
  4. Inspect candidate types on the nominated row. A relay/relay pair 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 gathered srflx candidates but never nominated points at a symmetric-NAT or dual-stack mismatch.
  5. Click Save Page at the top of about:webrtc to 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

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.