Tu app WebRTC funciona en tu wifi y falla justo al cliente que llama desde un hotel. El audio y vídeo punto a punto necesitan un camino entre los dos lados; cuando ambos están detrás de un NAT estricto, o un cortafuegos bloquea UDP, no hay camino y la llamada simplemente no conecta. Nadie ve un error: solo un «conectando…» interminable.
Un servidor TURN es el plan B para esas llamadas: los dos lados le envían su audio y vídeo, y él los retransmite. Si estás metiendo vídeo o voz en una app, montando Matrix o Jitsi para un equipo o haciendo chat de voz para un juego, necesitas uno. coturn es la implementación de código abierto de referencia, y es ligera.
Qué plan
TURN gasta poca CPU y memoria. Lo que usa es ancho de banda: cada flujo retransmitido pasa dos veces por el servidor, de entrada y de salida. En nuestros planes el tráfico es ilimitado a 1 Gbit/s, así que la pregunta es cuántas llamadas retransmitidas hay a la vez, no cuánto te van a cobrar.
| Llamadas retransmitidas a la vez | Plan |
|---|---|
| Unas pocas: un equipo, una app pequeña | Nano-IP: 2 vCPU, 1 GB, 15 GB ($8/mes) |
| Decenas | Micro-IP: 2 vCPU, 2 GB, 25 GB ($10/mes) |
| Más, o TURN junto a tu servidor de aplicación | Small-IP: 4 vCPU, 4 GB, 35 GB ($16/mes) |
Tiene que ser un plan con su propia IPv4: TURN consiste en conexiones entrantes en muchos puertos, y los planes NAT solo aceptan SSH desde fuera. En nuestros planes con IP dedicada, la dirección pública está directamente en la interfaz del servidor, así que coturn la encuentra sin línea external-ip. Pon el servidor cerca de tus usuarios: Alemania o Finlandia; la disponibilidad se ve al hacer el pedido. Pago con cripto, sin KYC, si eso te importa.
Puertos que abrir: 3478 UDP y TCP, 49152–65535 UDP para el audio y vídeo retransmitidos, y 5349 TCP si añades TLS.
Instalación y configuración
Como root en Ubuntu 24.04 o Debian 12:
apt install -y coturn
ip -4 addr # tu IP pública debería aparecer en la interfaz
Escribe la configuración con un secreto compartido nuevo:
SECRET=$(openssl rand -hex 32)
cat > /etc/turnserver.conf <<EOF
listening-port=3478
fingerprint
use-auth-secret
static-auth-secret=$SECRET
realm=turn.example.com
min-port=49152
max-port=65535
no-cli
no-multicast-peers
denied-peer-ip=0.0.0.0-0.255.255.255
denied-peer-ip=10.0.0.0-10.255.255.255
denied-peer-ip=100.64.0.0-100.127.255.255
denied-peer-ip=127.0.0.0-127.255.255.255
denied-peer-ip=169.254.0.0-169.254.255.255
denied-peer-ip=172.16.0.0-172.31.255.255
denied-peer-ip=192.168.0.0-192.168.255.255
EOF
echo "$SECRET" # tu backend necesita este valor
systemctl restart coturn
ufw allow 22/tcp && ufw allow 3478 && ufw allow 49152:65535/udp && ufw enable
No te saltes las líneas denied-peer-ip. Sin ellas, cualquiera con credenciales válidas puede usar tu relay para llegar a direcciones privadas, incluidos servicios del lado interno del propio servidor. Lo comprobamos: con estas líneas, un relay hacia una dirección privada se rechaza con «403 Forbidden IP».
Credenciales que caducan
Con use-auth-secret, coturn acepta un nombre de usuario que contiene una hora de caducidad y una contraseña que es un HMAC de ese nombre. Las genera tu backend; el secreto nunca sale del lado del servidor. En Node.js:
const crypto = require("crypto");
// Usuario/contraseña TURN temporales (el esquema TURN REST API que coturn entiende con use-auth-secret).
function turnCredentials(secret, user, ttlSeconds = 3600) {
const username = `${Math.floor(Date.now() / 1000) + ttlSeconds}:${user}`;
const credential = crypto.createHmac("sha1", secret).update(username).digest("base64");
return { username, credential };
}
console.log(JSON.stringify(turnCredentials(process.env.TURN_SECRET, "alice")));
En el navegador, pide a tu backend un par nuevo y pásalo a RTCPeerConnection:
const { username, credential } = await fetch("/api/turn-credentials").then((r) => r.json());
const pc = new RTCPeerConnection({
iceServers: [
{ urls: "stun:turn.example.com:3478" },
{
urls: ["turn:turn.example.com:3478?transport=udp", "turn:turn.example.com:3478?transport=tcp"],
username,
credential,
},
],
});
Pruébalo
Desde el servidor, con el mismo esquema usando openssl (produce exactamente la misma contraseña que el código de Node.js; lo comparamos):
U="$(( $(date +%s) + 3600 )):test"
P=$(echo -n "$U" | openssl dgst -binary -sha1 -hmac "$SECRET" | base64)
turnutils_uclient -y -n 10 -u "$U" -w "$P" YOUR_SERVER_IP
Al final quieres ver «Total lost packets 0». Con una contraseña incorrecta, el cliente se queda en «Cannot complete Allocation»: es el servidor haciendo su trabajo.
También lo comprobamos desde un Chromium real: con iceTransportPolicy: "relay" y credenciales frescas, el navegador obtuvo candidatos relay del servidor; con una contraseña mala, ninguno. Si quieres probar tu propia configuración igual, activa esa opción un momento: obliga a que todas las llamadas pasen por TURN.
TLS, si lo necesitas
Algunas redes bloquean UDP y son quisquillosas con el TCP plano; TURN sobre TLS atraviesa más de ellas. Consigue un certificado para turn.example.com (certbot vale). Sus archivos solo los puede leer root, mientras que coturn se ejecuta como el usuario turnserver, así que dale a coturn su propia copia:
mkdir -p /etc/coturn
cp /etc/letsencrypt/live/turn.example.com/fullchain.pem /etc/letsencrypt/live/turn.example.com/privkey.pem /etc/coturn/
chown turnserver: /etc/coturn/*.pem && chmod 640 /etc/coturn/privkey.pem
cat >> /etc/turnserver.conf <<'EOF'
tls-listening-port=5349
cert=/etc/coturn/fullchain.pem
pkey=/etc/coturn/privkey.pem
EOF
systemctl restart coturn && ufw allow 5349/tcp
Añade turns:turn.example.com:5349?transport=tcp a la lista urls. Los certificados duran poco, así que mete la copia en un deploy hook de certbot; si no, TLS se romperá sin avisar en la primera renovación.
Conviene saber
- TURN no convierte una mala conexión en buena. Añade un salto, así que coloca el servidor cerca de quienes lo necesitan.
- Si usas un SFU —el videobridge de Jitsi, por ejemplo—, es una pieza aparte. TURN ayuda a los clientes que no pueden llegar a él directamente.
- El secreto es todo el modelo de seguridad. Mantenlo fuera del frontend y cámbialo si alguna vez se filtra.
Relacionado
- VPS para Jitsi Meet: videollamadas en tu propio servidor
- Matrix Synapse autoalojado: las llamadas de Matrix también necesitan un servidor TURN
- VPS para un sitio web con IP propia: la app que reparte las credenciales
- Cómo configurar un cortafuegos ufw
Comentarios
Aún no hay comentarios. Sé el primero.