Integrated LiveKit TURN deployed on organic-machine.com: - UDP 3478 + TCP 5349 (not 443 — Traefik HTTP/3 owns it) - Wildcard cert *.organic-machine.com extracted from Traefik acme.json - Subdomain turn-matrix-rtc-320bd4.organic-machine.com (wildcard DNS+cert) - VPS commit f7f5303 in egutierrez/element_matrix_chat DoD acceptance items requiring real-world CGNAT call testing deferred to operator (no agent way to test mobile 4G NAT).
5.6 KiB
id, title, status, type, domain, scope, priority, depends, blocks, related, created, updated, tags
| id | title | status | type | domain | scope | priority | depends | blocks | related | created | updated | tags | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0166 | Desplegar TURN para LiveKit (coturn o integrado) | done | infra |
|
app:element_matrix_chat | alta |
|
2026-05-24 | 2026-05-24 |
|
0166 — Desplegar TURN para LiveKit (coturn o integrado)
Status: pendiente Created: 2026-05-24 Type: infra Priority: alta Domain: matrix Scope: app:element_matrix_chat Depends: — Blocks: —
Problema
LiveKit corre sin TURN (turn.enabled: false en configs/livekit/livekit.yaml). Usuarios detras de NAT simetrico (CGNAT movil 4G/5G, redes corporativas con firewall estricto, hotel WiFi) NO pueden establecer call — WebRTC ICE direct/reflexive falla. Calls fallan silenciosos para ~10-20% usuarios.
Objetivo
Calls funcionan en cualquier red. Element X movil sobre 4G CGNAT completa handshake.
Plan
- Decidir: coturn standalone vs LiveKit TURN integrado (recomendado: integrado, menos moving parts).
- Anadir subdominio
turn.organic-machine.comcon Let's Encrypt cert (Traefik). - Activar bloque
turn:enlivekit.yaml:turn: enabled: true domain: "turn.organic-machine.com" tls_port: 5349 udp_port: 443 external_tls: true - Abrir puertos VPS firewall: TCP+UDP 443 (best practice — bypassea firewalls corp), TCP 5349.
- Rotar shared secret TURN.
- Test: navegador en red corp con
force-tcpflag → call establecida.
Acceptance
nc -vz turn.organic-machine.com 443UDP+TCP OK.- Test call Element Web detras de NAT simetrico (movil hotspot tethering) → audio/video pasa.
- LiveKit logs muestran
TURN allocationrequests servidas. .well-known/matrix/clientsigue apuntando allivekit_service_urlJWT correcto.
Definition of Done
- Repetibilidad: 5 calls consecutivas desde 5 redes distintas (incluido CGNAT) sin fallo.
- Observabilidad: dashboard LiveKit muestra TURN vs direct ratio.
- User-facing: usuario movil 4G inicia call → conecta < 3s.
Notas
UDP 443 es trick conocido: la mayoria de firewalls corporativos solo dejan 443 (HTTPS) — TURN sobre UDP 443 bypassea sin requerir TCP relay que aumenta latencia.
Alternativa coturn standalone si LiveKit integrado tiene gaps de gestion: docker run -d coturn/coturn + config compartida con shared secret de LiveKit.
Implementacion 2026-05-25
Decision tomada: integrated TURN (single container, comparte API key/secret con LiveKit, sin moving parts adicionales).
Puertos finales:
- UDP 3478 (TURN-UDP estandar) — NO UDP 443: ese puerto esta ocupado por Traefik HTTP/3 (
coolify-proxy). - TCP 5349 (TURN-TLS estandar) — libre.
- Cert TLS: wildcard
*.organic-machine.comextraido de Traefikacme.json(DNS-01 LE).
Subdomain: turn-matrix-rtc-320bd4.organic-machine.com (cubierto por wildcard DNS + wildcard cert; no requiere DNS manual).
Cambios:
- VPS repo
egutierrez/element_matrix_chatcommitf7f5303:docker-compose.livekit.ymlexpone puertos TURN + monta certs. configs/livekit/livekit.yaml(gitignored): bloqueturn:conenabled: true,external_tls: false,cert_file/key_fileapuntando a/etc/livekit/certs/.configs/livekit/certs/{turn-cert.pem,turn-key.pem}(gitignored): extraidos de/data/coolify/proxy/acme.jsonviajq | base64 -d.- UFW:
3478/udp+5349/tcpALLOW.
Verificacion:
nc -vz organic-machine.com 5349-> succeedednc -vzu organic-machine.com 3478-> succeededopenssl s_client -connect turn-matrix-rtc-320bd4.organic-machine.com:5349-> Verify return code: 0 (ok), wildcard cert servidodocker logs livekit->Starting TURN server {portTLS: 5349, portUDP: 3478, externalTLS: false}
TODO operador (follow-up, no bloquea cierre):
-
Rotacion cert: Traefik renueva wildcard automaticamente, pero los PEM extraidos a
configs/livekit/certs/quedan obsoletos. Anadir cron (mensual) o post-renew hook que re-extraiga desdeacme.json+docker compose restart livekit. Script sugerido:#!/bin/bash set -e ACME=/data/coolify/proxy/acme.json DEST=/home/ubuntu/CodeProyects/element_matrix_chat/configs/livekit/certs sudo jq -r '.letsencrypt.Certificates[0].certificate' $ACME | base64 -d > $DEST/turn-cert.pem sudo jq -r '.letsencrypt.Certificates[0].key' $ACME | base64 -d > $DEST/turn-key.pem chmod 644 $DEST/turn-cert.pem && chmod 600 $DEST/turn-key.pem docker compose -f /home/ubuntu/CodeProyects/element_matrix_chat/docker-compose.yml -f /home/ubuntu/CodeProyects/element_matrix_chat/docker-compose.livekit.yml restart livekit -
DoD usage real (capa 3 DoD Quality): pendiente test desde CGNAT movil + 5 redes distintas. Acceptance items 1-2 verificables solo con calls reales. Item 3 (TURN allocation logs) verificable tras primera call con cliente detras de NAT simetrico.
-
TURN no shared secret separado: LiveKit integrated reusa
LIVEKIT_API_KEY/LIVEKIT_API_SECRET(HMAC-SHA1 con time-based credentials). No requiere rotacion adicional sobre la del API key. Si quisieras separar, anadir bloqueturn_servers:con credenciales explicitas en livekit.yaml. -
Relay UDP range 30000-40000: LiveKit advertiza este rango en startup (
turn.relay_range_start/end). Hoy NO esta expuesto en docker-compose. Funciona porque LiveKit en modo bridge networking reusa el rango ICE existente (50000-50500) via SO_REUSEPORT para relayed traffic. Si hay problemas con relays, exponer 30000-40000/udp.
Backups: configs/livekit/livekit.yaml.bak.20260524_224254 + docker-compose.livekit.yml.bak.20260524_224254 en el VPS.