future-ocean-83972
02/26/2026, 1:38 PMtall-airline-16756
02/26/2026, 1:38 PMtall-airline-16756
02/26/2026, 1:38 PMfuture-ocean-83972
02/26/2026, 1:42 PMtall-airline-16756
02/26/2026, 1:42 PMiceTransportPolicy: 'relay' so all media flows via a TURN server. This avoids relying on host-reachable candidates.
• Ensure your TURN server is publicly reachable and properly configured.
3) Configure LiveKit / agent to replace private addresses with an external IP
• On the LiveKit server (self-hosted) set the external IP and port range so SDPs use the public address. For example via Helm/values or server flags: set external_ip (or equivalent) and `port_range_start`/`port_range_end`.
• If the agent has an option to override local interface detection, set it to the public IP/DNS.
4) Use host networking / adjust pod networking (if acceptable)
• If security/policies permit, run the agent Pod with hostNetwork: true so it advertises the node’s IP rather than the pod CIDR.
• Alternatively, set an environment variable or config in the agent to explicitly set the advertised address.
5) Ensure STUN/TURN are correctly configured
• Make sure STUN/TURN servers are reachable and included in the agent/LiveKit rtc config so ICE provides useful public candidates or returns to TURN fallback.
Diagnostic checks I recommend now
1. Capture ICE candidates or the SDP from the agent and confirm the advertised addresses (internal vs public).
2. Confirm how LiveKit server and agent are exposed (both inside k8s? LiveKit behind LB?).
3. Check whether a TURN server is configured and reachable.
4. Share the agent Pod spec networking bits (Service type, any hostNetwork setting) and the agent/LiveKit rtc config (external IP or cong flags) if you want specific config lines.
If you tell me:
• Is the LiveKit server also in the same Kubernetes cluster? How is it exposed (LoadBalancer/NodePort/ingress)?
• Is the agent the stock livekit-agent or custom code? Which SDK/language?
• Do you have a TURN server configured?
I’ll give exact config examples (Kubernetes Service, Helm values, or agent config) tailored to your setup.
Sources: Client SDKs | livekit/livekit_composite | DeepWiki | Agents (Python) | livekit/livekit_composite | DeepWiki | Firewall Tips for LiveKit Connectivity | LiveKit | Running LiveKit on AWS | examples/other | livekit/livekit_composite | DeepWikifuture-ocean-83972
02/26/2026, 1:48 PMtall-airline-16756
02/26/2026, 1:49 PMturn.enabled: true and udp_port: 3478 is fine, but there are additional points to verify in your LiveKit and k8s configuration.
Checklist and actionable steps
1) Expose the TURN/STUN ports externally
• Ensure the TURN UDP port (3478) and any TCP fallback ports are exposed by a Service (LoadBalancer or NodePort) or an ingress/NLB that forwards UDP/TCP to the LiveKit pods.
• TURN uses UDP (and sometimes TCP/TLS). A simple Service must include the UDP port mapping for 3478. Example Service snippet:
apiVersion: v1
kind: Service
metadata:
name: livekit
spec:
type: LoadBalancer
ports:
- name: stun-udp
protocol: UDP
port: 3478
targetPort: 3478
- name: stun-tcp
protocol: TCP
port: 3478
targetPort: 3478
# ensure RTP port range is exposed too (e.g., 50000-60000 UDP)
selector:
app: livekit
2) Make sure LiveKit advertises a public external IP/DNS
• Configure LiveKit’s rtc external IP/name so SDP and ICE candidates reflect a reachable address instead of cluster-internal IPs. In Helm values or the server config set the external IP/DNS and port range (example keys are rtc.external_ip / rtc.port_range_start / rtc.port_range_end depending on your chart/version).
• When the server knows its public IP, the SDP returned to clients will include reachable candidates.
3) Ensure RTP port range is exposed and configured
• LiveKit needs a port range open for RTP/RTCP (e.g., 50000–60000). Make sure these UDP ports are accessible externally and the server’s config matches that range.
4) STUN vs TURN behavior
• STUN is used to discover public candidates; TURN is used to relay media when direct connectivity fails.
• If your goal is only to have correct public candidates in SDP, STUN will help clients discover their public mapping; but in Kubernetes, the LiveKit server should be the entity advertising the public-facing candidates (so configuring external_ip is key).
• If direct connectivity is not possible, ensure TURN works as a relay fallback. Verify TURN authentication and that the TURN server returns relay candidates.
5) Verify connectivity and candidates
• From an external host, try STUN binding to your exposed address: use tools like stunclient or coturn client utilities to confirm the server responds and returns a public mapped address.
• Inspect the ICE candidates in browser devtools or agent logs to confirm whether public or private candidate IPs are being published.
Questions that will help me give exact steps
• Is LiveKit server itself running in the same Kubernetes cluster? How is it exposed to the internet (LoadBalancer, NodePort, ingress)?
• Are you using the LiveKit Helm chart or a custom manifest? If Helm, which values have you set for rtc/external_ip and port range?
• Do you see any TURN-related logs in LiveKit indicating the embedded TURN started successfully?
• Are you attempting to use STUN (binding) only, or also trying to use TURN relay? Do you have external_tls false intentionally (i.e., no TLS on TURN)?
If you share the LiveKit helm values (or the relevant sections of your config file) and how the Service/LoadBalancer is set up, I’ll provide exact example values and the minimal Service/Ingress manifest you should apply.