WebRTC アプリが自分の Wi-Fi では動くのに、ホテルから電話してきたひとりの顧客だけ失敗する。ピアツーピアのメディアには両側をつなぐ経路が必要です。両方が厳しい NAT の内側にいたり、ファイアウォールが UDP を落としたりすると経路がなく、通話はただつながりません。エラーは誰にも見えず、「接続中…」が延々と続くだけです。
TURN サーバーはそうした通話のための予備の経路です。両側がメディアを TURN サーバーに送り、サーバーが中継します。アプリに映像や音声を組み込んでいる、チーム用に Matrix や Jitsi を運用している、ゲームのボイスチャットを作っている——そんなときに必要になります。coturn は定番のオープンソース実装で、軽量です。
どのプランを選ぶか
TURN は CPU とメモリをほとんど使いません。使うのは帯域です。中継されるストリームはどれも、入りと出でサーバーを 2 回通ります。当社プランのトラフィックは 1 Gbit/s で無制限なので、問題になるのは同時に何件の通話を中継するかであって、いくら請求されるかではありません。
| 同時に中継する通話 | プラン |
|---|---|
| 数件:チーム、小さなアプリ | Nano-IP:2 vCPU、1 GB、15 GB(月額 $8) |
| 数十件 | Micro-IP:2 vCPU、2 GB、25 GB(月額 $10) |
| それ以上、またはアプリサーバーと同居させる | Small-IP:4 vCPU、4 GB、35 GB(月額 $16) |
専用の IPv4 付きプランである必要があります。TURN は多数のポートへの着信接続がすべてで、NAT プランが外部から受け付けるのは SSH だけです。当社の専用 IP プランでは公開アドレスがサーバーのインターフェースに直接載っているので、coturn は external-ip の行がなくてもそれを見つけます。サーバーはユーザーの近くに置いてください。ドイツかフィンランドで、空き状況は注文時に表示されます。気になるなら暗号資産で支払えて、KYC は不要です。
開けるポート:UDP と TCP の 3478、中継メディア用に UDP の 49152〜65535、TLS を追加するなら TCP の 5349。
インストールと設定
Ubuntu 24.04 または Debian 12 で root として:
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 の一時ユーザー名/パスワード(coturn が use-auth-secret で解釈する TURN REST API 方式)
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 候補を受け取りました。誤ったパスワードでは 1 件も得られませんでした。自分の構成を同じように試したいなら、このオプションを一時的に有効にしてください。すべての通話を TURN 経由に強制します。
必要なら TLS
UDP を遮断し、素の TCP にもうるさいネットワークがあります。TLS 上の TURN なら、そうしたネットワークの多くを通り抜けられます。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
urls のリストに turns:turn.example.com:5349?transport=tcp を追加します。証明書は短命なので、コピー処理を certbot の deploy hook に入れておいてください。そうしないと、最初の更新で TLS が黙って壊れます。
知っておくとよいこと
- TURN は悪い接続を良くはしません。ホップがひとつ増えるので、必要とする人の近くにサーバーを置いてください。
- SFU(たとえば Jitsi の videobridge)を動かしているなら、それは別の部品です。TURN は、そこへ直接届かないクライアントを助けます。
- セキュリティモデルのすべてはシークレットです。フロントエンドに入れず、万一漏れたら交換してください。
関連記事
- Jitsi Meet 向け VPS:自分のサーバーでビデオ通話
- Matrix Synapse をセルフホスト:Matrix の通話にも TURN サーバーが必要
- 独自 IP のウェブサイト向け VPS:認証情報を発行するアプリ側
- ufw ファイアウォールの設定方法
コメント
まだコメントはありません。最初になりましょう。