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 exporter | coturn 自带 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. 端口与防火墙
| 端口 | 协议 | 用途 | 必开 |
|---|---|---|---|
| 3478 | UDP | STUN/TURN 主入口 | ✅ |
| 3478 | TCP | TURN over TCP | ✅ |
| 5349 | TCP | TURN over TLS | ✅ |
| 443 | TCP | TURN over TLS(兜底,最关键) | ✅ |
| 49152-65535 | UDP | 中继分配的端口范围 | ✅ |
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 服务器断开。两种处理:
- 延长 ttl:会议场景设 7200s,足够大多数通话。
- 续期 + 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/:
- 填入
turn:turn.example.com:3478、username、credential - 点 Add Server,然后 Gather candidates
- 必须看到一个
relay类型的候选
如果没有 relay 候选 → TURN 不通。看下一节排障。
7.3 用 tcpdump 看 STUN 包
tcpdump -i any -n 'udp port 3478' -c 20
# 客户端发起后应看到 STUN Binding Request 与 Allocate
8. 常见排障路径
| 现象 | 大概率原因 | 验证方法 |
|---|---|---|
拿到 srflx 但没 relay | TURN 鉴权失败 | 看 /var/log/turn.log 有没有 401 |
拿到 relay 但通话还失败 | 中继端口被防火墙拦 | 抓包看 49152-65535 |
| relay 地址是内网 IP | external-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。常见方案:
- prom-coturn-exporter(社区项目,解析 telnet CLI 输出)。
- 自写脚本:通过
turnutils_stats拿计数器,转成 Prometheus 格式。
最简版:
turnutils_stats -s telnet -p 5766 -P admin_password | \
awk '/total_sessions/ {print "coturn_sessions_total " $2}'
9.2 容量经验值
| 实例规格 | 并发会话 | 峰值带宽 |
|---|---|---|
| 2C4G | 200-500 | 100 Mbps |
| 4C8G | 800-1500 | 500 Mbps |
| 8C16G | 2000-4000 | 1 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-peers、no-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-ip | NAT 后 relay 地址给的是内网 | 必填公网 IP |
不限制 denied-peer-ip | 被当跳板攻击内网(SSRF) | 黑名单所有 RFC1918 |
| 单实例扛全量 | 没冗余,挂了全断 | 多实例 + 客户端并行 |
| 用 L4 LB 分发 UDP | 同会话被分到不同实例 | 客户端侧并行 iceServers |
| 证书续期后没 reload | TLS 用旧证书直到重启 | renewal-hooks 自动 reload |
12. 权威资料
- coturn 官方仓库 + Wiki: https://github.com/coturn/coturn/wiki
- coturn
turnserver.conf全字段说明: https://github.com/coturn/coturn/blob/master/examples/etc/turnserver.conf - IETF RFC 8656 TURN: https://www.rfc-editor.org/rfc/rfc8656
- IETF RFC 7065 TURN URI: https://www.rfc-editor.org/rfc/rfc7065
- IETF draft REST API for Access to TURN Services: https://datatracker.ietf.org/doc/html/draft-uberti-rtcweb-turn-rest-00
- WebRTC Trickle ICE 在线测试: https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/
- 核对日期:2026-06-22