跳到主要内容

coturn部署实战

coturn 部署实战

coturn 是 WebRTC 生态里事实上的开源 STUN/TURN 服务器。这篇文档给出一份能直接抄走、能上线、能监控的部署方案,覆盖配置、短期凭据、TLS、TCP 兜底、容器化和常见排障。

不讲 STUN/TURN 协议本身(见 ICE全流程.md),只讲怎么让它在生产网络上跑稳。

1. 部署前的关键决策

决策选项推荐
监听协议UDP / TCP / TLS / DTLS至少 UDP/3478 + TLS/443
公网 IP单 IP / 多 IP单 IP 足够,多 IP 仅大规模需要
凭据机制长期密码 / 短期 HMAC必须用短期 HMAC
TLS 证书Let's Encrypt / 商用Let's Encrypt 足够
部署形态裸机 / Docker / K8s裸机或 Docker,K8s 上 HostNetwork
监控Prometheus exportercoturn 自带 telnet 接口 + node-exporter

最大坑:很多人把 coturn 跑在 Docker 默认网络里,结果 relay-ip 拿到容器内网 IP,所有客户端连不上中继地址。一定要 --network=host 或者显式配 external-ip

2. 最小可用配置(生产基线)

/etc/turnserver.conf(注释行为决策理由,可保留):

# === 监听 ===
listening-port=3478
tls-listening-port=5349
# 同时监听 443 给企业防火墙环境兜底
alt-tls-listening-port=443

# 本机所有 IP;多网卡时显式指定
listening-ip=0.0.0.0
relay-ip=0.0.0.0

# 关键:客户端看到的公网 IP,NAT 后必填
external-ip=203.0.113.10

# === 中继端口范围 ===
min-port=49152
max-port=65535

# === 鉴权:短期 HMAC ===
lt-cred-mech
use-auth-secret
static-auth-secret=REPLACE_WITH_64_CHAR_RANDOM_HEX
realm=turn.example.com

# === TLS ===
cert=/etc/letsencrypt/live/turn.example.com/fullchain.pem
pkey=/etc/letsencrypt/live/turn.example.com/privkey.pem
# 禁用过时协议
no-tlsv1
no-tlsv1_1
cipher-list="ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256"

# === 安全 ===
no-multicast-peers
no-cli # 关闭 5766 管理 CLI(除非内网用)
no-loopback-peers # 禁止中继到 127.0.0.0/8
denied-peer-ip=10.0.0.0-10.255.255.255 # 禁止中继到内网,防 SSRF
denied-peer-ip=172.16.0.0-172.31.255.255
denied-peer-ip=192.168.0.0-192.168.255.255
denied-peer-ip=169.254.0.0-169.254.255.255
# 但允许中继到本机外网(如果服务器自己也跑 SFU)
allowed-peer-ip=203.0.113.10

# === 性能 ===
fingerprint
stale-nonce=600
no-stdout-log
log-file=/var/log/turn.log
syslog
simple-log

# === 限制 ===
user-quota=12 # 每用户最多 12 个分配
total-quota=1200 # 全局最多 1200 个分配
max-bps=0 # 不限速;按需打开

启动:

turnserver -c /etc/turnserver.conf --daemon

systemd unit(Ubuntu 已自带 /lib/systemd/system/coturn.service):

systemctl enable --now coturn
systemctl status coturn

3. 端口与防火墙

端口协议用途必开
3478UDPSTUN/TURN 主入口
3478TCPTURN over TCP
5349TCPTURN over TLS
443TCPTURN over TLS(兜底,最关键)
49152-65535UDP中继分配的端口范围

iptables 示例:

iptables -A INPUT -p udp --dport 3478 -j ACCEPT
iptables -A INPUT -p tcp --dport 3478 -j ACCEPT
iptables -A INPUT -p tcp --dport 5349 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
iptables -A INPUT -p udp --dport 49152:65535 -j ACCEPT

云厂商安全组也要放行同样规则。AWS / 阿里云 / GCP 上很多 TURN 失败案例都是因为安全组没放 49152-65535 UDP 范围。

4. 短期凭据:必须自己生成,不要让前端拼

