coturn TURN 服务器完整指南
大约 28 分钟
coturn TURN 服务器完整指南
适用于 WebRTC 视频会议系统的 NAT 穿透解决方案
场景说明:WebRTC 搭建的视频会议,因为网络问题,可能存在无法通讯的情况,所以需要NAT 穿透 的方式建立通道
📑 目录
1. 核心概念
1.1 什么是 STUN/TURN?
STUN (Session Traversal Utilities for NAT)
客户端 → [我的公网IP是什么?] → STUN 服务器
← [你的公网IP是 x.x.x.x] ←
作用:
- ✅ 帮助客户端发现自己的公网 IP
- ✅ 用于 P2P 打洞(NAT 穿透)
- ✅ 不转发数据,只提供 IP 信息
TURN (Traversal Using Relays around NAT)
客户端 A → TURN 服务器 → 客户端 B
作用:
- ✅ 当 P2P 无法建立时中继数据
- ✅ 解决对称 NAT 问题
- ✅ 保证连接成功率 100%
1.2 ICE 连接类型优先级
1️⃣ HOST (本地直连) - 最快,0 延迟
2️⃣ SRFLX (STUN 反射) - 快,低延迟
3️⃣ PRFLX (对等反射/P2P打洞) - 快,低延迟
4️⃣ RELAY (TURN 中继) - 最慢,但最可靠
WebRTC 自动选择最快的可用方式!
1.3 什么时候需要 TURN?
| 场景 | 连接方式 | 是否需要 TURN |
|---|---|---|
| 同一局域网 | HOST 直连 | ❌ 不需要 |
| 有公网 IPv6 | HOST 直连 | ❌ 不需要 |
| 普通 NAT(可打洞) | PRFLX/SRFLX | ❌ 不需要 |
| 对称 NAT | 无法 P2P | ✅ 需要 |
| 跨网段(防火墙隔离) | 无法直连 | ✅ 需要 |
| 企业严格防火墙 | 无法 P2P | ✅ 需要 |
2. 服务器部署
2.1 环境要求
推荐配置:
- 系统:CentOS 7/8 或 Ubuntu 18.04+
- CPU:2 核心+
- 内存:2GB+
- 带宽:10Mbps+(根据并发用户调整)
- 公网 IP:必须
端口要求:
3478(TCP/UDP) - STUN/TURN5349(TCP) - TURNS (TLS)49152-65535(UDP) - 中继端口范围
2.2 安装 coturn
CentOS/RHEL
# 安装 EPEL 源
sudo yum install epel-release -y
# 安装 coturn
sudo yum install coturn -y
# 查看版本
turnserver --version
Ubuntu/Debian
# 更新包列表
sudo apt update
# 安装 coturn
sudo apt install coturn -y
# 启用服务
sudo systemctl enable coturn
2.3 云服务器安全组配置
⚠️ 重要:必须开放的端口
| 端口范围 | 协议 | 方向 | 用途 | 优先级 |
|---|---|---|---|---|
3478 | TCP | 入站 | STUN/TURN 主端口 | ⭐⭐⭐⭐⭐ 必须 |
3478 | UDP | 入站 | STUN/TURN 主端口 | ⭐⭐⭐⭐⭐ 必须 |
5349 | TCP | 入站 | TURNS (TLS) | ⭐⭐⭐⭐ 推荐 |
49152-65535 | UDP | 入站 | TURN 中继端口范围 | ⭐⭐⭐⭐⭐ 必须 |
49152-65535 | TCP | 入站 | TURN TCP 中继(可选) | ⭐⭐ 可选 |

