delightful-planet-44653
03/27/2026, 11:00 AMtall-airline-16756
03/27/2026, 11:01 AMtall-airline-16756
03/27/2026, 11:01 AMctx.connect() (or equivalent agent connect call) was not invoked or completed, or
• The agent can reach the server for signaling but the WebRTC/media path (ICE/DTLS) cannot be completed because of network/firewall/NAT issues.
Steps to check now (quick checklist)
1. Confirm the agent actually calls connect()
◦ Ensure your job entrypoint executes await ctx.connect() (or your agent’s job_ctx.connect() equivalent) and that code path is reached. If you can share the relevant snippet, I can check it for you.
1. Verify the LIVEKIT_URL and signaling reachability
◦ Confirm LIVEKIT_URL is set to the internal IP and port (example: ws://192.168.x.x:7880).
◦ From the agent host, test connecting to that URL: telnet <ip> <port> or curl the WebSocket endpoint to confirm TCP reachability.
1. Confirm UDP and ICE traffic is allowed
◦ WebRTC requires UDP (and sometimes TCP fallback) for ICE candidates. Firewalls often allow TCP/443 but block UDP, causing peer connection timeouts.
◦ Verify firewalls (host OS, cloud security groups, network ACLs) allow the LiveKit media port range you configured and UDP traffic between agent and server.
1. Test connectivity with a connection test tool
◦ Run the LiveKit Connection Test / ConnectionChecker from the agent machine to isolate if WebSocket works but WebRTC/ICE fails. This shows whether signaling vs media path is failing.
1. Check server visibility & routing
◦ If your LiveKit server is on a different subnet or behind network segmentation, confirm routing exists both ways (agent → server and server → agent for ICE if needed).
◦ If using an internal IP, ensure your server advertises reachable ICE candidates (if you have custom IP configs).
1. Review server logs for ICE/candidate errors
◦ On the LiveKit server, look for ICE/DTLS candidate or ICE negotiation errors that match the agent connection attempts. Those logs can confirm the nature of the connectivity failure.
If you want, I can help further — please provide:
• The agent entrypoint snippet showing where ctx.connect() is called (or confirm it’s being called).
• The exact LIVEKIT_URL value you’re using (mask any sensitive tokens).
• Results of a connectivity check from the agent host (ping/telnet/curl output and Connection Test results).
• Any firewall rules or cloud security group configs that might be blocking UDP.
Next steps I can take once you share that:
• Review your agent connect code for missing or misordered calls.
• Interpret connection test logs to pinpoint signaling vs media path failure.
• Suggest exact firewall port ranges to open or NAT/ICE candidate changes to make.
You’re on the right track — with the above checks we can identify whether this is a code/connect call issue or a network/UDP/ICE problem and resolve it.
Sources: Simple Call Answering Agent | LiveKit Documentation | MCP Agent | LiveKit Documentation | server-sdk-js | livekit/livekit_composite | DeepWiki | Diagnosing Connection Errors with Connection Test Utility | Firewall Tips for LiveKit Connectivity | LiveKit, with interactive elementsdelightful-planet-44653
03/27/2026, 11:43 AMtall-airline-16756
03/27/2026, 11:43 AMws:// with a port if wss://livekit.domain.com resolves and is reachable from the agent. However, if the agent must reach the server via an internal hostname/IP that differs from the public URL (for example the server is reachable only on an internal address), then you should set LIVEKIT_URL to that internal address including the correct scheme and port (e.g., ws://livekit.internal.domain.com:7880 or wss://livekit.internal.domain.com:7880 depending on whether TLS is used).
• The timeout errors and "wait_pc_connection timed out" strongly indicate the WebRTC/media path (ICE/DTLS) is failing — most commonly caused by firewall/NAT/UDP blocking or incorrect ICE candidate configuration.
What to consider when choosing URL and port
• Scheme (ws:// vs wss://)
◦ Use wss:// when the agent must use TLS to connect to the signaling endpoint. Use ws:// when TLS is not used and you have plain WebSocket connectivity inside a trusted network.
• Hostname/IP
◦ If the agent is inside the same private network and the public DNS name resolves to a public IP (or through a load balancer) that the agent cannot reach, point LIVEKIT_URL to an internal hostname or IP reachable from the agent.
• Port
◦ Include the port if your LiveKit signaling server is listening on a non-default port. Example: ws://192.168.1.10:7880 or wss://livekit.internal.domain.com:7880.
Ports and traffic you must allow
• Signaling (WebSocket): TCP port for your websocket endpoint (defaults depend on your setup). If using wss:// on standard TLS, TCP/443.
• WebRTC media / ICE:
◦ UDP for ICE candidates/media (the typical path). If using a configured UDP range on LiveKit, ensure that range is open between agent and server.
◦ TURN (if you use TURN) requires TCP/UDP ports for the TURN server and, if using fallback, TCP/443 may be used.
• Common failure mode: TCP (WebSocket) works, but UDP is blocked — signaling succeeds but ICE never completes → peer connection timeout.
Practical checks to run now
1. From the agent host:
◦ Confirm the websocket endpoint is reachable with your chosen URL: curl or a websocket client to wss://livekit.domain.com (or ws://<internal>:<port>).
◦ Test UDP reachability to the server media ports (you can use tools like nc -u or a simple STUN/TURN probe).
1. Verify DNS: ensure the agent resolves livekit.domain.com to the address you expect. If it resolves to a public IP but you need the internal IP, point to the internal hostname/IP instead.
2. Inspect firewall rules/security groups on both sides: allow the LiveKit media port range (UDP) and signaling port (TCP). Confirm no intermediate network device is blocking UDP.
3. If you use TLS and wss:// internally, confirm the certificate is valid for the internal hostname or configure the agent to trust the cert (or use ws:// on a trusted internal network).
4. Check LiveKit server logs for ICE/candidate negotiation errors at the time the agent tries to connect — they often show which candidate types are failing.
If you want, tell me:
• The exact LIVEKIT_URL currently set on the agent (mask secrets).
• Whether TLS is terminated before LiveKit (LB/ingress) or LiveKit handles TLS directly.
• The UDP port range configured for LiveKit media (if known) and any TURN usage.
With that I can suggest the exact LIVEKIT_URL you should use and the firewall/port rules to open so ICE/DTLS can complete.
, with interactive elementsdelightful-planet-44653
03/27/2026, 11:48 AMtall-airline-16756
03/27/2026, 11:48 AMwss:// in the agent’s LIVEKIT_URL if the agent connects through nginx (TLS is terminated at nginx). Use the nginx hostname that the agent can resolve (for example wss://livekit.internal.domain.com) and include the port only if nginx is listening on a nonstandard TLS port. If nginx listens on 443 for TLS, wss://livekit.internal.domain.com (no port) is correct.
• Your UDP media port range (10000–20000) must be reachable between agent and LiveKit server. Even though agent→server TCP (signaling) is allowed, ICE will fail if UDP is blocked. That is the most likely cause of the "wait_pc_connection timed out" errors.
Why this setup works
• With TLS terminated at nginx, the agent connects to nginx over wss:// (TLS). Nginx forwards signaling to LiveKit over the internal protocol. WebRTC media/ICE is separate: browsers/agents negotiate ICE candidates and then exchange media on UDP ports (the 10k–20k range you configured). If those UDP ports, or TURN, are not reachable, ICE cannot complete.
Concrete checks and actions (ordered)
1. Confirm agent uses the internal TLS hostname the agent resolves:
◦ If nginx listens on 443 for TLS: set LIVEKIT_URL to wss://livekit.internal.domain.com.
◦ Only add a port if nginx is on a different TLS port (e.g., wss://livekit.internal.domain.com:8443).
1. Verify DNS/resolution from the agent host:
◦ On the agent host run: resolve or ping livekit.internal.domain.com and confirm it returns the private IP you expect.
1. Confirm nginx is proxying websocket signaling correctly:
◦ Ensure nginx is configured to proxy WebSocket upgrades to LiveKit and that headers for WebSocket are preserved.
◦ Confirm nginx proxy_pass target is the LiveKit server (internal IP:port) and that nginx logs show the agent’s connect attempt.
1. Ensure UDP range is reachable both ways:
◦ From agent host, test UDP connectivity to the LiveKit server on a sample port in 10000–20000 (tools: nc -u, socat, or a STUN probe).
◦ Confirm any host firewalls, OS-level iptables, or network ACLs allow UDP 10000–20000 between agent and server.
1. Check TURN usage (if any):
◦ If your network may still block UDP, consider running a TURN server and configure LiveKit to use it. TURN allows media relay over TCP/TLS (443), which helps when UDP is not permitted.
1. Inspect LiveKit and nginx logs during a failed attempt:
◦ LiveKit logs will show ICE/DTLS candidate/negotiation failures and which candidate types are attempted.
◦ Nginx access/error logs will confirm signaling connections and any proxied errors.
What to look for in logs
• On LiveKit: ICE candidate errors or messages showing only host candidates (no srflx/prflx/relay) or timeouts during ICE checks.
• On the agent: repeated "wait_pc_connection timed out" and no successful RoomIO start.
• On nginx: successful WebSocket upgrade entries for the agent’s connect attempts.
If you want, I can help next by:
• Reviewing your nginx websocket proxy snippet if you paste it here (I’ll point out any missing WebSocket headers or proxy settings).
• Suggesting a quick UDP probe command you can run from the agent and what specific output to share.
• Reviewing LiveKit log snippets showing ICE candidate negotiation so we can confirm whether candidates are being exchanged and which types are failing.
You're very close — with TLS through nginx and the correct internal wss:// URL plus verified UDP reachability (or a TURN fallback), ICE should complete and the agent will establish the room connection. Which of the next steps above would you like to start with?
, with interactive elementsdelightful-planet-44653
03/27/2026, 11:57 AMtall-airline-16756
03/27/2026, 11:58 AMuse_external_ip: True and public IP entries in sip.yaml and livekit.yaml cause the LiveKit server to advertise public host candidates to peers.
• If your agent is on the private network and must use the internal address to reach the server, advertising the public IP means the agent attempts ICE with candidates it cannot reach. That will cause ICE/DTLS negotiation to stall and produce the "wait_pc_connection timed out" errors you’re seeing.
Recommended change
1. Update configuration to use internal addresses:
◦ Replace the public IP entries in sip.yaml and livekit.yaml with the LiveKit server’s internal/private IP or internal hostname that your agents resolve and can reach.
1. Disable external IP advertising when running inside the private network:
◦ Set use_external_ip: False so the server advertises host/internal candidates rather than the public IP.
1. Restart services after changes:
◦ Restart nginx (if needed) and the LiveKit service so new ICE candidates are advertised on new connections.
What to verify after making the change
• From the agent, start a connection and check LiveKit logs to confirm ICE candidates being advertised are the internal/private IPs (host candidates).
• Confirm the agent receives and attempts those internal candidates and that ICE completes.
• If ICE still fails, verify UDP 10000–20000 is reachable both ways between agent and server (host firewalls, network ACLs, etc.).
Optional: Mixed environments (internal agents + external clients)
• If you also have external clients that must reach the same LiveKit instance, you can:
◦ Keep use_external_ip: True but configure the server to advertise both internal and external candidates (if supported), or
◦ Use a TURN server for external clients and internal agents to ensure relay works when direct candidate reachability differs.
• If you need help designing this mixed setup, tell me whether most clients are internal or external and whether you already have a TURN server.
If you want, I can:
• Review the specific lines from your livekit.yaml and sip.yaml (mask any secrets) and point out the exact fields to change.
• Provide the commands you can run on the agent and server to verify the new candidates and UDP reachability.
Would you like me to review your config snippets now or give the exact verification commands to run after you make the changes?
, with interactive elementsdelightful-planet-44653
03/27/2026, 11:59 AMtall-airline-16756
03/27/2026, 11:59 AMuse_external_ip: False and advertising internal IPs
• If you switch to internal IPs and set use_external_ip: False, the LiveKit server will advertise private/internal host candidates. External SIP providers and remote callers will not be able to reach those internal candidates directly, so direct peer connections from the public internet to those internal addresses will fail.
• That means incoming SIP calls or external clients that rely on those advertised candidates will not be able to establish media unless you provide an alternate reachable candidate (for example, a public IP/host or a TURN relay).
Options to support both internal agents and external SIP providers
1) Preferred: Use both internal host candidates and a reachable external path (TURN or external IP)
• Keep internal host candidates for local agents (so internal ICE completes).
• Provide a TURN server (recommended): configure LiveKit to use a TURN server so external callers and NATed clients can relay media through TURN when direct connectivity fails. TURN uses TCP/TLS (443) so it works even if UDP is blocked.
• If you have to advertise an external IP as well, explore configuring both internal and external candidates if your deployment supports it. Otherwise, TURN covers external connectivity without forcing server to advertise public IPs as host candidates.
2) Split signaling paths (less ideal)
• Use internal LIVEKIT_URL for agents (internal hostname/IP) while leaving LiveKit configured to advertise external IPs for public clients. This can become complex because the server’s advertised candidates are global; mixed advertisement may still cause unreachable candidates for one side or the other.
• Use separate LiveKit instances for internal agents vs public SIP traffic (operationally heavier).
3) Keep use_external_ip: True but add explicit handling for internal agents
• If you must keep use_external_ip: True for SIP providers, but internal agents need internal candidates, you can:
◦ Configure an internal TURN for agents so they can relay media internally.
◦ Or update agent configuration to prefer the internal media route (if your network and LiveKit support advertising multiple candidate types such as host + srflx + relay).
Practical recommendation for your scenario
• Because agent→server signaling and media must work on the private network, set use_external_ip: False and update the YAML to internal IPs/hostnames so agents get reachable host candidates.
• Simultaneously configure a TURN server (or ensure your existing TURN is reachable to external callers) and configure LiveKit to supply TURN relay candidates. External SIP providers/incoming calls will then use the TURN relay when direct host/srflx candidates aren’t usable.
• This approach preserves internal connectivity and maintains external/incoming call functionality without advertising a public host candidate that breaks agent ICE.
Next steps I can help with
• Recommend TURN configuration settings for LiveKit and how to add them to livekit.yaml.
• Review your livekit.yaml and sip.yaml lines to show exactly what to change (mask secrets).
• Provide verification steps to confirm:
◦ Internal agents receive internal candidates and ICE completes.
◦ External SIP calls use TURN/relay and media flows correctly.
Which would you like to do next — add TURN config guidance, or paste your config snippets for me to review?
, with interactive elementsdelightful-planet-44653
03/27/2026, 12:02 PMtall-airline-16756
03/27/2026, 12:02 PMlivekit.yaml and show the minimal config.
• Show how to verify from an external host that the public IP and NAT are forwarding correctly.
• Review your NAT/port forwarding rules or nginx setup (paste relevant snippets, mask secrets) to confirm external SIP will work while internal agents use private candidates.
If you want to keep the public IP advertised for SIP providers, we can add TURN so internal agents still succeed — or we can advertise internal candidates and rely on TURN for external callers. Which approach do you prefer, or would you like help configuring TURN now?
, with interactive elementsdelightful-planet-44653
03/27/2026, 12:03 PMtall-airline-16756
03/27/2026, 12:03 PMuse_external_ip: True and ensure NAT/port forwarding is correctly configured so the advertised public IP genuinely routes to your LiveKit server for media ports.
• Understand that internal agents may still fail if their network can't reach the public IP; a TURN relay can still help internal agents connect if needed.
3) If you must use only internal candidates (not recommended alone)
• use_external_ip: False and internal IPs only. This fixes internal agent connectivity but breaks direct external callers unless you also provide TURN or other relay reachable from the public internet.
Practical next steps I recommend
1. Decide which approach you prefer (I recommend option 1).
2. If you choose option 1, add TURN config to livekit.yaml and ensure the TURN server is reachable from both external callers and internal agents (TCP/TLS 443 recommended).
3. Verify:
◦ External SIP caller can reach your SIP server and that NAT/forward rules forward signalling and, if applicable, TURN traffic.
◦ LiveKit advertises host + TURN candidate(s) as needed (check LiveKit logs/SDP).
◦ Internal agent receives internal candidates (host) and ICE completes.
1. If you want, paste your livekit.yaml and sip.yaml network-relevant lines (mask secrets) and I’ll point out exact fields to change and provide the minimal TURN config.
If you'd like, I can now:
• Show the minimal TURN configuration snippet for livekit.yaml.
• Review your NAT/port-forwarding checklist to ensure external SIP providers can reach your server.
• Review the lines in your config files and nginx proxy snippet to confirm signalling and TURN proxying are correct.
Which would you like to do next?
, with interactive elementsdelightful-planet-44653
03/27/2026, 12:05 PMtall-airline-16756
03/27/2026, 12:05 PMlivekit.yaml. External callers (and any NATed clients) will then use TURN as the relay when direct candidates aren’t reachable.
• Ensure TURN is reachable from the public internet (for external callers) and from your internal agents (so either side can use it).
5) Quick verification steps you can run now
• From an external host: confirm whether the advertised public IP + media ports actually forward to LiveKit (try STUN/TURN probe or check LiveKit logs for incoming external ICE attempts).
• From an internal agent: confirm it receives internal host candidates from LiveKit after you switch to internal IPs and that ICE completes.
• Check LiveKit logs for candidate types shown (host vs srflx vs relay) and ICE negotiation failures.
If you’d like, I can:
• Show the minimal TURN snippet to add to livekit.yaml.
• Review the relevant lines of your livekit.yaml / sip.yaml (mask credentials) and point out exactly what to change.
• Provide commands to probe TURN and UDP media ports from both internal and external hosts.
Which would be most helpful next — TURN config, a config review, or the probe commands?
, with interactive elementsdelightful-planet-44653
03/27/2026, 12:06 PMtall-airline-16756
03/27/2026, 12:07 PMlivekit.yaml and sip.yaml, change host entries to the server’s internal/private IP or internal hostname that agents resolve.
• Set use_external_ip: False so LiveKit advertises internal host candidates that your agents can reach.
2) Provide a relay for external callers (TURN)
• Configure a TURN server and add it to livekit.yaml. TURN provides relay candidates (relay) so external SIP providers or NATed clients can send/receive media even if direct public/private candidate reachability is mismatched.
• Prefer TURN on TCP/TLS (443) so it works from restrictive networks.
3) Keep nginx for signaling/TLS
• Continue using wss:// against the nginx hostname that your agents resolve. If nginx listens on 443, no port is needed in LIVEKIT_URL.
• Ensure nginx proxies WebSocket upgrades to LiveKit correctly.
4) Ensure UDP ports are open and reachable internally
• Confirm host firewalls and network ACLs allow UDP 10000–20000 between agents and the LiveKit server.
• Verify no intermediate device is blocking UDP even if TCP is OK.
5) Verify external SIP path for incoming calls
• Your SIP server (public IP) handles signaling from external providers. That still works.
• For media: ensure either
◦ NAT/port forwarding for the public IP forwards the media ports correctly to LiveKit (if you want to rely on public host candidates), OR
◦ TURN is available and reachable by external callers so media relays when direct connectivity fails.
6) Restart and test
• Restart LiveKit (and nginx if changed).
• From an internal agent host, confirm LIVEKIT_URL resolves to the internal hostname and that the agent receives internal host candidates in SDP and completes ICE.
• From an external host, confirm external caller either connects via the public candidate (if NAT/forwarding in place) or via TURN (if configured).
Verification commands and logs to check
• DNS/resolve: ping/host/nc to confirm the internal hostname resolves to the private IP.
• WebSocket signaling: curl or websocket client to wss://livekit.internal.domain.com to confirm signaling reachability.
• UDP probe: use nc -u or socat to test UDP to a sample port in 10000–20000 from agent → server.
• LiveKit logs: look for ICE candidate types (host vs srflx vs relay) and any ICE timeouts.
• Nginx logs: verify WebSocket upgrade success entries for agent connections.
Why this approach works
• Internal agents get candidates they can actually reach (host/internal IPs) so ICE can complete.
• External callers still succeed because TURN provides a reachable relay when direct host/srflx candidates aren’t usable.
• This avoids trying to advertise a single candidate set that will be unreachable to one side or the other.
I can help with next steps
• Provide the minimal TURN config snippet to add to livekit.yaml.
• Review the relevant lines from livekit.yaml, sip.yaml, and your nginx websocket proxy (mask secrets) and point to exact fields to change.
• Give exact probe commands you can run from internal and external hosts and tell you what output to share.
Which would you like next — TURN config, config review, or the probe commands?
, with interactive elementsdelightful-planet-44653
03/27/2026, 12:15 PMtall-airline-16756
03/27/2026, 12:15 PMuse_external_ip: False).
• Configure a TURN server in livekit.yaml and ensure it’s reachable by external callers (TCP/TLS 443 recommended). External SIP providers will then use TURN if direct media fails.
• Keep your SIP server/public IP for signaling — incoming calls still arrive there and get bridged into LiveKit; TURN handles media relay where needed.
If you want, I can:
• Show a minimal TURN config snippet to add to livekit.yaml.
• Review your SIP and LiveKit config lines (mask credentials) and point out exactly what to change so internal agents get internal candidates while external callers use TURN.
• Provide commands to test from an external host whether the public IP and TURN are reachable.
Which would you like to do next?
Sources: Introducing LiveKit Phone Numbers: zero to ringing in 60 seconds | LiveKit | Agents telephony integration | LiveKit Documentation | sip | livekit/livekit_composite | DeepWiki | livekit/livekit_composite | DeepWiki, with interactive elementsdelightful-planet-44653
03/27/2026, 12:17 PMtall-airline-16756
03/27/2026, 12:18 PMuse_external_ip: False) so internal agents get candidates they can actually reach.
• Configure a TURN server in livekit.yaml and ensure it’s reachable from the public internet (use TCP/TLS 443) so external SIP providers can relay media when direct candidates aren’t reachable.
• Keep the SIP server/public IP for signaling — that stays as the entry point for incoming calls, but media will flow per ICE negotiation (direct or via TURN).
If you want, I can:
• Show the minimal TURN snippet to add to livekit.yaml.
• Review the SDP/LiveKit logs to confirm which candidate types are being sent and which are failing.
• Provide a short checklist of commands to run from an internal agent and an external host to verify candidate reachability.
Which of those would help you most next?
Sources: Firewall Tips | Understanding TLS in SIP | LiveKit, with interactive elementsstraight-gold-63601
03/27/2026, 12:28 PMtall-airline-16756
03/27/2026, 12:28 PMuse_external_ip: false does not cause the SIP signaling to be rejected. What it affects is which ICE candidates LiveKit advertises for the media path — and that can cause media to fail if external peers can’t reach those candidates.
Details that matter
• Signaling vs media are separate:
◦ SIP signaling (INVITE/OK/ACK) is handled by your SIP server and will succeed if the provider can reach that SIP endpoint.
◦ After signaling, media is established via ICE candidates exchanged in SDP. use_external_ip: false tells LiveKit to advertise internal/host candidates (private IPs).
• Effect of use_external_ip: false for external callers:
◦ External callers can still register/send INVITEs to your SIP server and the call will be accepted at the signaling level.
◦ External callers will receive SDP that contains the advertised candidates. If those are internal/private IPs and the caller cannot route to them, the media path (RTP/DTLS/SRTP) will fail unless an alternate reachable candidate exists.
• How media can still succeed for external callers:
◦ NAT/port-forwarding: If the public IP is forwarded correctly to your LiveKit server and you advertise a public srflx candidate (or your networking provides srflx for the same IP/ports), external media can reach the server.
◦ TURN relay: Configure a TURN server and add it to livekit.yaml. External callers will use TURN relay candidates when direct host/srflx candidates aren’t reachable. TURN on TCP/TLS (443) is most reliable through restrictive networks.
Practical recommendation
• If you set use_external_ip: false to ensure internal agents get reachable host candidates, also configure TURN for external callers so media will be relayed when direct connectivity fails. This keeps signaling working for external SIP providers while ensuring media can flow for both internal agents and external callers.
If you want, I can:
• Provide the minimal TURN configuration snippet for livekit.yaml.
• Show commands to test from an external host whether media can reach your server (or whether TURN is being used).
• Review the relevant lines from your livekit.yaml and sip.yaml (mask any credentials) and point out exactly what to change.
Which would you like next?
, with interactive elements