−25%

на годовую оплату Windows, до 31.10. К тарифам

EQVPS
Начать

VPS для TURN-сервера (coturn) под WebRTC

Звонки, которые не проходят через строгий NAT, спасает TURN-релей. coturn на сервере со своим IPv4: порты, временные учётные данные, проверенный конфиг и тариф.

Ваше WebRTC-приложение работает у вас по Wi-Fi и не работает у того самого клиента, который звонит из гостиницы. Медиа между участниками нужен путь; когда обе стороны за строгим NAT или файрвол режет UDP, пути нет, и звонок просто не соединяется. Ошибки никто не видит — только бесконечное «соединение…».

TURN-сервер — запасной вариант для таких звонков: обе стороны отправляют медиа на него, а он ретранслирует. Если вы встраиваете видео или голос в приложение, держите Matrix или Jitsi для команды или делаете голосовой чат для игры — он вам нужен. coturn — стандартная открытая реализация, и она лёгкая.

Какой тариф

CPU и память TURN почти не трогает. Тратит он полосу: каждый ретранслируемый поток идёт через сервер дважды, туда и обратно. Трафик на наших тарифах безлимитный на 1 Гбит/с, так что вопрос не в счёте, а в том, сколько ретранслируемых звонков идёт одновременно.

Звонков через релей одновременноТариф
Несколько — команда, небольшое приложениеNano-IP — 2 vCPU, 1 ГБ, 15 ГБ ($8/мес)
ДесяткиMicro-IP — 2 vCPU, 2 ГБ, 25 ГБ ($10/мес)
Больше, или TURN рядом с сервером приложенияSmall-IP — 4 vCPU, 4 ГБ, 35 ГБ ($16/мес)

Тариф обязательно со своим IPv4: TURN — это сплошь входящие соединения на множество портов, а NAT-тарифы снаружи принимают только SSH. На наших тарифах с выделенным IP публичный адрес висит прямо на интерфейсе сервера, так что coturn находит его сам, строка external-ip не нужна. Ставьте сервер поближе к пользователям: Германия или Финляндия, доступность видна при заказе. Оплата криптой без KYC — если для вас это важно.

Какие порты открыть: 3478 по UDP и TCP, 49152–65535 по UDP для ретранслируемого медиа и 5349 по TCP, если добавите TLS.

Установка и настройка

От root на Ubuntu 24.04 или Debian 12:

apt install -y coturn
ip -4 addr    # публичный IP должен быть в списке на интерфейсе

Пишем конфиг со свежим общим секретом:

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"    # это значение нужно вашему бэкенду
systemctl restart coturn
ufw allow 22/tcp && ufw allow 3478 && ufw allow 49152:65535/udp && ufw enable

Строки denied-peer-ip не пропускайте. Без них любой, у кого есть действующие учётные данные, может через ваш релей достучаться до частных адресов — в том числе до сервисов на внутренней стороне самого сервера. Мы проверили: с этими строками релей на частный адрес отклоняется с «403 Forbidden IP».

Учётные данные со сроком годности

С use-auth-secret coturn принимает имя пользователя, в котором зашито время истечения, и пароль — HMAC от этого имени. Генерирует их ваш бэкенд, секрет никогда не уходит со стороны сервера. На Node.js:

const crypto = require("crypto");

// Временные логин/пароль для TURN (схема TURN REST API, которую coturn понимает через 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")));

В браузере запрашиваете у бэкенда свежую пару и отдаёте её в 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,
    },
  ],
});

Проверка

С самого сервера, по той же схеме через openssl (он даёт ровно тот же пароль, что и код на Node.js, — мы сравнивали):

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

В конце должно быть «Total lost packets 0». С неверным паролем клиент останавливается на «Cannot complete Allocation» — значит, сервер делает свою работу.

Ещё мы проверили из настоящего Chromium: с iceTransportPolicy: "relay" и свежими учётными данными браузер получил от сервера relay-кандидатов, с неверным паролем — ни одного. Хотите так же проверить свою установку — включите эту опцию ненадолго: она заставляет любой звонок идти через TURN.

TLS, если нужен

Некоторые сети режут UDP и придирчиво относятся к обычному TCP; TURN поверх TLS проходит через большее их число. Получите сертификат на turn.example.com (подойдёт certbot). Его файлы может читать только root, а coturn работает от пользователя turnserver, поэтому дайте coturn собственную копию:

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

Добавьте turns:turn.example.com:5349?transport=tcp в список urls. Сертификаты живут недолго, поэтому копирование положите в deploy hook certbot — иначе TLS тихо сломается при первом же продлении.

Полезно знать

  • TURN не делает плохое соединение хорошим. Он добавляет ещё один участок пути, так что ставьте сервер ближе к тем, кому он нужен.
  • Если у вас SFU — например, видеомост Jitsi, — это отдельная часть. TURN помогает тем клиентам, которые не могут достучаться до неё напрямую.
  • Секрет — это вся модель безопасности. Держите его подальше от фронтенда и меняйте, если он когда-нибудь утечёт.

По теме

Готовы развернуть? Оплата криптой, без KYC — сервер за минуту.

Развернуть →

Частые вопросы

Зачем WebRTC-звонкам вообще TURN-сервер?

WebRTC пытается соединить участников напрямую, и обычно это получается. Но когда обе стороны за симметричным NAT или за файрволом, который режет UDP — гостиничный Wi-Fi, офисные сети, некоторые мобильные операторы, — прямого пути нет, и звонок молча не соединяется. TURN-сервер ретранслирует медиа именно для таких звонков; обычно называют долю примерно в один звонок из десяти.

Можно ли поднять TURN на NAT-тарифе?

Нет. Клиенты подключаются входящими соединениями на UDP и TCP 3478 и на диапазон портов релея, а NAT-тариф снаружи принимает только ваш SSH-порт. Нужен тариф со своим IPv4.

Сколько он съест трафика?

Каждый ретранслируемый поток проходит через сервер дважды — пришёл от одной стороны, ушёл к другой. Видеозвонок один на один на 1–2 Мбит/с в каждую сторону — это несколько Мбит/с на сервере. CPU и RAM этого почти не замечают; настоящий потолок — порт на 1 Гбит/с.

Почему бы просто не прописать в приложении постоянные логин и пароль?

Потому что всё, что лежит в JavaScript браузера, публично, а TURN-сервер с известным паролем — бесплатный релей для всего интернета. С use-auth-secret ваш бэкенд выдаёт учётные данные со сроком годности, подписанные секретом, которого браузер не видит.

Нужен ли TLS на порту 5349?

Помогает в сетях, которые режут UDP и придирчиво относятся к обычному TCP. Добавляйте его, когда обычный TURN уже работает: дайте coturn сертификат на имя TURN-сервера и откройте 5349/tcp. Релей через TLS мы тоже проверили.

Комментарии

Пока нет комментариев. Будьте первым.

Оставить комментарий

Комментарии проходят модерацию перед публикацией.