各云服务商配置指南
阿里云 ECS
# 方法 1:通过控制台(推荐)
1. 登录阿里云控制台
2. 进入 ECS 实例 → 安全组
3. 点击"配置规则" → "入方向"
4. 点击"添加规则",依次添加:
规则 1:
端口范围:3478/3478
授权对象:0.0.0.0/0
协议类型:自定义 TCP
优先级:1
描述:TURN TCP
规则 2:
端口范围:3478/3478
授权对象:0.0.0.0/0
协议类型:自定义 UDP
优先级:1
描述:TURN UDP
规则 3:
端口范围:5349/5349
授权对象:0.0.0.0/0
协议类型:自定义 TCP
优先级:1
描述:TURNS
规则 4:
端口范围:49152/65535
授权对象:0.0.0.0/0
协议类型:自定义 UDP
优先级:1
描述:TURN 中继端口
# 方法 2:通过命令行(阿里云 CLI)
aliyun ecs AuthorizeSecurityGroup \
--RegionId cn-hangzhou \
--SecurityGroupId sg-xxxxx \
--IpProtocol tcp \
--PortRange 3478/3478 \
--SourceCidrIp 0.0.0.0/0
aliyun ecs AuthorizeSecurityGroup \
--RegionId cn-hangzhou \
--SecurityGroupId sg-xxxxx \
--IpProtocol udp \
--PortRange 3478/3478 \
--SourceCidrIp 0.0.0.0/0
aliyun ecs AuthorizeSecurityGroup \
--RegionId cn-hangzhou \
--SecurityGroupId sg-xxxxx \
--IpProtocol tcp \
--PortRange 5349/5349 \
--SourceCidrIp 0.0.0.0/0
aliyun ecs AuthorizeSecurityGroup \
--RegionId cn-hangzhou \
--SecurityGroupId sg-xxxxx \
--IpProtocol udp \
--PortRange 49152/65535 \
--SourceCidrIp 0.0.0.0/0
腾讯云 CVM
# 方法 1:通过控制台
1. 登录腾讯云控制台
2. 云服务器 → 安全组
3. 选择对应安全组 → 入站规则 → 添加规则
类型:自定义
来源:0.0.0.0/0
协议端口:TCP:3478
策略:允许
类型:自定义
来源:0.0.0.0/0
协议端口:UDP:3478
策略:允许
类型:自定义
来源:0.0.0.0/0
协议端口:TCP:5349
策略:允许
类型:自定义
来源:0.0.0.0/0
协议端口:UDP:49152-65535
策略:允许
# 方法 2:通过命令行(腾讯云 CLI)
tccli vpc CreateSecurityGroupPolicies \
--SecurityGroupId sg-xxxxx \
--SecurityGroupPolicySet.Ingress.0.Protocol TCP \
--SecurityGroupPolicySet.Ingress.0.Port 3478 \
--SecurityGroupPolicySet.Ingress.0.CidrBlock 0.0.0.0/0 \
--SecurityGroupPolicySet.Ingress.0.Action ACCEPT
华为云 ECS
# 通过控制台
1. 华为云控制台 → 弹性云服务器
2. 安全组 → 入方向规则 → 添加规则
协议:TCP
端口:3478
源地址:0.0.0.0/0
描述:TURN TCP
协议:UDP
端口:3478
源地址:0.0.0.0/0
描述:TURN UDP
协议:TCP
端口:5349
源地址:0.0.0.0/0
描述:TURNS
协议:UDP
端口:49152-65535
源地址:0.0.0.0/0
描述:TURN 中继
AWS EC2
# 方法 1:通过控制台
1. EC2 Dashboard → Security Groups
2. 选择安全组 → Inbound rules → Edit inbound rules
Type: Custom TCP
Port range: 3478
Source: 0.0.0.0/0
Description: TURN TCP
Type: Custom UDP
Port range: 3478
Source: 0.0.0.0/0
Description: TURN UDP
Type: Custom TCP
Port range: 5349
Source: 0.0.0.0/0
Description: TURNS
Type: Custom UDP
Port range: 49152-65535
Source: 0.0.0.0/0
Description: TURN Relay
# 方法 2:通过 AWS CLI
aws ec2 authorize-security-group-ingress \
--group-id sg-xxxxx \
--protocol tcp \
--port 3478 \
--cidr 0.0.0.0/0
aws ec2 authorize-security-group-ingress \
--group-id sg-xxxxx \
--protocol udp \
--port 3478 \
--cidr 0.0.0.0/0
aws ec2 authorize-security-group-ingress \
--group-id sg-xxxxx \
--protocol tcp \
--port 5349 \
--cidr 0.0.0.0/0
aws ec2 authorize-security-group-ingress \
--group-id sg-xxxxx \
--protocol udp \
--port 49152-65535 \
--cidr 0.0.0.0/0
Azure VM
# 通过 Azure CLI
az network nsg rule create \
--resource-group myResourceGroup \
--nsg-name myNSG \
--name Allow-TURN-TCP \
--protocol tcp \
--priority 100 \
--destination-port-ranges 3478 \
--access Allow
az network nsg rule create \
--resource-group myResourceGroup \
--nsg-name myNSG \
--name Allow-TURN-UDP \
--protocol udp \
--priority 101 \
--destination-port-ranges 3478 \
--access Allow
az network nsg rule create \
--resource-group myResourceGroup \
--nsg-name myNSG \
--name Allow-TURNS \
--protocol tcp \
--priority 102 \
--destination-port-ranges 5349 \
--access Allow
az network nsg rule create \
--resource-group myResourceGroup \
--nsg-name myNSG \
--name Allow-TURN-Relay \
--protocol udp \
--priority 103 \
--destination-port-ranges 49152-65535 \
--access Allow
本地防火墙配置
目前云服务不确定是否需要这样额外配置,最好配置上
CentOS/RHEL (firewalld)
# 1. 检查防火墙状态
sudo systemctl status firewalld
# 2. 添加端口规则
sudo firewall-cmd --permanent --add-port=3478/tcp
sudo firewall-cmd --permanent --add-port=3478/udp
sudo firewall-cmd --permanent --add-port=5349/tcp
sudo firewall-cmd --permanent --add-port=49152-65535/udp
sudo firewall-cmd --permanent --add-port=49152-65535/tcp # 可选
# 3. 重新加载防火墙
sudo firewall-cmd --reload
# 4. 验证规则
sudo firewall-cmd --list-all
# 期望输出包含:
# ports: 3478/tcp 3478/udp 5349/tcp 49152-65535/udp
# 5. 如果需要删除规则
# sudo firewall-cmd --permanent --remove-port=3478/tcp
# sudo firewall-cmd --reload
CentOS/RHEL (iptables)
# 如果使用 iptables 而不是 firewalld
# 1. 添加规则
sudo iptables -A INPUT -p tcp --dport 3478 -j ACCEPT
sudo iptables -A INPUT -p udp --dport 3478 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 5349 -j ACCEPT
sudo iptables -A INPUT -p udp --dport 49152:65535 -j ACCEPT
# 2. 保存规则
sudo service iptables save
# 或
sudo iptables-save > /etc/sysconfig/iptables
# 3. 查看规则
sudo iptables -L -n -v | grep -E "3478|5349|49152"
Ubuntu/Debian (ufw)
# 1. 检查 ufw 状态
sudo ufw status
# 2. 添加规则
sudo ufw allow 3478/tcp comment 'TURN TCP'
sudo ufw allow 3478/udp comment 'TURN UDP'
sudo ufw allow 5349/tcp comment 'TURNS'
sudo ufw allow 49152:65535/udp comment 'TURN Relay'
# 3. 如果 ufw 未启用,启用它
sudo ufw enable
# 4. 验证规则
sudo ufw status numbered
# 期望输出:
# [ 1] 3478/tcp ALLOW IN Anywhere # TURN TCP
# [ 2] 3478/udp ALLOW IN Anywhere # TURN UDP
# [ 3] 5349/tcp ALLOW IN Anywhere # TURNS
# [ 4] 49152:65535/udp ALLOW IN Anywhere # TURN Relay
# 5. 删除规则(如果需要)
# sudo ufw delete [规则编号]
Ubuntu/Debian (iptables)
# 1. 添加规则
sudo iptables -I INPUT -p tcp --dport 3478 -j ACCEPT
sudo iptables -I INPUT -p udp --dport 3478 -j ACCEPT
sudo iptables -I INPUT -p tcp --dport 5349 -j ACCEPT
sudo iptables -I INPUT -p udp --dport 49152:65535 -j ACCEPT
# 2. 保存规则
sudo apt install iptables-persistent
sudo netfilter-persistent save
# 3. 查看规则
sudo iptables -L -n -v
端口验证
验证端口是否正确监听
# 方法 1:使用 netstat
sudo netstat -tulnp | grep -E '3478|5349'
# 期望输出:
# tcp 0.0.0.0:3478 0.0.0.0:* LISTEN 1234/turnserver
# udp 0.0.0.0:3478 0.0.0.0:* 1234/turnserver
# tcp 0.0.0.0:5349 0.0.0.0:* LISTEN 1234/turnserver
# 方法 2:使用 ss (更现代)
sudo ss -tulnp | grep -E '3478|5349'
# 方法 3:使用 lsof
sudo lsof -i :3478
sudo lsof -i :5349
# 方法 4:使用 nmap (需要安装)
sudo yum install nmap -y # CentOS
sudo apt install nmap -y # Ubuntu
nmap -p 3478,5349,49152-49200 localhost
从外部验证端口可达性
# 方法 1:使用 telnet (从另一台机器)
telnet YOUR_SERVER_IP 3478
# 如果连接成功,说明端口已开放
# 方法 2:使用 nc (netcat)
nc -zv YOUR_SERVER_IP 3478
nc -zuv YOUR_SERVER_IP 3478 # UDP
# 方法 3:使用在线工具
# 访问:https://www.yougetsignal.com/tools/open-ports/
# 输入服务器 IP 和端口 3478
# 方法 4:使用 nmap 扫描
nmap -p 3478,5349 YOUR_SERVER_IP
nmap -sU -p 3478 YOUR_SERVER_IP # UDP 扫描
常见端口问题排查
问题 1:端口被占用
# 检查谁在使用端口
sudo lsof -i :3478
# 如果被其他进程占用,停止该进程
sudo kill -9 [PID]
# 或修改 coturn 配置使用其他端口
listening-port=3479
问题 2:防火墙规则未生效
# CentOS/RHEL
# 1. 确认 firewalld 正在运行
sudo systemctl status firewalld
# 2. 检查规则是否添加到正确的 zone
sudo firewall-cmd --get-active-zones
sudo firewall-cmd --zone=public --list-all
# 3. 确保规则已持久化
sudo firewall-cmd --runtime-to-permanent
# 4. 完全重载防火墙
sudo firewall-cmd --complete-reload
# Ubuntu/Debian
# 重新加载 ufw
sudo ufw reload
问题 3:云服务器安全组未生效
# 检查清单:
1. 确认安全组已绑定到实例
2. 检查入站规则方向(Inbound/Ingress)
3. 验证协议类型(TCP/UDP)
4. 确认端口范围格式正确
5. 源地址设置为 0.0.0.0/0(或特定 IP)
6. 规则优先级不冲突
# 验证方法:
# 临时关闭服务器本地防火墙测试
sudo systemctl stop firewalld # CentOS
sudo ufw disable # Ubuntu
# 如果关闭防火墙后可以连接,说明是本地防火墙问题
# 如果仍然不能连接,说明是云安全组问题
高级网络配置
NAT 环境配置(内网服务器映射到公网)
# /etc/coturn/turnserver.conf
# 内网 IP
listening-ip=0.0.0.0
relay-ip=192.168.1.100 # 内网 IP
# 公网 IP
external-ip=120.48.94.49 # 公网 IP
# 这样 coturn 会:
# 1. 在内网 IP 上监听
# 2. 但告诉客户端使用公网 IP 连接
多网卡服务器
# 如果服务器有多个网卡,指定监听的网卡
# 只监听特定网卡
listening-ip=eth0
# 或指定具体 IP
listening-ip=192.168.1.100
# 多个网卡都监听
listening-ip=0.0.0.0
IPv6 支持
# 同时支持 IPv4 和 IPv6
listening-ip=::
listening-ip=0.0.0.0
# 或分别指定
listening-ip=192.168.1.100 # IPv4
listening-ip=2001:db8::1 # IPv6
端口配置完整验证脚本 --- 没验证过
#!/bin/bash
# 保存为 /usr/local/bin/check-turn-ports
echo "=== coturn 端口验证 ==="
echo ""
# 颜色定义
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m'
# 检查服务状态
echo "【1】服务状态"
if systemctl is-active --quiet coturn; then
echo -e "${GREEN}✅ coturn 服务运行中${NC}"
else
echo -e "${RED}❌ coturn 服务未运行${NC}"
exit 1
fi
# 检查端口监听
echo ""
echo "【2】端口监听检查"
PORTS=("3478" "5349")
for port in "${PORTS[@]}"; do
if netstat -tulnp 2>/dev/null | grep -q ":$port"; then
echo -e "${GREEN}✅ 端口 $port 正在监听${NC}"
netstat -tulnp 2>/dev/null | grep ":$port"
else
echo -e "${RED}❌ 端口 $port 未监听${NC}"
fi
done
# 检查防火墙规则
echo ""
echo "【3】防火墙规则检查"
if command -v firewall-cmd &> /dev/null; then
# CentOS/RHEL firewalld
echo "防火墙类型: firewalld"
for port in 3478 5349; do
if sudo firewall-cmd --list-ports | grep -q "$port"; then
echo -e "${GREEN}✅ 端口 $port 已在防火墙开放${NC}"
else
echo -e "${YELLOW}⚠️ 端口 $port 未在防火墙配置中${NC}"
fi
done
if sudo firewall-cmd --list-ports | grep -q "49152-65535"; then
echo -e "${GREEN}✅ 中继端口范围已开放${NC}"
else
echo -e "${YELLOW}⚠️ 中继端口范围未配置${NC}"
fi
elif command -v ufw &> /dev/null; then
# Ubuntu/Debian ufw
echo "防火墙类型: ufw"
sudo ufw status | grep -E "3478|5349|49152"
else
echo -e "${YELLOW}⚠️ 未检测到 firewalld 或 ufw${NC}"
fi
# 检查进程连接
echo ""
echo "【4】活跃连接"
CONN_COUNT=$(netstat -anp 2>/dev/null | grep turnserver | grep ESTABLISHED | wc -l)
echo "活跃连接数: $CONN_COUNT"
# 外部端口测试提示
echo ""
echo "【5】外部可达性测试"
PUBLIC_IP=$(curl -s ifconfig.me)
echo "服务器公网 IP: $PUBLIC_IP"
echo ""
echo "请从外部机器测试:"
echo " telnet $PUBLIC_IP 3478"
echo " telnet $PUBLIC_IP 5349"
echo ""
echo "或使用在线工具:"
echo " https://www.yougetsignal.com/tools/open-ports/"
# 提供修复建议
echo ""
echo "【6】快速修复命令"
echo "如果端口未开放,运行:"
echo ""
if command -v firewall-cmd &> /dev/null; then
cat << 'EOF'
# CentOS/RHEL
sudo firewall-cmd --permanent --add-port=3478/tcp
sudo firewall-cmd --permanent --add-port=3478/udp
sudo firewall-cmd --permanent --add-port=5349/tcp
sudo firewall-cmd --permanent --add-port=49152-65535/udp
sudo firewall-cmd --reload
EOF
elif command -v ufw &> /dev/null; then
cat << 'EOF'
# Ubuntu/Debian
sudo ufw allow 3478/tcp
sudo ufw allow 3478/udp
sudo ufw allow 5349/tcp
sudo ufw allow 49152:65535/udp
sudo ufw reload
EOF
fi
使用方法:
chmod +x /usr/local/bin/check-turn-ports
sudo check-turn-ports
端口配置最佳实践
- ✅ 最小化原则:只开放必需的端口
- ✅ UDP 优先:TURN 主要使用 UDP,确保 UDP 端口开放
- ✅ 中继端口范围:49152-65535 是标准,不要随意缩小
- ✅ 双重验证:云安全组 + 本地防火墙都要配置
- ✅ 定期审计:定期检查端口使用情况
- ⚠️ 避免端口冲突:确保没有其他服务占用这些端口
- ⚠️ 注意安全:生产环境考虑限制源 IP 范围
3. 证书配置
3.1 获取 SSL 证书
方式 1:Let's Encrypt(推荐,免费)
# 安装 certbot
sudo yum install certbot -y # CentOS
# 或
sudo apt install certbot -y # Ubuntu
# 获取证书(需要域名指向服务器)
sudo certbot certonly --standalone -d meeting.yourdomain.com
# 证书位置:
# /etc/letsencrypt/live/meeting.yourdomain.com/fullchain.pem
# /etc/letsencrypt/live/meeting.yourdomain.com/privkey.pem
方式 2:购买商业证书
将证书文件上传到服务器:
# 创建证书目录
sudo mkdir -p /home/ssl
# 上传证书文件
# fullchain.pem 或 .crt (包含完整证书链)
# privkey.pem 或 .key (私钥)
3.2 证书验证
验证证书完整性
# 1. 查看证书信息
openssl x509 -in /path/to/cert.crt -noout -text | head -20
# 2. 验证证书链(必须包含中间证书)
openssl crl2pkcs7 -nocrl -certfile /path/to/cert.crt | \
openssl pkcs7 -print_certs -noout
# 期望输出:至少 2 个证书
# subject=/CN=meeting.yourdomain.com # 域名证书
# issuer=/C=XX/O=CA Provider/CN=Intermediate # 中间证书
# 3. 验证私钥匹配
openssl x509 -noout -modulus -in /path/to/cert.crt | openssl md5
openssl rsa -noout -modulus -in /path/to/key.key | openssl md5
# 两个 MD5 值必须相同!
验证 TLS 连接
# 测试 TURNS 端口
openssl s_client -connect meeting.yourdomain.com:5349 -showcerts </dev/null 2>&1 | \
grep -E "Verify return code|subject=|issuer="
# 期望输出:
# subject=/CN=meeting.yourdomain.com
# issuer=/C=XX/O=Certificate Authority
# Verify return code: 0 (ok) ✅ 成功
3.3 证书权限设置
# 设置证书文件权限
sudo chown coturn:coturn /path/to/cert.crt
sudo chown coturn:coturn /path/to/key.key
sudo chmod 644 /path/to/cert.crt
sudo chmod 600 /path/to/key.key # 私钥必须保护
4. 配置优化
4.1 基础配置(推荐)
创建或编辑 /etc/coturn/turnserver.conf:
# ============================================
# 基本功能
# ============================================
fingerprint # 启用指纹验证
lt-cred-mech # 长期凭据机制
realm=meeting.yourdomain.com # 域名
# ============================================
# 用户认证
# ============================================
user=turnuser:your_strong_password_2026
# ============================================
# 端口配置
# ============================================
listening-port=3478 # STUN/TURN 主端口
tls-listening-port=5349 # TURNS (TLS) 端口
min-port=49152 # 中继端口范围开始
max-port=65535 # 中继端口范围结束
# ============================================
# 网络配置
# ============================================
listening-ip=0.0.0.0 # 监听所有网卡
external-ip=YOUR_PUBLIC_IP # 替换为服务器公网 IP
# 如果是 NAT 环境(内网映射到公网):
# relay-ip=PRIVATE_IP # 内网 IP
# external-ip=PUBLIC_IP # 公网 IP
# ============================================
# TLS 证书
# ============================================
cert=/path/to/fullchain.crt # 完整证书链
pkey=/path/to/privkey.key # 私钥
# ============================================
# 日志配置
# ============================================
log-file=/var/log/turnserver.log
verbose # 详细日志(生产环境可去掉)
# ============================================
# 安全配置
# ============================================
no-multicast-peers=true # 禁止组播(安全)
# no-loopback-peers=false # 测试时允许本地回环
# 生产环境改为 true
# ============================================
# 性能优化(可选)
# ============================================
# total-quota=100 # 最大并发会话
# max-bps=1000000 # 每会话最大带宽 (bps)
# bps-capacity=0 # 总带宽限制 (0=无限)
4.2 常见配置错误(避免)
❌ 致命错误配置:
# ❌ 错误 1:禁用 TURN 功能
stun-only # 删除这一行!
# ❌ 错误 2:禁用 TCP 中继
no-tcp-relay # 删除这一行!
# ❌ 错误 3:只监听内网 IP(云服务器)
listening-ip=192.168.x.x # 应该用 0.0.0.0
# ❌ 错误 4:缺少 external-ip
# 必须配置公网 IP
4.3 启动服务
# 重启服务
sudo systemctl restart coturn
# 查看状态 【这个一定要观察】
sudo systemctl status coturn
# 期望看到:
# Active: active (running)
# 设置开机自启
sudo systemctl enable coturn
# 查看监听端口
sudo netstat -tulnp | grep -E '3478|5349'
5. 前端集成
5.1 ICE 服务器配置
完整配置示例
// 定义 ICE 服务器池
const ICE_SERVER_POOL = [
// ⭐ 优先使用自建 STUN
{ urls: 'stun:meeting.yourdomain.com:3478' },
// ⭐ TURN UDP(推荐,最稳定)
{
urls: 'turn:meeting.yourdomain.com:3478',
username: 'turnuser',
credential: 'your_strong_password_2026'
},
// ⭐ TURN TCP(UDP 被阻止时使用)
{
urls: 'turn:meeting.yourdomain.com:3478?transport=tcp',
username: 'turnuser',
credential: 'your_strong_password_2026'
},
// ⭐ TURNS (TLS)(最安全)
{
urls: 'turns:meeting.yourdomain.com:5349',
username: 'turnuser',
credential: 'your_strong_password_2026'
},
// 公共 STUN 作为备份
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'stun:stun.voipbuster.com' },
{ urls: 'stun:stun.ekiga.net' }
];
// PeerJS 配置
const peer = new Peer({
host: 'meeting.yourdomain.com',
port: 3030,
path: '/peerjs',
config: {
iceServers: ICE_SERVER_POOL
// 使用默认策略,让 WebRTC 自动选择最佳连接
}
});
// 原生 RTCPeerConnection 配置
const pc = new RTCPeerConnection({
iceServers: ICE_SERVER_POOL
});
配置策略说明
| 策略 | 配置 | 适用场景 |
|---|---|---|
| 默认(推荐) | 不设置 iceTransportPolicy | 自动选择最佳连接 |
| 强制 TURN | iceTransportPolicy: 'relay' | 测试 TURN,或极端安全需求 |
| 禁用 TURN | 只配置 STUN | 节省带宽(不推荐) |
5.2 连接监控代码
// 监听 ICE 连接状态
peer.on('call', (call) => {
const pc = call.peerConnection;
pc.onicecandidate = (event) => {
if (event.candidate) {
console.log('[ICE 候选]', event.candidate.type, event.candidate.address);
}
};
pc.oniceconnectionstatechange = () => {
console.log('[ICE 状态]', pc.iceConnectionState);
};
// 获取最终使用的连接类型
pc.addEventListener('connectionstatechange', async () => {
if (pc.connectionState === 'connected') {
const stats = await pc.getStats();
stats.forEach(report => {
if (report.type === 'candidate-pair' && report.state === 'succeeded') {
const local = stats.get(report.localCandidateId);
const remote = stats.get(report.remoteCandidateId);
console.log(`[连接成功] ${local.candidateType} → ${remote.candidateType}`);
}
});
}
});
});
6. 验证测试
6.1 服务器端测试
测试 1:基本连通性
# 测试 UDP STUN
turnutils_stunclient meeting.yourdomain.com
# 期望输出:
# 0: IPv4. Response ... your public IP
# 测试 TCP
turnutils_stunclient -t meeting.yourdomain.com
测试 2:TURN 功能(推荐)
# 测试 UDP TURN
turnutils_uclient -u turnuser -w your_password -y meeting.yourdomain.com
# 期望输出:
# allocate response received: success
# IPv4. Received relay addr: YOUR_IP:XXXXX
# Total lost packets 0 (0.000000%) ✅
# 测试 TCP TURN
turnutils_uclient -t -u turnuser -w your_password -y meeting.yourdomain.com
# 测试 TURNS (TLS)
turnutils_uclient -t -S -u turnuser -w your_password -y meeting.yourdomain.com
# 期望看到:
# tls_connect: client session connected with cipher ECDHE-RSA-AES256-GCM-SHA384
# allocate response received: success ✅
测试 3:证书验证
# 验证 TURNS 证书
openssl s_client -connect meeting.yourdomain.com:5349 -showcerts </dev/null 2>&1 | \
grep "Verify return code"
# 期望:Verify return code: 0 (ok)
6.2 浏览器端测试
测试脚本:验证 TURN 是否可用
打开视频聊天室页面,在浏览器控制台 (F12) 运行:
// 清空控制台
console.clear();
console.log('%c=== TURN 服务器连接测试 ===', 'color: blue; font-size: 18px; font-weight: bold');
const TURN_CONFIG = {
iceServers: [
{ urls: 'stun:meeting.yourdomain.com:3478' },
{
urls: 'turn:meeting.yourdomain.com:3478',
username: 'turnuser',
credential: 'your_password'
},
{
urls: 'turns:meeting.yourdomain.com:5349',
username: 'turnuser',
credential: 'your_password'
}
]
};
const pc = new RTCPeerConnection(TURN_CONFIG);
let relayCandidates = 0;
pc.onicecandidate = (event) => {
if (event.candidate) {
const { candidate, type } = event.candidate;
console.log(`候选: ${type}`, candidate);
if (type === 'relay' || candidate.includes('typ relay')) {
relayCandidates++;
console.log('%c✅ 获得 RELAY 候选!TURN 工作正常!', 'color: green; font-weight: bold');
}
} else {
console.log('\n=== ICE 收集完成 ===');
if (relayCandidates > 0) {
console.log(`%c🎉 成功!获得 ${relayCandidates} 个 RELAY 候选`, 'color: green; font-size: 16px; font-weight: bold');
console.log('%cTURN 服务器配置正确!', 'color: green; font-weight: bold');
} else {
console.log('%c❌ 失败!未获得 RELAY 候选', 'color: red; font-size: 16px; font-weight: bold');
console.log('可能原因:');
console.log('1. TURN 服务器未启动');
console.log('2. 端口未开放');
console.log('3. 用户名/密码错误');
console.log('4. 证书问题(TURNS)');
}
}
};
// 创建 data channel 触发 ICE 收集
pc.createDataChannel('test');
pc.createOffer().then(offer => pc.setLocalDescription(offer));
console.log('⏳ 等待 ICE 候选收集...');
期望输出:
候选: host candidate:...
候选: srflx candidate:...
✅ 获得 RELAY 候选!TURN 工作正常!
候选: relay candidate:...
=== ICE 收集完成 ===
🎉 成功!获得 X 个 RELAY 候选
TURN 服务器配置正确!
6.3 在线工具测试 【没使用过】
访问 WebRTC 测试网站:
Trickle ICE - https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/
STUN/TURN URI: turn:meeting.yourdomain.com:3478 Username: turnuser Password: your_password 点击 "Add Server" → "Gather candidates" 期望看到: ✅ relay xxx.xxx.xxx.xxx:xxxxx typ relayIceTest - https://icetest.info/
添加您的 TURN 服务器配置,点击测试。
7. 日志分析
注意:
7.1 日志文件位置
# 标准日志位置
/var/log/turnserver.log # 固定文件名
/var/log/turnserver_YYYY-MM-DD.log # 日期格式(自动轮转)
# 查看当天日志
tail -f /var/log/turnserver_$(date +%Y-%m-%d).log
# 或者通过 journald
sudo journalctl -u coturn -f
7.2 关键日志解读
正常工作日志
# 1. 服务启动
INFO: Coturn Version Coturn-4.6.2
INFO: Listening on: 0.0.0.0:3478
INFO: TLS listening on: 0.0.0.0:5349
INFO: Relay ports: 49152-65535
# 2. TURN 分配请求
INFO: New allocation requested by 39.144.240.104
INFO: allocation created, lifetime=600
INFO: session 001000000000001052: realm <meeting.yourdomain.com>, username <turnuser>
# 3. Peer 连接
INFO: session 001000000000001052: peer 192.168.2.3 bound
INFO: session 001000000000001052: peer 192.168.3.6 bound
# 4. 数据传输统计
INFO: session usage: realm=<meeting.yourdomain.com>, username=<turnuser>, rp=6, rb=420, sp=6, sb=660
# rp = 接收包数, rb = 接收字节, sp = 发送包数, sb = 发送字节
# 5. 会话关闭
INFO: session closed (2nd stage), user <turnuser>, reason: TCP connection closed by client
INFO: session delete: realm=<meeting.yourdomain.com>, username=<turnuser>
异常日志
# 错误 1:认证失败
ERROR: 401 Unauthorized
# 解决:检查用户名/密码配置
# 错误 2:证书问题
ERROR: TLS/TCP socket error: Connection reset by peer
# 解决:验证证书完整性和权限
# 错误 3:端口被阻止
WARNING: Cannot bind to port 3478
# 解决:检查防火墙和端口占用
# 错误 4:权限问题
ERROR: Cannot open log file for writing
# 解决:检查日志文件权限
7.3 日志统计脚本
#!/bin/bash
# 保存为 /usr/local/bin/turn-stats
LOG="/var/log/turnserver_$(date +%Y-%m-%d).log"
echo "=== TURN 服务器统计 ($(date)) ==="
echo ""
if [ ! -f "$LOG" ]; then
echo "❌ 日志文件不存在: $LOG"
exit 1
fi
echo "【文件信息】"
echo " 位置: $LOG"
echo " 大小: $(du -h $LOG | cut -f1)"
echo " 行数: $(wc -l < $LOG)"
echo ""
echo "【会话统计】"
echo " 总会话数: $(grep -c "allocation created" $LOG)"
echo " 已关闭: $(grep -c "closed (2nd stage)" $LOG)"
echo " 认证成功: $(grep -c "username=<turnuser>" $LOG)"
echo ""
echo "【连接客户端】"
grep -oP "remote \K[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+" $LOG | sort -u | head -10
echo ""
echo "【Peer 统计】"
echo " 内网连接:"
grep "peer.*deleted" $LOG | grep -oP "peer \K192\.168\.[0-9]+\.[0-9]+" | sort | uniq -c | sort -rn | head -5
echo " 外网连接: $(grep "peer.*deleted" $LOG | grep -oP "peer \K[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+" | grep -v "^192\.168\." | wc -l)"
echo ""
echo "【错误统计】"
echo " 错误数: $(grep -c "ERROR" $LOG)"
echo " 警告数: $(grep -c "WARNING" $LOG)"
echo ""
echo "【数据传输】"
TOTAL_RX=$(grep "usage:" $LOG | grep -oP "rb=\K[0-9]+" | awk '{sum+=$1} END {print sum}')
TOTAL_TX=$(grep "usage:" $LOG | grep -oP "sb=\K[0-9]+" | awk '{sum+=$1} END {print sum}')
echo " 总接收: $((TOTAL_RX / 1024)) KB"
echo " 总发送: $((TOTAL_TX / 1024)) KB"
使用方法:
chmod +x /usr/local/bin/turn-stats
turn-stats
8. 问题排查
8.1 如何判断 TURN 是否被使用?
方法 1:浏览器端验证(推荐)
在视频会议页面按 F12,运行:
console.clear();
const TURN_IP = 'YOUR_TURN_SERVER_IP'; // 替换为您的 TURN 服务器 IP
let turnCount = 0;
let directCount = 0;
setTimeout(() => {
const connections = peer.connections; // PeerJS
// 或者遍历所有 RTCPeerConnection 对象
Object.keys(connections).forEach(userId => {
connections[userId].forEach(conn => {
const pc = conn.peerConnection;
if (!pc) return;
pc.getStats().then(stats => {
stats.forEach(report => {
if (report.type === 'candidate-pair' && report.state === 'succeeded') {
const remote = stats.get(report.remoteCandidateId);
const local = stats.get(report.localCandidateId);
const localAddr = `${local.address || local.ip}:${local.port}`;
const remoteAddr = `${remote.address || remote.ip}:${remote.port}`;
// 判断是否使用 TURN
if (local.candidateType === 'relay' ||
remote.candidateType === 'relay' ||
(remote.address === TURN_IP || remote.ip === TURN_IP)) {
turnCount++;
console.log(`✅ TURN 中继: ${userId.substring(0,8)} - ${local.candidateType} → ${remote.candidateType}`);
console.log(` ${localAddr} ←→ ${remoteAddr}`);
} else {
directCount++;
console.log(`🏠 直连: ${userId.substring(0,8)} - ${local.candidateType} → ${remote.candidateType}`);
console.log(` ${localAddr} ←→ ${remoteAddr}`);
}
}
});
});
});
});
setTimeout(() => {
console.log(`\n📊 统计: TURN=${turnCount}, 直连=${directCount}`);
if (turnCount > 0) {
console.log('%c✅ TURN 服务器正在工作!', 'color: green; font-weight: bold');
} else {
console.log('ℹ️ 当前所有连接都是直连(不需要 TURN)');
}
}, 2000);
}, 2000);
判断依据:
| 输出 | 连接类型 | 是否使用 TURN |
|---|---|---|
host → host | 本地直连 | ❌ |
prflx → prflx | P2P 打洞 | ❌ |
srflx → srflx | STUN 辅助 | ❌ |
relay → relay | TURN 中继 | ✅ 是 |
prflx → relay | 混合连接 | ✅ 是 |
| 远程地址 = TURN IP | TURN 中继 | ✅ 是 |
方法 2:服务器端验证
# 1. 查看活跃连接数
sudo netstat -anp | grep turnserver | grep ESTABLISHED | wc -l
# 如果 > 0,说明有连接在使用 TURN
# 2. 查看日志
sudo tail -f /var/log/turnserver_$(date +%Y-%m-%d).log | grep "allocation\|peer"
# 如果看到 "allocation created" 和 "peer bound",说明 TURN 正在工作
# 3. 查看 peer 连接
sudo journalctl -u coturn -n 100 | grep "peer.*bound"
# 输出示例:
# session 001000000000001052: peer 192.168.2.3 bound
# session 001000000000001052: peer 192.168.3.6 bound
方法 3:网络抓包(高级)
# 抓取 TURN 端口数据包
sudo tcpdump -i any port 3478 -nn
# 如果看到持续的数据包交换,说明 TURN 正在转发数据
8.2 常见问题排查
问题 1:浏览器获取不到 RELAY 候选
症状:
// 只有 host 和 srflx,没有 relay
候选: host
候选: srflx
=== ICE 收集完成 ===
❌ 失败!未获得 RELAY 候选
排查步骤:
# 1. 确认 coturn 服务运行
sudo systemctl status coturn
# 必须是 "Active: active (running)"
# 2. 检查端口监听
sudo netstat -tulnp | grep -E '3478|5349'
# 必须看到 3478 和 5349
# 3. 测试服务器端 TURN
turnutils_uclient -u turnuser -w password -y yourdomain.com
# 必须看到 "success" 和 "Received relay addr"
# 4. 检查防火墙
sudo firewall-cmd --list-all | grep -E "3478|5349|49152"
# 或
sudo iptables -L -n | grep -E "3478|5349"
# 5. 检查云服务商安全组
# 在云控制台确认端口已开放
# 6. 检查配置文件
grep -E "stun-only|no-tcp-relay" /etc/coturn/turnserver.conf
# 不应该有这些配置!
# 7. 查看日志错误
sudo journalctl -u coturn -n 50 | grep ERROR
问题 2:TURNS (TLS) 连接失败
症状:
ERROR: TLS/TCP socket error: Connection reset by peer
排查步骤:
# 1. 验证证书
openssl x509 -in /path/to/cert.crt -noout -text | grep -E "Subject:|Issuer:|Not After"
# 2. 检查证书链
openssl crl2pkcs7 -nocrl -certfile /path/to/cert.crt | \
openssl pkcs7 -print_certs -noout
# 必须至少有 2 个证书(域名证书 + 中间证书)
# 3. 测试 TLS 连接
openssl s_client -connect yourdomain.com:5349 -showcerts </dev/null 2>&1 | \
grep "Verify return code"
# 必须是:Verify return code: 0 (ok)
# 4. 检查证书权限
ls -lh /path/to/cert.crt /path/to/key.key
# 必须:
# -rw-r--r-- coturn coturn cert.crt
# -rw------- coturn coturn key.key
# 5. 检查配置
grep -E "cert=|pkey=" /etc/coturn/turnserver.conf
# 路径必须正确!
问题 3:日志文件为空
症状:
tail -f /var/log/turnserver.log
# 没有任何输出
排查步骤:
# 1. 检查实际日志位置
ls -lh /var/log/turnserver*.log
# 可能是日期格式:turnserver_2025-10-21.log
# 2. 查看 systemd 日志
sudo journalctl -u coturn -f
# 日志可能在这里!
# 3. 检查日志配置
grep "log-file" /etc/coturn/turnserver.conf
# 4. 检查日志文件权限
ls -lh /var/log/turnserver.log
sudo chown coturn:coturn /var/log/turnserver.log
sudo chmod 644 /var/log/turnserver.log
# 5. 检查 systemd 服务配置
cat /usr/lib/systemd/system/coturn.service | grep ExecStart
# 如果有 --no-stdout-log,可能影响日志
# 6. 重启服务后观察
sudo systemctl restart coturn
sleep 2
sudo journalctl -u coturn -n 30
问题 4:跨网段连接失败
症状:
- 同网段用户可以互相看到
- 跨网段用户无法连接
排查步骤:
# 1. 验证 TURN 是否工作
# 在浏览器运行验证脚本(见 8.1)
# 2. 检查日志是否有跨网段连接
sudo grep "peer.*bound" /var/log/turnserver_$(date +%Y-%m-%d).log | \
grep -E "192.168.2|192.168.3"
# 应该看到:
# session: peer 192.168.2.x bound
# session: peer 192.168.3.x bound
# 3. 检查前端配置
# 确认前端已配置 TURN 服务器
# 4. 测试 TURN 连通性
# 从客户端机器测试
curl -v telnet://yourdomain.com:3478
9. 性能监控
9.1 实时监控脚本
#!/bin/bash
# 保存为 /usr/local/bin/turn-monitor
while true; do
clear
echo "=== TURN 服务器实时监控 ==="
echo "时间: $(date '+%Y-%m-%d %H:%M:%S')"
echo ""
# 服务状态
STATUS=$(systemctl is-active coturn)
if [ "$STATUS" = "active" ]; then
echo "✅ 服务状态: 运行中"
else
echo "❌ 服务状态: $STATUS"
fi
# 连接统计
CONNECTIONS=$(netstat -anp 2>/dev/null | grep turnserver | grep ESTABLISHED | wc -l)
echo "🔗 活跃连接: $CONNECTIONS"
# 端口监听
PORT_3478=$(netstat -tulnp 2>/dev/null | grep :3478 | wc -l)
PORT_5349=$(netstat -tulnp 2>/dev/null | grep :5349 | wc -l)
echo "📡 端口监听: 3478($PORT_3478) 5349($PORT_5349)"
# 今日统计
TODAY_LOG="/var/log/turnserver_$(date +%Y-%m-%d).log"
if [ -f "$TODAY_LOG" ]; then
ALLOCATIONS=$(grep -c "allocation created" $TODAY_LOG 2>/dev/null || echo 0)
ERRORS=$(grep -c "ERROR" $TODAY_LOG 2>/dev/null || echo 0)
echo "📊 今日会话: $ALLOCATIONS"
echo "⚠️ 今日错误: $ERRORS"
fi
# 内存使用
MEM=$(ps aux | grep turnserver | grep -v grep | awk '{print $4}')
echo "💾 内存使用: ${MEM}%"
echo ""
echo "按 Ctrl+C 退出"
sleep 5
done
使用:
chmod +x /usr/local/bin/turn-monitor
turn-monitor
9.2 性能指标
正常运行指标:
| 指标 | 正常范围 | 说明 |
|---|---|---|
| CPU 使用率 | < 50% | 10 并发用户 |
| 内存使用 | < 200MB | 基础占用 |
| 网络带宽 | 根据用户数 | 每用户 ~1-2 Mbps |
| 活跃连接数 | = 并发用户数 × 2 | 双向连接 |
性能瓶颈:
# 当并发用户 > 100 时,考虑:
# 1. 增加端口范围
min-port=40000
max-port=65535
# 2. 限制单用户带宽
max-bps=2000000 # 2 Mbps per session
# 3. 限制总连接数
total-quota=200
# 4. 使用多台 TURN 服务器负载均衡
10. 最佳实践
10.1 安全加固
1. 强密码策略
# 使用复杂密码(至少 20 字符)
user=turnuser:Xy9#mK2$pL8@qR5&wT3!vN6
# 或者使用动态认证(推荐生产环境)
# use-auth-secret
# static-auth-secret=your_long_random_secret
2. 限制访问
# 只允许特定域名
allowed-peer-ip=0.0.0.0
# 或限制特定 IP 段
# denied-peer-ip=10.0.0.0-10.255.255.255
# denied-peer-ip=172.16.0.0-172.31.255.255
3. 防止滥用
# 限制会话时长
max-allocate-lifetime=3600 # 1 小时
# 限制单 IP 连接数
max-allocate-session-count=50
# 启用速率限制
total-quota=100
10.2 生产环境配置
# ============================================
# 生产环境推荐配置
# ============================================
# 基本功能
fingerprint
lt-cred-mech
realm=meeting.yourdomain.com
# 认证(使用动态密钥)
use-auth-secret
static-auth-secret=YOUR_LONG_RANDOM_SECRET_KEY
# 端口
listening-port=3478
tls-listening-port=5349
min-port=49152
max-port=65535
# 网络
listening-ip=0.0.0.0
external-ip=YOUR_PUBLIC_IP
# 证书
cert=/path/to/fullchain.crt
pkey=/path/to/privkey.key
# 日志(关闭 verbose)
log-file=/var/log/turnserver.log
# simple-log # 生产环境建议启用,减少日志量
# 安全
no-multicast-peers=true
no-loopback-peers=true
max-allocate-lifetime=3600
total-quota=200
# 性能
max-bps=2000000
10.3 自动化维护
日志轮转配置
创建 /etc/logrotate.d/coturn:
/var/log/turnserver*.log {
daily
rotate 7
compress
delaycompress
missingok
notifempty
create 0644 coturn coturn
postrotate
systemctl reload coturn > /dev/null 2>&1 || true
endscript
}
证书自动更新(Let's Encrypt)
# 创建更新脚本
cat > /usr/local/bin/update-turn-cert << 'EOF'
#!/bin/bash
# 更新证书后重启 coturn
certbot renew --quiet
if [ $? -eq 0 ]; then
# 复制证书到 coturn 目录
cp /etc/letsencrypt/live/meeting.yourdomain.com/fullchain.pem /home/ssl/cert.crt
cp /etc/letsencrypt/live/meeting.yourdomain.com/privkey.pem /home/ssl/key.key
# 设置权限
chown coturn:coturn /home/ssl/cert.crt /home/ssl/key.key
chmod 644 /home/ssl/cert.crt
chmod 600 /home/ssl/key.key
# 重启服务
systemctl restart coturn
echo "$(date): 证书更新成功" >> /var/log/cert-update.log
fi
EOF
chmod +x /usr/local/bin/update-turn-cert
# 添加定时任务
crontab -e
# 每月 1 号凌晨 3 点执行
0 3 1 * * /usr/local/bin/update-turn-cert
健康检查脚本
#!/bin/bash
# 保存为 /usr/local/bin/turn-healthcheck
ALERT_EMAIL="admin@yourdomain.com"
LOG_FILE="/var/log/turn-healthcheck.log"
check_service() {
if ! systemctl is-active --quiet coturn; then
echo "$(date): ❌ coturn 服务未运行,尝试重启..." | tee -a $LOG_FILE
systemctl restart coturn
sleep 5
if systemctl is-active --quiet coturn; then
echo "$(date): ✅ coturn 服务重启成功" | tee -a $LOG_FILE
else
echo "$(date): ❌ coturn 服务重启失败" | tee -a $LOG_FILE
# 发送告警邮件
echo "coturn 服务异常" | mail -s "TURN 服务器告警" $ALERT_EMAIL
fi
fi
}
check_ports() {
if ! netstat -tulnp | grep -q :3478; then
echo "$(date): ❌ 端口 3478 未监听" | tee -a $LOG_FILE
systemctl restart coturn
fi
}
check_disk() {
DISK_USAGE=$(df -h /var/log | tail -1 | awk '{print $5}' | sed 's/%//')
if [ $DISK_USAGE -gt 90 ]; then
echo "$(date): ⚠️ 磁盘使用率 ${DISK_USAGE}%" | tee -a $LOG_FILE
# 清理旧日志
find /var/log -name "turnserver_*.log" -mtime +7 -delete
fi
}
check_service
check_ports
check_disk
echo "$(date): ✅ 健康检查完成" >> $LOG_FILE
添加定时任务:
chmod +x /usr/local/bin/turn-healthcheck
# 每 5 分钟执行一次
crontab -e
*/5 * * * * /usr/local/bin/turn-healthcheck
10.4 高可用部署
多服务器配置
前端配置多个 TURN 服务器:
const ICE_SERVER_POOL = [
// TURN 服务器 1
{
urls: 'turn:turn1.yourdomain.com:3478',
username: 'turnuser',
credential: 'password'
},
// TURN 服务器 2(备份)
{
urls: 'turn:turn2.yourdomain.com:3478',
username: 'turnuser',
credential: 'password'
},
// TURN 服务器 3(地域分布)
{
urls: 'turn:turn3-us.yourdomain.com:3478',
username: 'turnuser',
credential: 'password'
}
];
WebRTC 会自动选择延迟最低的服务器!
11. 故障排查清单
11.1 快速诊断命令
# 一键诊断脚本
bash <(cat << 'EOF'
echo "=== coturn 快速诊断 ==="
echo ""
echo "【1】服务状态"
systemctl status coturn --no-pager | head -5
echo ""
echo "【2】端口监听"
netstat -tulnp | grep -E '3478|5349'
echo ""
echo "【3】活跃连接"
netstat -anp 2>/dev/null | grep turnserver | grep ESTABLISHED | wc -l
echo ""
echo "【4】最近错误"
journalctl -u coturn -n 20 --no-pager | grep ERROR
echo ""
echo "【5】配置检查"
grep -E "stun-only|no-tcp-relay|^user=|^realm=" /etc/coturn/turnserver.conf | grep -v "^#"
echo ""
echo "【6】证书验证"
openssl s_client -connect $(hostname):5349 -showcerts </dev/null 2>&1 | grep "Verify return code"
EOF
)
11.2 问题矩阵
| 症状 | 可能原因 | 解决方案 |
|---|---|---|
| 浏览器无 RELAY 候选 | TURN 未启动 | systemctl restart coturn |
| 端口未开放 | 检查防火墙和安全组 | |
| 配置错误 | 检查 stun-only 等禁用选项 | |
| TURNS 连接失败 | 证书问题 | 验证证书链和权限 |
| 端口 5349 未开放 | 开放 TCP 5349 | |
| 日志为空 | 日志权限问题 | chown coturn:coturn |
| 使用日期格式 | 查看 turnserver_YYYY-MM-DD.log | |
| systemd 捕获 | 使用 journalctl -u coturn | |
| 跨网段连接失败 | TURN 未工作 | 验证 TURN 功能 |
| 前端未配置 TURN | 检查 iceServers 配置 | |
| 连接频繁断开 | 会话超时 | 调整 max-allocate-lifetime |
| 网络不稳定 | 检查带宽和丢包率 |
12. 参考资源
12.1 官方文档
- coturn GitHub: https://github.com/coturn/coturn
- RFC 5389 (STUN): https://tools.ietf.org/html/rfc5389
- RFC 5766 (TURN): https://tools.ietf.org/html/rfc5766
- WebRTC 标准: https://www.w3.org/TR/webrtc/
12.2 在线测试工具
- Trickle ICE: https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/
- IceTest: https://icetest.info/
- WebRTC Troubleshooter: https://test.webrtc.org/
12.3 性能基准
| 指标 | 1 核 1GB | 2 核 2GB | 4 核 4GB |
|---|---|---|---|
| 并发用户 | ~20 | ~50 | ~100+ |
| 带宽需求 | 20 Mbps | 50 Mbps | 100 Mbps |
| 内存占用 | ~100 MB | ~200 MB | ~400 MB |
13. 附录
13.1 完整配置模板
# /etc/coturn/turnserver.conf
# coturn TURN 服务器生产环境配置模板
# ============================================
# 基本功能
# ============================================
fingerprint
lt-cred-mech
realm=meeting.yourdomain.com
# ============================================
# 认证方式(选择其一)
# ============================================
## 方式 1:静态用户(测试环境)
user=turnuser:your_strong_password
## 方式 2:动态密钥(生产环境推荐)
# use-auth-secret
# static-auth-secret=your_long_random_secret_key
# ============================================
# 端口配置
# ============================================
listening-port=3478
tls-listening-port=5349
min-port=49152
max-port=65535
# ============================================
# 网络配置
# ============================================
listening-ip=0.0.0.0
external-ip=YOUR_PUBLIC_IP
## NAT 环境(内网映射)配置
# relay-ip=PRIVATE_IP
# external-ip=PUBLIC_IP
# ============================================
# TLS 证书
# ============================================
cert=/path/to/fullchain.crt
pkey=/path/to/privkey.key
# ============================================
# 日志配置
# ============================================
log-file=/var/log/turnserver.log
## 开发/调试环境
verbose
## 生产环境(减少日志量)
# simple-log
# ============================================
# 安全配置
# ============================================
no-multicast-peers=true
## 生产环境禁止本地回环
no-loopback-peers=true
## 测试环境允许本地测试
# no-loopback-peers=false
# ============================================
# 性能限制
# ============================================
## 最大会话时长(秒)
max-allocate-lifetime=3600
## 总连接数限制
total-quota=200
## 单会话带宽限制(bps)
max-bps=2000000
## 总带宽限制(0 = 无限)
bps-capacity=0
# ============================================
# 其他选项(可选)
# ============================================
## CLI 管理密码(可选)
# cli-password=your_cli_password
## 禁用 UDP(如果只用 TCP)
# no-udp
## 禁用 TCP(如果只用 UDP)
# no-tcp
## 启用 Prometheus 监控
# prometheus
13.2 常用命令速查
# 服务管理
systemctl start coturn # 启动
systemctl stop coturn # 停止
systemctl restart coturn # 重启
systemctl status coturn # 状态
systemctl enable coturn # 开机自启
# 日志查看
journalctl -u coturn -f # 实时日志
journalctl -u coturn -n 100 # 最近 100 行
tail -f /var/log/turnserver_$(date +%Y-%m-%d).log # 文件日志
# 连接查看
netstat -anp | grep turnserver | grep ESTABLISHED # 活跃连接
netstat -tulnp | grep -E '3478|5349' # 监听端口
lsof -i :3478 # 端口占用
# 测试命令
turnutils_stunclient yourdomain.com # STUN 测试
turnutils_uclient -u user -w pass -y yourdomain.com # TURN 测试
# 证书验证
openssl s_client -connect yourdomain.com:5349 # TLS 测试
openssl x509 -in cert.crt -noout -dates # 证书有效期
📝 总结
本指南涵盖了 coturn TURN 服务器从部署到运维的完整流程:
✅ 部署:系统要求、安装、配置
✅ 证书:获取、验证、配置 TLS
✅ 集成:前端 ICE 配置、连接监控
✅ 验证:服务器测试、浏览器测试
✅ 日志:位置、解读、统计分析
✅ 排查:判断 TURN 使用、常见问题解决
✅ 监控:性能指标、健康检查
✅ 最佳实践:安全加固、高可用部署
核心要点:
- TURN 是 WebRTC 连接的最后保障,确保 100% 连接成功率
- 证书配置必须使用完整证书链
- 日志可能在文件或 journald,两处都要检查
- 判断 TURN 是否使用:看连接类型和远程地址
- 生产环境必须做好监控和自动化维护