The UE Stream Only Gets to 'negotiated' From Outside
We had Pixel Streaming running — the Unreal Engine rendering streamed to a browser over WebRTC. On the internal network it was perfect. From the external internet, the connection would get to "negotiated" and stop cold, with zero video frames arriving. Signaling succeeded but media was zero. Where was it blocked?
ICE Candidates Were All Internal IPs
Before establishing a WebRTC connection, ICE negotiates "what path should we use to connect?" Both sides exchange addresses (candidates) they can be reached at. The problem: the UE streamer was only advertising internal IP (host) candidates.
Rendering diagram…
On the internal network, those internal IPs were reachable and it worked. From outside, there was no path to those addresses — negotiation completed but no media flowed. The classic "says it's connected but no video" symptom.
The Fix — TURN Relay
To move media across NAT and firewalls, a relay server (TURN) is required. When direct connectivity is impossible, the TURN server relays packets between the two sides.
Rendering diagram…
Three configuration elements were essential:
- Provide both internal and public candidates — internal connections use the internal path (avoiding hairpin routing), external connections use the public path.
- Public candidate over TCP — many external networks block UDP, so a TCP transport is used as a fallback.
iceTransportPolicy: relay— force the connection to always use the relay path, abandoning direct attempts.
💡 Internal TURN is for hairpin avoidance — Many networks block hairpin routing, where traffic from inside the network tries to reach its own public IP and loop back. Having a separate internal TURN server for internal clients and a public TURN server for external clients provides reliable connectivity in both cases.
⚠️ Assume external UDP is blocked — Corporate and public Wi-Fi networks frequently block UDP. Since WebRTC defaults to UDP, "works on my machine, doesn't work at the customer site" often has this root cause. Opening a TCP transport for public candidates clears this block.
Verified With Evidence
Post-fix validation was done with packets, not guesswork — relay traffic from the TURN server reached thousands of packets during external access, and the GPU's NVENC encoder was actively running at the same time. Both internal and external streams were confirmed to deliver video.
In a nutshell
📌 When WebRTC stalls at "negotiated" with zero media from outside, the cause is ICE candidates limited to internal IPs. TURN relay (internal + public candidates, public over TCP,
iceTransportPolicy: relay) crosses NAT and UDP blocks. Validate with relay packet counts and NVENC activity — evidence, not assumptions.
References
See standard test results with videos and verdict evidence on the imSim Portal.
View public results →