coturn TURN 服务器完整指南

lishihuan大约 28 分钟

coturn TURN 服务器完整指南

适用于 WebRTC 视频会议系统的 NAT 穿透解决方案

场景说明:WebRTC 搭建的视频会议,因为网络问题,可能存在无法通讯的情况,所以需要NAT 穿透 的方式建立通道

📑 目录

  1. 核心概念
  2. 服务器部署
  3. 证书配置
  4. 配置优化
  5. 前端集成
  6. 验证测试
  7. 日志分析
  8. 问题排查
  9. 性能监控
  10. 最佳实践

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 直连❌ 不需要
有公网 IPv6HOST 直连❌ 不需要
普通 NAT(可打洞)PRFLX/SRFLX❌ 不需要
对称 NAT无法 P2P需要
跨网段(防火墙隔离)无法直连需要
企业严格防火墙无法 P2P需要

2. 服务器部署

2.1 环境要求

推荐配置

  • 系统:CentOS 7/8 或 Ubuntu 18.04+
  • CPU:2 核心+
  • 内存:2GB+
  • 带宽:10Mbps+(根据并发用户调整)
  • 公网 IP:必须

端口要求

  • 3478 (TCP/UDP) - STUN/TURN
  • 5349 (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 云服务器安全组配置

⚠️ 重要:必须开放的端口

端口范围协议方向用途优先级
3478TCP入站STUN/TURN 主端口⭐⭐⭐⭐⭐ 必须
3478UDP入站STUN/TURN 主端口⭐⭐⭐⭐⭐ 必须
5349TCP入站TURNS (TLS)⭐⭐⭐⭐ 推荐
49152-65535UDP入站TURN 中继端口范围⭐⭐⭐⭐⭐ 必须
49152-65535TCP入站TURN TCP 中继(可选)⭐⭐ 可选
image-20251021180641715
image-20251021180641715

各云服务商配置指南

阿里云 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

端口配置最佳实践

  1. 最小化原则:只开放必需的端口
  2. UDP 优先:TURN 主要使用 UDP,确保 UDP 端口开放
  3. 中继端口范围:49152-65535 是标准,不要随意缩小
  4. 双重验证:云安全组 + 本地防火墙都要配置
  5. 定期审计:定期检查端口使用情况
  6. ⚠️ 避免端口冲突:确保没有其他服务占用这些端口
  7. ⚠️ 注意安全:生产环境考虑限制源 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自动选择最佳连接
强制 TURNiceTransportPolicy: '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 测试网站:

  1. Trickle ICE - https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/open in new window

    STUN/TURN URI: turn:meeting.yourdomain.com:3478
    Username: turnuser
    Password: your_password
    
    点击 "Add Server" → "Gather candidates"
    
    期望看到:
    ✅ relay xxx.xxx.xxx.xxx:xxxxx typ relay
    
  2. IceTest - https://icetest.info/open in new window

    添加您的 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 → prflxP2P 打洞
srflx → srflxSTUN 辅助
relay → relayTURN 中继
prflx → relay混合连接
远程地址 = TURN IPTURN 中继

方法 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 官方文档

12.2 在线测试工具

12.3 性能基准

指标1 核 1GB2 核 2GB4 核 4GB
并发用户~20~50~100+
带宽需求20 Mbps50 Mbps100 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 使用、常见问题解决
监控:性能指标、健康检查
最佳实践:安全加固、高可用部署

核心要点

  1. TURN 是 WebRTC 连接的最后保障,确保 100% 连接成功率
  2. 证书配置必须使用完整证书链
  3. 日志可能在文件或 journald,两处都要检查
  4. 判断 TURN 是否使用:看连接类型远程地址
  5. 生产环境必须做好监控和自动化维护