TURN 长期凭据(用户名/密码硬编码)等于免费给全网当代理用。生产必须用 HMAC 短期凭据REST API for Access to TURN Services):

import crypto from 'crypto';

function makeTurnCredentials(userId, secret = process.env.TURN_SECRET, ttl = 3600) {
const unixTimestamp = Math.floor(Date.now() / 1000) + ttl;
const username = `${unixTimestamp}:${userId}`;
const hmac = crypto.createHmac('sha1', secret);
hmac.update(username);
const credential = hmac.digest('base64');
return {
urls: [
'stun:turn.example.com:3478',
'turn:turn.example.com:3478?transport=udp',
'turn:turn.example.com:3478?transport=tcp',
'turns:turn.example.com:443?transport=tcp',
],
username,
credential,
};
}

// 信令服务在 join 成功时下发
ws.send(JSON.stringify({
type: 'joined',
iceServers: [makeTurnCredentials(userId)],
}));

coturn 端配 use-auth-secret + static-auth-secret=<同一个 secret> 即可,无需在 coturn 上预创建用户

4.1 凭据过期续期

如果通话超过 ttl(默认 1 小时),中继连接会被 TURN 服务器断开。两种处理:

  1. 延长 ttl:会议场景设 7200s,足够大多数通话。
  2. 续期 + ICE Restart:信令周期性下发新凭据,前端调 pc.setConfiguration({ iceServers: [...new] }) 然后 pc.restartIce()
setInterval(async () => {
const fresh = await fetch('/api/turn-credentials').then(r => r.json());
pc.setConfiguration({ ...pc.getConfiguration(), iceServers: fresh });
pc.restartIce();
}, 50 * 60 * 1000); // ttl=3600 时提前 10 分钟换

5. TLS:443 端口与 TCP 兜底的意义

很多企业出口防火墙只放 80/443 TCP。在这些环境里:

  • turn:host:3478?transport=udp ❌ 被拦
  • turn:host:3478?transport=tcp ❌ 被拦
  • turns:host:443?transport=tcp ✅ 跟正常 HTTPS 一样

伪装成 HTTPS 流量是 TURN 唯一能在严格企业网络存活的方式。turns over 443 必须配。

5.1 Let's Encrypt 证书续期

# 首次签发(需要域名指向本机)
certbot certonly --standalone -d turn.example.com

# coturn 不支持热重载证书,需要在续期后 reload
cat > /etc/letsencrypt/renewal-hooks/deploy/coturn.sh <<'EOF'
#!/bin/bash
systemctl reload coturn || systemctl restart coturn
EOF
chmod +x /etc/letsencrypt/renewal-hooks/deploy/coturn.sh

coturn 5.x 以后 reload 等同于 SIGUSR2,会重新加载证书但不断开现有连接。

6. Docker / Docker Compose 部署

docker-compose.yml

services:
coturn:
image: coturn/coturn:4.6
network_mode: host # 必须 host,否则 relay-ip 全错
restart: unless-stopped
volumes:
- ./turnserver.conf:/etc/coturn/turnserver.conf:ro
- /etc/letsencrypt:/etc/letsencrypt:ro
- coturn-logs:/var/log
command: ["-c", "/etc/coturn/turnserver.conf"]
healthcheck:
test: ["CMD", "turnutils_uclient", "-v", "-y", "127.0.0.1"]
interval: 30s
timeout: 10s
retries: 3

volumes:
coturn-logs:

不要ports: 映射端口范围 49152-65535——Docker 给每个端口建一条 iptables 规则,几万条规则会让宿主机 CPU 直接爆掉。只能用 network_mode: host

7. 部署验证:3 条必跑的命令

7.1 turnutils_uclient(coturn 官方测试工具)

turnutils_uclient -v -y -u username -w credential turn.example.com -p 3478
# 看到 "All connections were OK" 即正常

7.2 trickle-ice 在线测试

打开 https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/

  1. 填入 turn:turn.example.com:3478、username、credential
  2. 点 Add Server,然后 Gather candidates
  3. 必须看到一个 relay 类型的候选

如果没有 relay 候选 → TURN 不通。看下一节排障。

7.3 用 tcpdump 看 STUN 包

tcpdump -i any -n 'udp port 3478' -c 20
# 客户端发起后应看到 STUN Binding Request 与 Allocate

