Jitsi 部署系列的第二篇,重点写 NAT 环境。上一篇写的是有独立公网 IP 的服务器怎么装 Jitsi,很多朋友照着做都跑通了,但轮到真实场景就卡壳——服务器在公司内网、在机房出口后面、在家宽路由后面,没有公网 IP,不知道怎么把服务暴露出去。这篇文章专门解决这类问题:在 NAT 环境下把 Jitsi Meet 完整装起来,让外网用户能正常开会、看画面、听声音,媒体流稳定不掉线。
适合两类人看:一类是准备在企业内网、云上 VPC 或者家庭宽带后面自建开源视频会议的朋友;另一类是刚接触 Jitsi、网络基础一般、想把组件关系和部署顺序一次搞清楚的新手。我会把部署过程中的配置细节、踩过的坑、排查过的真实案例原原本本写出来,不是理论教程,是操作实录。
1. NAT环境下部署的整体思路
1.1 为什么NAT是常态,而不是例外
很多刚接触 Jitsi 的人会下意识觉得,视频会议服务器嘛,肯定要有公网 IP。但实际部署下来你会发现,真正拿到独立公网 IP 的场景反而是少数。企业办公网络里,服务器多半挂在内部交换机的某个网段上,出口统一走公司网关的 NAT;云上如果买了按量付费的内网机器,或者想把服务放在负载均衡后面,同样要面对 NAT;更不用说分支机构、实验室、门店这类场景,出口往往是运营商光猫自带的 NAT。
所以说,NAT 部署不是"不正规"的部署方式,它是很多真实环境里唯一可选的方案。先把"谁能访问服务器"这个关系捋清楚,NAT 环境的 Jitsi 完全可以做得和公网部署一样稳定。
这里要先区分两个概念:入站映射和出站会话。通常说的端口映射是入站方向,路由器把公网侧某个端口转发到内网服务器的 IP 端口;而出站方向,内网服务器主动访问外网一般不需要额外配置,NAT 网关会自动建立临时映射。Jitsi 在 NAT 环境下的工作逻辑就是:网页和信令走 TCP 入站映射,媒体流走 UDP 入站映射,再靠 STUN 和 TURN 解决更复杂的穿越问题。
1.2 组件分工与流量模型
Jitsi Meet 不是一块大而全的程序,而是多个开源组件协同工作。理解分工,对后面排查问题至关重要。
| 组件 | 角色 | 主要端口/协议 | NAT 环境下的关键点 |
|---|---|---|---|
| Nginx | 对外 Web 入口,提供前端页面和反向代理 | 80/443 TCP | 做入站映射即可 |
| Prosody | XMPP 信令服务器,管理会议室和用户状态 | 5222/5280 等 TCP | 一般不出现在公网映射清单,内部组件通过它连接 |
| Jicofo | 会议焦点管理器,负责创建会议、协调媒体通道 | 内部使用 5347 等 | 无公网暴露需求 |
| JVB | 媒体桥,实际转发音视频和屏幕共享流 | 10000 UDP | 最需要花心思的组件,必须让公网用户能到达 |
| Coturn | TURN/STUN 服务,P2P 不通时的中转兜底 | 3478/5349 TCP/UDP | 有公网映射即可,用于媒体链路兜底 |
看这个表就能明白我的配置顺序:先把 Web 和信令跑通,再处理 JVB 的 UDP 映射,最后加 TURN 兜底。前面没通,后面配了也白配。
2. 部署前的网络与环境准备
2.1 端口规划:一张表说清楚
部署前先把端口规划做出来,不要上来就装。我这次的示例环境是:服务器内网 IP 为192.168.1.100,路由器出口公网 IP 为203.0.113.5,域名用meet.example.com做演示。你在真实环境里把这三项替换成自己的即可。
需要映射的端口如下:
| 服务 | 协议 | 端口 | 映射目标 |
|---|---|---|---|
| HTTPS 网页与信令 WebSocket | TCP | 443 → 192.168.1.100:443 | 公网到内网 |
| HTTP 跳转(可选) | TCP | 80 → 192.168.1.100:80 | 公网到内网 |
| JVB 媒体 UDP 通道 | UDP | 10000 → 192.168.1.100:10000 | 公网到内网 |
| TURN 基础端口 | TCP/UDP | 3478 → 192.168.1.100:3478 | 公网到内网 |
| TURN over TLS(可选) | TCP/UDP | 5349 → 192.168.1.100:5349 | 公网到内网 |
有个容易忽略的细节:不要把 JVB 的 UDP 端口范围配得太大。JVB 默认支持 10000-20000 这样一个范围,如果用这种模式,路由器上要连续映射上万条 UDP 规则,很多家用路由器撑不住,企业防火墙也可能要折腾半天。我的经验是直接把 JVB 固定在 10000 单端口,相当于只需要一条 UDP 映射规则,省事得多。
2.2 DNS、域名证书与系统要求
域名方面,强烈建议用独立二级域名,比如meet.example.com,不要直接用裸域名。原因有几个:一是 Jitsi 的 Prosody、Nginx、前端配置里,域名是贯穿始终的主线,独立域名不容易和邮件、内部系统等其它服务冲突;二是证书签发更省心,申请免费 ACME 证书时,对二级域名做验证很常规。
DNS 只需要一条 A 记录:meet.example.com指向出口公网 IP203.0.113.5。这里藏着一个很多人掉过的坑:在内网服务器上,如果 DNS 解析把meet.example.com解析到公网 IP,Prosody 内部组件之间的连接可能绕到公网再回来,等于自己绕远路。后文我会讲怎么用/etc/hosts把这个问题填掉。
服务器系统我推荐 Ubuntu 22.04 LTS 或 Debian 12,官方 apt 源里的 Jitsi 包版本较新,依赖也顺手。硬件方面,纯内部测试 2 核 2G 就能跑;真要投入使用,建议 4 核 8G 起步,磁盘至少 40G。视频会议是不折不扣的 CPU 消耗大户,编码、转发、加密都吃 CPU,核数越多,同时开会的人数上限越高。
3. 核心配置逐项拆解(从安装到跑通)
3.1 安装与初始化
先更新系统并装基础工具。我习惯在系统初始化阶段把时区、swap、防火墙这些一并处理掉:
apt update && apt upgrade -y apt install -y wget curl gnupg2 apt-transport-https ca-certificates ufw然后添加 Jitsi 官方 apt 仓库。Jitsi 的包发布机制比较特殊,具体命令在官方文档里有更新,我在这条环境上常用的是把官方签名密钥和源写到/etc/apt/sources.list.d/下:
curl -sL https://download.jitsi.org/jitsi-repo-key.gpg.key | gpg --dearmor > /usr/share/keyrings/jitsi-keyring.gpg echo "deb [signed-by=/usr/share/keyrings/jitsi-keyring.gpg] https://download.jitsi.org stable/" > /etc/apt/sources.list.d/jitsi-stable.list apt update安装主包时,推荐直接装jitsi-meet,它会连带装好 Nginx、Prosody、Jicofo、JVB 这些核心组件:
apt install -y jitsi-meet安装过程中会弹交互式界面,问两件事:域名是什么、证书怎么处理。域名填meet.example.com;证书选择"生成自签名证书"或"申请 ACME 证书"。NAT 环境下我建议先用自签名证书把整个流程跑通,再切换正式证书。这样能把"网络通不通"和"证书是否有效"两个问题分开排查,省掉很多混淆。
注意:安装向导生成的 Nginx 配置、Prosody 域名文件全部基于你填写的域名。如果这里填错了,后面手动修改牵扯的面会非常大。安装前务必确认域名拼写和规划一致。
3.2 Prosody 配置:先让信令"找得到家"
Prosody 是 XMPP 信令服务器,网页端、Jicofo、JVB 之间的连接控制消息全部经过它。安装完成后,Prosody 的站点配置在/etc/prosody/conf.avail/meet.example.com.cfg.lua,主配置在/etc/prosody/prosody.cfg.lua。
NAT 环境下,Prosody 本身不需要直接暴露到公网,它走 Nginx 反向代理的 443 端口,通过 BOSH/WebSocket 与浏览器通信。所以配置 Prosody 的核心目标,是让内部组件之间互相连得上,而不是让它监听公网端口。
需要检查几个关键项:
modules_enabled中要包含bosh、websocket等基础模块。cross_domain_bosh或跨域相关配置不要配错,浏览器跨域访问 WebSocket 被拦下后,登录、进会都会失败。- 文件末尾有两段
VirtualHost配置,其中一个conference.meet.example.com是会议室服务,必须存在。
组件连接配置在文件底部,通常长这样:
Component "conference.meet.example.com" "muc" modules_enabled = { "muc_mam" } Component "jitsi-videobridge.meet.example.com" component_secret = "这里填组件密钥"这个component_secret是 JVB 和 Jicofo 连接 Prosody 用的共享密钥,安装时自动生成。如果后端 JVB 起不来,日志提示认证失败,十有八九是这里的密钥与 JVB 侧配置不一致。
改完配置,先做语法检查:
prosodyctl check再重启服务:
systemctl restart prosody实操心得:Prosody 日志默认在
/var/log/prosody/prosody.log。调试阶段建议把日志级别调到info,能看到每个组件的连接记录,对排查"组件之间有没有连上"特别有用。确认稳定后,再调回warn减少日志量。
3.3 JVB 的 NAT Harvester 配置:媒体不卡的根基
JVB 是整个系统里受 NAT 影响最大的组件。它要告诉所有参会者:"我的媒体流可以从这个公网地址和端口收到",而它自己位于 NAT 内部时完全不知道公网地址,必须显式告知。
JVB 的运行时配置在/etc/jitsi/videobridge/sip-communicator.properties,核心是这几行:
org.jitsi.videobridge.NAT_HARVESTER_LOCAL_ADDRESS=192.168.1.100 org.jitsi.videobridge.NAT_HARVESTER_PUBLIC_ADDRESS=203.0.113.5 org.jitsi.videobridge.udp.port=10000第一行是服务器实际网卡的内网地址;第二行是公网用户访问服务器时使用的地址,也就是路由器公网 IP;第三行把媒体端口固定在 10000。这里面用到的机制,简单说就是让 JVB 在 ICE 协商阶段只上报这一对内网/公网地址,客户端收到候选地址后,会往公网 IP 的 10000 端口发媒体包,路由器再把包转发给内网服务器。
同时建议把单端口模式调整好。某些版本默认会在 10000 之后的连续端口上创建媒体通道,即使我们只配了起步端口,也要防止范围扩大。可以在配置里再加一行:
org.jitsi.videobridge.udp.maxPort=10000这两行配完后,JVB 只监听 10000 UDP。如果没配maxPort,JVB 有可能监听 10000-20000 整个范围,公网侧 NAT 映射的人工成本高,还容易出现媒体流断断续续。
改完重启 JVB:
systemctl restart jitsi-videobridge怎么确认配置生效?两条命令:
ss -ulnp | grep 10000看进程是否真的监听 UDP 10000。
journalctl -u jitsi-videobridge -n 100日志里出现 harvester 或IceUdpTransport相关的启动记录,说明 NAT Harvester 被正常加载了。
注意:
NAT_HARVESTER_LOCAL_ADDRESS填的一定是服务器本机实际网卡地址,不能填127.0.0.1,也不要填多网卡的虚拟地址。填错了,公网客户端发的媒体包根本找不到设备,这是媒体不通方向里最高频的配置错误。
3.4 Coturn 兜底:P2P 走不通时的最后保障
就算 JVB 配好了 NAT Harvester,仍然有一些网络环境会导致媒体流无法直达:比如双方都在严格型 NAT 后面,或者企业出口做了 SIP ALG 之类的干扰。这种情况就得靠 TURN 服务器中转。
Jitsi 的安装包会带 Coturn,但默认没有完整配置。配置文件路径是/etc/turnserver.conf。NAT 环境下有两种玩法。
第一种,Coturn 和 Jitsi 在同一台内网服务器上,靠路由器做 UDP 端口映射。这样配置时,要在turnserver.conf里指定监听端口和对外映射地址:
listening-port=3478 tls-listening-port=5349 fingerprint lt-cred-mech use-auth-secret static-auth-secret=这里填一个随机密钥 realm=meet.example.com external-ip=203.0.113.5/192.168.1.100external-ip的含义是:告诉 Coturn,外部世界的你是203.0.113.5,但你的本机网卡地址是192.168.1.100。这和 JVB 的 NAT Harvester 是一个思路,都是"内外地址分离声明"。
第二种,Coturn 部署在一台有独立公网 IP 的服务器上,Jitsi 这边通过配置指向它。这种玩法适合已有基础设施的团队,但配置会牵涉到 Jitsi 前端config.js里的 turn 地址和密钥同步,代码量稍多。
不管哪种玩法,Jitsi 这一侧都要让前端知道 TURN 服务器的凭据。安装 Jitsi 时生成的/etc/jitsi/meet/meet.example.com-config.js里,通常会有p2p、stunServers等配置块,turn 相关字段在后面。新版 Jitsi 里常见的是turnCredentials配置节,需要把 Coturn 的static-auth-secret同步到这里的secret字段,两者不一致时,TURN 中继就不会生效。
实际操作中,我建议把 TURN 验证分成两步:先独立测试 Coturn 本身可用,再让 Jitsi 接上。浏览器控制台的 WebRTC 日志里,如果一直看不到包含relay类型的 candidate,就说明 Coturn 没有被正确识别,只是在往 STUN 里兜圈子。
3.5 Nginx 反向代理与系统防火墙
Nginx 是外网用户访问服务器的第一个入口。安装 Jitsi 时自动生成的站点文件/etc/nginx/sites-available/meet.example.com.conf,主要配置了 80 跳 443、443 的 Web 服务以及 WebSocket 反向代理。
NAT 环境下,Nginx 配置本身基本不用大改,但有两个检查点值得留意:
- 确认
server_name与域名一致。 - 确认
sites-enabled里没有多余的默认站点抢占端口。我之前遇到过装完 Jitsi 后,默认站点和 Jitsi 站点同时监听 443,外网访问一直跳到默认页面的情况。
检查监听状态:
ss -tnlp | grep -E ':80|:443'防火墙规则,我习惯按最小化原则开:
ufw allow OpenSSH ufw allow 80/tcp ufw allow 443/tcp ufw allow 10000/udp ufw allow 3478/tcp ufw allow 3478/udp ufw enable需要特别强调的是,NAT 环境里路由器上的端口映射和服务器本机的 ufw 是多层关系,必须同时放行。有的朋友只在路由器上做了映射,服务器本机的 ufw 没有放行 10000/udp,结果外网访问 Web 正常,媒体流全部被本机防火墙丢弃,表现就是"能进会但没画面"。这个坑出现频率相当高。
3.6 内网解析与外网验证的分层策略
部署完成的验证要分两层走,这是我踩过多次坑之后总结出来的顺序,不是多余谨慎。
第一层,内网验证。在部署机上修改/etc/hosts,把meet.example.com解析到内网 IP192.168.1.100,用浏览器访问https://meet.example.com。这一步能最快确认 Web、Prosody、JVB 的本地连通性。如果这一步都不通,不要急着测外网,先解决内部问题。
第二层,外网验证。把部署机的/etc/hosts恢复为正常解析,或者直接用另一台处于外网的电脑访问,确认页面能打开、会议能创建。创建会议后,再开一个外网窗口加入同一会议,看能否看到对方、听到声音。
还有一点新手经常忽略:部署服务器自身的/etc/hosts里,如果没有把meet.example.com指向内网 IP,而是靠 DNS 解析到公网 IP,那么 JVB 所在服务器访问 Prosody 时可能绕到公网。大多数情况能用,但会引入额外延迟,甚至可能出现数据包绕到路由器后又映射回自己的尴尬局面。稳妥做法是在/etc/hosts里加一行:
192.168.1.100 meet.example.com conference.meet.example.com注意:改 hosts 只影响这台服务器自己的解析,不会影响外网用户。外网用户仍然会用公共 DNS 的 A 记录解析到公网 IP,两不冲突。
4. 常见问题与排查实录
4.1 能进会议室但看不到画面听不到声音
这是最常见的求助场景:页面打开正常、会议室能建立、自己能看到本地预览,但对方看不到你,你也看不到对方。
按概率从高到低,排查顺序我给一个固定清单:
- 检查路由器是否映射了 UDP 10000 端口。TCP 映射和 UDP 映射在很多路由器面板上是分开勾选的,UDP 经常被漏掉。
- 检查服务器本机 ufw 是否放行 10000/udp。用
ss -ulpn | grep 10000看监听,再用另一台外网机器用 UDP 工具做连通性测试。 - 检查 JVB 的 NAT Harvester 参数,重点看
PUBLIC_ADDRESS是否是该公网 IP,LOCAL_ADDRESS是否是实际网卡地址。 - 检查浏览器 WebRTC 日志里的 ICE candidate。F12 控制台看候选类型,如果全是 host 类型且地址是私网网段,说明 STUN/TURN 配置没生效,媒体包根本发不到正确的地方。
我自己处理过的一个典型案例:某机构的服务器挂在部门出口,网络管理员只做了 443 的 TCP 映射,UDP 完全没做。网页访问正常,但媒体流全部走不通。补上 UDP 10000 映射后,问题立刻消失。排查这类问题,思路一定要固定在"媒体是独立的 UDP 通道"这个前提上,别被"网页正常"迷惑。
4.2 偶尔能连上但过几分钟就掉
这种"能通但掉线"的问题,多半指向 NAT 网关的 UDP 会话老化时间。很多家用路由器为了节省内存,会把 NAT 映射表的老化时间设到 30 秒到 1 分钟。Jitsi 会议中如果一段时间没有媒体包经过,NAT 条目就过期了,后续到达映射公网端口的媒体包,路由器不知道该转发到内网哪个地址,连接自然断开。
排查方法是先确认掉线规律:如果间隔大约等于路由器的 UDP timeout,那就基本锁定了。解决办法有两个:
- 在路由器或企业防火墙上把 UDP 会话老化时间调大到 5 到 10 分钟。
- 如果网关不支持调整,就启用 TURN 服务。TURN 走中继链路,在 NAT 网关眼中是稳定的连接,不容易触发超时。
这本质上不是 Jitsi 的缺陷,而是 NAT 网关的状态行为。向网络管理员说明时,建议把"UDP timeout 对视频通话的影响"讲清楚,大多数企业防火墙都支持调整。
4.3 远程端总是显示"网络连接质量差"
如果所有用户都提示质量差,大概率是 JVB 的媒体链路本身丢包严重。可以先测一下公网到服务器的往返延迟和丢包率:
ping -c 20 203.0.113.5如果丢包率超过 1%,就要检查路由器是否有限速策略、有没有安全设备在做深度包检测并干扰 UDP,以及运营商线路质量。排除这些问题后再回到 Jitsi 配置上。
如果只有部分用户质量差,比如一方在公司内网、一方在家庭宽带,这种跨网络场景即使媒体链路通了,延迟和丢包也可能偏高。此时启用 TURN 中继反而可能更稳定,因为中继路径会有更明确的网络结构,而不是靠 NAT 穿透碰运气。
另外多说一句:Jitsi 的带宽自适应算法会根据网络状况自动降分辨率。如果发现长期只有 180p 或 360p,先解决网络链路问题,再去怀疑配置。
4.4 快速排查命令速查表
常见现象和优先排查项,整理成一张表:
| 现象 | 优先排查项 | 命令/操作 |
|---|---|---|
| 外网访问 Web 打不开 | 路由器 443 映射、Nginx 站点 | ss -tnlp | grep 443,浏览器直接访问公网 IP |
| 能进会但无媒体流 | UDP 10000 映射、ufw、JVB 参数 | ss -ulnp | grep 10000,检查sip-communicator.properties |
| 组件间认证失败 | Prosody 密钥与 JVB/Jicofo 不一致 | grep component_secret /etc/prosody/conf.avail/*.cfg.lua |
| JVB 日志报错但原因不明 | JVB 日志级别过低 | 查看/var/log/jitsi/jvb.log,临时调高日志级别重启 |
| 过一会才掉线 | NAT 网关 UDP 老化时间 | 调大路由器 UDP timeout,或启用 TURN |
我个人在多次部署里验证过一句话:NAT 环境下部署 Jitsi,本质上就是打通一条"公网到内网服务器"的固定路径,然后把每个组件在路径中的位置说清楚。不要想着一次性把所有组件配完再测,每配一个组件就做一次小验证。信令通了测媒体,媒体通了再上 TURN,层层推进。很多人折腾半天最后发现只是 UDP 端口没放行,就是因为跳过了中间的小验证,把所有变量混在一起,根本无法定位。
这个系列后续如果大家反馈多,可以把会议录播、多机负载、大并发参数调优单独拆一篇出来。先分享到这里,有问题评论区聊。