8. 常见排障路径

现象大概率原因验证方法
拿到 srflx 但没 relayTURN 鉴权失败/var/log/turn.log 有没有 401
拿到 relay 但通话还失败中继端口被防火墙拦抓包看 49152-65535
relay 地址是内网 IPexternal-ip 没配external-ip=公网IP
间歇性失败单实例 CPU/带宽打满htop + iftop 看实例负载
通话 1 小时后掉短期凭据过期延长 ttl 或主动续期
TLS 握手失败证书过期 / cipher 不匹配openssl s_client -connect host:443
Docker 部署连不上没用 host 网络network_mode: host

8.1 看 coturn 日志的方式

tail -f /var/log/turn.log | grep -E 'session|allocation|error'

关键日志:

session 000000000000000001: realm <turn.example.com> user <1719000000:alice>: incoming packet ALLOCATE processed
session 000000000000000001: usage: realm=<turn.example.com>, username=<...>, rcvp=12, rcvb=1450, sentp=8, sentb=980

rcvp/rcvb/sentp/sentb 是包数和字节数,可以判断真实流量。

9. 监控与容量规划

9.1 Prometheus 指标

coturn 5.x 没有内置 Prometheus exporter。常见方案:

  1. prom-coturn-exporter(社区项目,解析 telnet CLI 输出)。
  2. 自写脚本:通过 turnutils_stats 拿计数器,转成 Prometheus 格式。

最简版:

turnutils_stats -s telnet -p 5766 -P admin_password | \
awk '/total_sessions/ {print "coturn_sessions_total " $2}'

9.2 容量经验值

实例规格并发会话峰值带宽
2C4G200-500100 Mbps
4C8G800-1500500 Mbps
8C16G2000-40001 Gbps

瓶颈是带宽不是 CPU。每路 720p 视频中继 ≈ 1.5 Mbps × 2 方向 = 3 Mbps。一台 1Gbps 实例理论最多 300 路。

9.3 多实例负载均衡

不要用 L4 LB 做 TCP 端口的 round-robin——TURN 的 UDP 中继有状态,分到不同实例就断连。常见做法:

  • DNS 轮询多个 TURN 域名,前端把它们都塞进 iceServers
  • 浏览器内部对 iceServers 数组并行尝试,会自动用第一个能通的
iceServers: [
{ urls: 'turn:turn1.example.com:3478?transport=udp', ... },
{ urls: 'turn:turn2.example.com:3478?transport=udp', ... },
{ urls: 'turns:turn1.example.com:443?transport=tcp', ... },
{ urls: 'turns:turn2.example.com:443?transport=tcp', ... },
]

地理就近:用 GeoDNS 把用户路由到最近 TURN 实例,跨大洲走中继的延迟比就近 +100ms 还多。

10. 安全清单

✅ 用短期 HMAC 凭据,不用长期密码 ✅ denied-peer-ip 屏蔽所有内网段(防 SSRF) ✅ no-multicast-peersno-loopback-peers ✅ TLS 1.2 起步,禁 SSLv3 / TLS 1.0 / 1.1 ✅ no-cli 或 CLI 端口 (5766) 仅本机访问 ✅ 限制 user-quota / total-quota 防被刷 ✅ 日志脱敏:不要把 username(含 userId)发到外部日志系统 ❌ 不要把 static-auth-secret 写进前端代码 ❌ 不要让 TURN 服务器同时跑业务(被打挂后业务也死)

11. 反模式

反模式后果替代
前端硬编码 TURN 长期密码全网当代理用,被滥用服务端签发短期凭据
只开 UDP/3478企业网用户连不上同时开 TLS/443 TCP
Docker 用桥接网络中继地址全错network_mode: host
不配 external-ipNAT 后 relay 地址给的是内网必填公网 IP
不限制 denied-peer-ip被当跳板攻击内网(SSRF)黑名单所有 RFC1918
单实例扛全量没冗余,挂了全断多实例 + 客户端并行
用 L4 LB 分发 UDP同会话被分到不同实例客户端侧并行 iceServers
证书续期后没 reloadTLS 用旧证书直到重启renewal-hooks 自动 reload

12. 权威资料