NAT环境下装Jitsi,最大的坑往往不在安装本身,而在你第一次呼叫时才发现“只见画面、听不到声音”或者“完全是黑屏”。上一篇文章我们聊了基础架构,这篇直接进入实战:把Jitsi服务器架在一台内网机器上,通过端口映射对外提供服务。我会把网络规划、安装细节、NAT专项配置、验证方法全部过一遍,把那些文档里不写、但实际部署时必踩的坑提前指出来。
1. 为什么NAT环境是Jitsi部署的“特殊战场”
1.1 NAT的两种典型形态
NAT(网络地址转换)在Jitsi部署场景里,通常表现为两种形态。
第一种是家用或小型办公网络:你有一条宽带,路由器分配一个私网IP给服务器,比如192.168.1.100,公网侧只有一个动态或静态的公网IP。外部用户访问时,数据包先到达路由器,由路由器做DNAT(目的地址转换),把公网IP的某个端口映射到内网服务器的对应端口。
第二种是云主机环境:很多云厂商的虚拟机默认是私有IP,比如10.0.0.4,然后通过VPC网关或负载均衡把公网IP映射到这台机器。这种场景下,服务器上看到的网卡IP是私网地址,但对外通信时源地址会被替换成公网IP。
不管哪种形态,核心问题是一样的:Jitsi的媒体组件(JVB)需要告诉客户端“去哪里找你”。如果JVB不知道自己的公网地址,它会把内网IP告诉客户端,外部用户拿到一个192.168.x.x的地址,自然连不上。
1.2 先搞懂Jitsi的通信模型
在动手装之前,必须清楚Jitsi的通信链路是什么样的。简单说有三条通道:
第一条是HTTPS信令通道。用户浏览器访问会议网址,Nginx把请求转发给Prosody和Jicofo,完成房间创建、用户鉴权、参会者列表同步。这条通道走TCP 80/443,相对简单。
第二条是BOSH/WebSocket通道。浏览器和Prosody之间维持实时信令,主要通过/http-bind(BOSH)或/xmpp-websocket路径,在Nginx里做反代即可。
第三条是媒体流通道。这是NAT环境下最麻烦的一条。浏览器需要把音视频流发送给JVB,JVB再转发给房间里的其他人。WebRTC默认使用UDP,端口是10000(Jitsi默认的UDP端口范围是10000-20000,但容器部署时通常只用10000一个端口,传统deb包安装则默认整个范围)。
问题在于:浏览器怎么知道JVB的公网地址?这是ICE( Interactive Connectivity Establishment)协议干的事。JVB会收集自己的候选地址(candidate)告诉浏览器,浏览器也收集自己的候选地址,双方尝试找到一条可用的路径。在NAT环境下,JVB收集到的往往是内网地址,这时候就需要我们手动告诉它“你的公网地址是xxx”。
可能我说得有点快,打个比方:你请朋友来家里做客,你告诉对方“我家在幸福小区3栋201”,这是内网地址;但如果朋友住在外地,他根本找不到这个小区。你需要告诉对方“你在火车站出站口等我,我举着牌子接你”,这个“火车站出站口”就是公网映射后的地址。Jitsi的NAT配置做的就是这件事——给JVB发一个“站牌”。
2. 部署前的网络规划:一次画清楚所有映射关系
2.1 Jitsi服务端组件与端口对照
先列一张端口清单,这是整个部署的地基。我建议你把这表格打印出来,或者放在手边,后面配置防火墙和路由器映射时逐项对照:
| 组件 | 默认端口 | 协议 | 用途 | 是否需要映射到公网 |
|---|---|---|---|---|
| Nginx | 80、443 | TCP | Web页面、信令入口 | 必须 |
| Prosody | 5280(BOSH)、5281(WebSocket)、5222(C2S) | TCP | XMPP信令 | 后端内部,不直接对外 |
| Jicofo | 5347 | TCP | 会议调度与房间管理 | 后端内部,不直接对外 |
| JVB | 10000-20000(默认) | UDP | 音视频媒体流 | 必须 |
| JVB | 4443 | TCP | 媒体流TCP回退(TURN-like) | 强烈建议映射 |
| prosody组件端口 | 5280、5281 | TCP | 内部组件通信 | 不需要 |
这里有个重点:80和443端口对外映射是共识,但UDP 10000端口经常被漏掉。很多人配置完端口映射后,页面能打开、房间能创建,但进去后画面黑屏、只有对方名字却听不到声音,十有八九就是UDP 10000没有映射,或者被防火墙拦了。
2.2 公网IP不够用时的替代方案
如果你只有一个公网IP(家庭宽带、小型VPS都是这种),完全够用。因为Jitsi只需要三组对外端口:TCP 80/443、UDP 10000(或更大范围)、TCP 4443(可选)。这三组端口全部映射到同一台内网服务器的相同端口即可。
如果你的环境比较特殊,比如公网IP不在你手上,或者无法映射UDP端口,那就要考虑TURN服务器方案。Jitsi可以在部署时同时配置coturn(TURN服务),让客户端在UDP不通时走TCP 443通道穿透。这个方案我会在后面常见问题里展开,网络通畅时优先走UDP,体验最好。
2.3 域名解析与证书规划
我强烈建议你给Jitsi配一个真实域名,而不是直接用IP访问。原因有三个:
一是浏览器安全限制。摄像头和麦克风权限在非HTTPS环境下会被浏览器拦截(除非你手动设置站点例外),用http://IP访问时经常出现“无法使用麦克风”的问题。
二是Let’s Encrypt证书签发需要域名和80端口验证。虽然Jitsi安装时也支持自签名证书,但浏览器会报警告,体验非常差。
三是后续维护方便。你换公网IP时,只要改DNS解析就行,不需要改一堆配置。
域名这里建议用二级域名,比如meet.yourdomain.com,专门给会议用,不占用主站的HTTPS泛解析。
3. 完整安装步骤:从裸机到跑起来的每一步
3.1 系统准备与初始配置
我推荐用Debian 11或Ubuntu 22.04 LTS,这两个系统在Jitsi社区里测试最充分。系统装好后,第一件事是固定IP地址,不要让服务器通过DHCP随机获取地址,否则端口映射会失效。
然后做系统更新:
sudo apt update && sudo apt upgrade -y如果你在云环境或区域网络内访问软件源很慢,可以顺手配置一下apt镜像源,这个看个人网络情况。更新完成、确认服务器IP稳定后就可以装Jitsi了。
3.2 安装Jitsi主程序
Jitsi官方仓库的地址在prosody.im仓库或者download.jitsi.org。安装前先添加仓库密钥:
wget -qO - https://download.jitsi.org/jitsi-key.gpg.key | sudo apt-key add - sudo sh -c 'echo "deb https://download.jitsi.org stable/" > /etc/apt/sources.list.d/jitsi-stable.list' sudo apt update如果你用的系统比较新(比如Debian 12),apt-key会提示deprecated,但这不影响功能。也可以换成挂keyring的方式,限制更严格,原理一样。
接着安装:
sudo apt install jitsi-meet安装过程中,脚本会问你几个问题:
- Hostname:填写你规划的会议域名,比如
meet.yourdomain.com。注意这不是服务器的hostname,而是对外服务的域名。 - SSL证书方式:选“I want to use my existing certificate”或“Generate a new self-signed certificate”。建议先选自签名,装完后再切到Let’s Encrypt,这样如果流程卡住,至少还能用IP或临时域名访问。
安装完成后,Jitsi的各组件都会自动以服务方式启动。这时候桌面会多出一个小机器人图标吗?不会,Jitsi的一切都是通过web界面操作,服务器端没有图形界面。
3.3 网络层配置:防火墙与端口映射
到这一步,先暂停一下,因为网络配置必须在进入具体应用配置前搞定,不然后面测试的时候排错范围很大。
首先在内网服务器上放行端口。如果你用ufw:
sudo ufw allow 80/tcp sudo ufw allow 443/tcp sudo ufw allow 10000/udp sudo ufw allow 4443/tcp # 如果要用TCP媒体回退 sudo ufw allow 5347/tcp # Jicofo与Prosody通信 sudo ufw allow 5280/tcp # BOSH信令 sudo ufw allow 5281/tcp # WebSocket sudo ufw enable注意,服务端口5222(Prosody客户端直连端口)在浏览器场景下不对外,但如果你要用桌面端或移动端客户端(如Jitsi Meet移动应用)直接连,可能需要。实际测试下来,移动端通过443即可完成信令,一般不需要额外开5222。
接着做路由器或云安全组的端口映射。这一步放在路由器管理页面操作(家用路由器)或者在云控制台的安全组/防火墙规则里配置。映射规则非常简单,就是把公网IP的指定端口一对一转发到服务器内网IP的相同端口:
| 公网端口 | 映射目标(内网IP:端口) | 协议 |
|---|---|---|
| 80 | 192.168.1.100:80 | TCP |
| 443 | 192.168.1.100:443 | TCP |
| 10000 | 192.168.1.100:10000 | UDP |
| 4443 | 192.168.1.100:4443 | TCP |
有些家用路由器在NAT设置里叫“虚拟服务器”或“端口转发”,云安全组则是在“入方向规则”里添加。
需要特别确认一件事:你的路由器是否支持UDP端口映射。绝大部分支持,但个别廉价路由器的“端口转发”功能只支持TCP,这会导致媒体流完全不通。可以用tcping工具或者建个测试UDP端口来判断,不过最快的方法是直接看路由器的参数页有没有明确标注支持UDP。
3.4 HTTPS证书:从自签名切换到Let’s Encrypt
装好jitsi-meet后,如果你选择的是自签名证书,浏览器访问时会有安全警告。我习惯装完立刻切换为Let’s Encrypt证书,操作很简单:
sudo /usr/share/jitsi-meet/scripts/install-letsencrypt-cert.sh脚本会让你填一个邮箱地址(用于证书过期通知),然后自动配置证书自动续期。
这里有一个NAT环境的特殊点:Let’s Encrypt的HTTP验证要求访问的公网80端口能到达服务器。如果端口映射还没配好,证书签发会失败。所以顺序一定是:先做端口映射,再签证书。
如果你的网络环境不方便暴露80端口(比如运营商封了80),那就只能用DNS验证方式签证书,这需要在DNS服务商后台手动添加TXT记录,操作复杂一些,但可行。这个场景我不展开,多数情况下80端口都是通的。
3.5 核心难点:JVB的NAT地址配置
终于到本文的重头戏了。Jitsi安装完成后,JVB默认只知道自己的内网IP,它向外部客户端通告的candidate地址也是内网IP。这时候外部用户加入会议,WebRTC协商出来的媒体地址是192.168.x.x,数据包发不进来,表现就是只能看到对方的文字消息,但画面和声音全黑。
解决办法是告诉JVB它的公网地址。
编辑JVB的配置文件:
sudo nano /etc/jitsi/videobridge/sip-communicator.properties在文件末尾追加:
org.ice4j.ice.harvest.NAT_HARVESTER_LOCAL_ADDRESS=192.168.1.100 org.ice4j.ice.harvest.NAT_HARVESTER_PUBLIC_ADDRESS=202.103.xx.xx这里,LOCAL_ADDRESS填Jitsi服务器在内网的实际IP,PUBLIC_ADDRESS填你映射出去的公网IP。填写完成后重启videobridge服务:
sudo systemctl restart jitsi-videobridge2如果用的是较新版本的Jitsi(比如2023年后的版本),也可以直接在/etc/jitsi/videobridge/config里设置环境变量:
JVB_NAT_HARVESTER_LOCAL_ADDRESS=192.168.1.100 JVB_NAT_HARVESTER_PUBLIC_ADDRESS=202.103.xx.xx两种方式等价,看你手上的版本是哪种机制。改完config文件后同样需要重启服务。
这一步做完,外部用户加入会议时,JVB会把公网IP放在ICE候选里,媒体流就能正确回传了。
注意:如果你的公网IP是动态的(比如家用宽带重启后IP变了),就需要配合DDNS服务,并及时更新NAT_HARVESTER_PUBLIC_ADDRESS。有一个办法是写个脚本定期检查公网IP变化并重写配置、重启服务,但这属于进阶玩法,这里先提个醒。
3.6 配置Prosody与Jicofo的鉴权
Jitsi默认允许匿名登录,也就是说任何知道网址的人都能进你的会议。如果你希望给参会者设置密码,可以在Prosody里启用mod_auth_token认证,或者更轻量地在会议房间层面设置密码。但作为内部协作工具,我一般建议用前置密码保护或者不做全局限制,把重点放在是否允许创建房间上。
如果你想限制只有管理员能创建房间、普通用户只能加入别人创建的会议,可以按下面的方式配置。
编辑/etc/prosody/conf.d/meet.yourdomain.com.cfg.lua:
VirtualHost "meet.yourdomain.com" authentication = "internal_hashed" modules_enabled = { "roster"; "presence"; "message"; "muc"; "ping"; "bosh"; "websocket"; }然后通过prosodyctl创建管理员账号:
sudo prosodyctl register admin meet.yourdomain.com your_strong_password但“管理员”要生效,还需要让Jicofo认识这个用户。为了方便,Jitsi安装脚本已经生成了两个内部账号:focus和jvb,用于组件之间的认证。如果你不做管理员区分,保持默认即可,它不会影响正常使用。
3.7 Nginx反代配置检查
Jitsi安装包会自动生成Nginx站点配置,路径一般是/etc/nginx/sites-available/meet.yourdomain.com.conf。核心的反代规则已经写好了,你只需要确认两个关键location块存在:
location /http-bind { proxy_pass http://127.0.0.1:5280/http-bind; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; } location /xmpp-websocket { proxy_pass http://127.0.0.1:5281/xmpp-websocket; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $http_host; }改完后测试并重载Nginx:
sudo nginx -t sudo systemctl reload nginx这步如果配置有误,最常见的症状是会议页面一直转圈加载,但网络请求里能看到部分API返回500。
4. 验证与自检:模拟内外网交互场景
4.1 服务器端自检
配置全部完成后,先别急着找真实用户测试。在服务器上做一次快速自检:
检查各服务状态:
systemctl status prosody systemctl status jicofo systemctl status jitsi-videobridge2检查关键端口在监听:
ss -tulpn | grep -E '80|443|5280|5281|5347|10000'如果TCP端口都在监听,但UDP 10000没看到,很可能是JVB还没启动完毕或者UDP绑定失败。再等几秒重新看一次。
4.2 内网客户端验证
用你办公环境里的电脑访问https://meet.yourdomain.com,新建一个会议。这一步主要验证:
- 页面能否正常加载(Nginx和证书)
- 摄像头能否打开(HTTPS + 浏览器权限)
- 进入房间后能否看到自己(WebRTC本地流)
- 能否邀请另一台内网机器加入并互通音视频(内网ICE路径)
内网测试通过,不代表外网就通,因为内网访问时浏览器和JVB可能都处于同一个NAT后面,即使地址通告有误也可能误打误撞通了。所以这一步只能说完成了50%。
4.3 外网客户端验证(关键实验)
用手机流量(关闭WiFi)访问会议网址,或者找一位外部网络环境的朋友加入同一个房间。观察几个现象:
- 能否加入房间并看到已有参会者
- 加入后画面是否正常显示、声音是否清晰
- 用另一个外网设备加入,三方能否互通
如果外网加入成功但画面黑屏、只有音频或只有视频,多半是媒体服务转发的某个地址有问题。这时候打开浏览器控制台,看WebRTC的ICE候选(chrome://webrtc-internals),重点看candidate列表里有没有公网IP。
如果你发现candidate里只有内网地址,那就是NAT_HARVESTER_PUBLIC_ADDRESS配置没生效,或者videobridge服务没重启成功。
如果candidate里有公网IP但通信还是失败,则要检查UDP 10000端口映射是否真的通。可以做个UDP连通性测试,或者临时开个nc -u监听观察一下。
5. NAT环境下高频问题排查实录
5.1 问题速查表
这一节我把实际部署中遇到的问题按优先级整理成表,方便你遇到类似情况时快速对照:
| 症状 | 可能原因 | 排查与解决 |
|---|---|---|
| 页面能打开,但加入会议后一直转圈 | Nginx反代BOSH/WebSocket失败;Prosody未启动 | 检查sudo nginx -t;查看systemctl status prosody;确认5280/5281端口是否被Firefox/Chrome的拦截策略影响 |
| 加入会议后只有自己画面,别人看到你黑屏 | 麦克风/摄像头权限被浏览器拦截 | 改用HTTPS访问;检查地址栏左边权限设置;刷新页面 |
| 能听到声音但看不到画面(或相反) | WebRTC只协商了音频或视频编解码器,常见于带宽受限 | 临时降低分辨率;检查JVB日志看是否编码器协商失败 |
| 跨网络呼叫时黑屏/无声音 | JVB NAT地址未配置或配置错误 | 查看sip-communicator.properties;打开webrtc-internals看有没有公网IP的candidate;确认UDP端口映射 |
| 内部正常、外网无法访问页面 | 80/443端口映射未配置或防火墙拦截 | 在路由器/云安全组检查443端口转发;telnet测试外网443连通性 |
| 会议进行中偶发卡顿、花屏 | 公网带宽不足或UDP丢包严重 | 降低最大码率(在配置中设置bitrate限制);检查路由器NAT会话表是否溢出 |
| 移动端无法连接 | 部分NAT路由器对WebSocket处理兼容性差 | 在Nginx中启用WebSocket的proxy_read_timeout和proxy_send_timeout为7200s |
| 证书过期导致页面打不开 | Let's Encrypt续期失败 | 检查续期任务systemctl status certbot.timer;手动运行sudo certbot renew |
5.2 排查JVB NAT配置的最有效方法
如果外网呼叫仍然失败,我最先做的一件事是看JVB日志:
sudo journalctl -u jitsi-videobridge2 -n 100 -f日志里会显示ICE协商过程,查找addIceCandidate或ICE failed相关关键字。如果看到MISSED或FAILED字样,优先怀疑NAT配置或UDP端口不通。
另一个实用的测试方法是在服务器上临时用一个公网可达的UDP端口做测试。比如你在路由器上临时加一条规则:公网20000/udp映射到内网服务器的10000/udp,然后在服务器上监听10000端口,用外部机器发UDP包进去。如果服务器能收到包,说明路由器和防火墙链路是通的;如果收不到,问题就出在网络链路上,与Jitsi配置无关。
5.3 我踩过的几个深度坑
第一,忘记配置JVB的单端口模式。默认情况下,JVB在UDP端口10000-20000范围内监听,路由器如果要映射整个UDP范围会很麻烦(很多家用路由器不支持范围映射,只支持单端口转发)。我的解决方法是修改JVB配置,让它只监听10000一个端口:
sudo nano /etc/jitsi/videobridge/sip-communicator.properties添加:
org.jitsi.videobridge.REUSE_SSRC=true org.jitsi.videobridge.udp.port=10000 org.jitsi.videobridge.udp.port.range=1有些版本用org.ice4j.ice.udp.port=10000、org.ice4j.ice.udp.port.range=1。这个改动做完,UDP映射只要一条规则就搞定。这个配置在不同版本的Jitsi中键名略有差异,如果你用的版本找不到这两个键,去/etc/jitsi/videobridge/config里找JVB_UDP_PORT设成10000、JVB_UDP_PORT_RANGE设成1效果一样。
第二,没有处理NAT回环(hairpin)问题。内网用户通过域名访问时,数据包到达路由器后需要被“折返”回内网服务器,这个行为叫NAT hairpin或NAT loopback。很多家用路由器默认不支持,表现为:内网访问meet.yourdomain.com打不开,但手机流量访问正常。解决方法是换用支持hairpin的路由器,或者在内网DNS里把域名解析到内网IP(所谓split DNS)。
第三,TURN服务在NAT网络里很有必要。上面常规的UDP 10000映射做好了,对大多数网络环境够用;但如果你经常有用户处于很严格的网络环境(如公司防火墙只放行443),就需要部署coturn。安装和配置其实不是太难:
sudo apt install coturn然后在/etc/turnserver.conf里把listening-ip设成内网IP,relay-ip设成内网IP,external-ip设成公网IP,最后在/etc/jitsi/videobridge/sip-communicator.properties里配置TURN凭证,再让Jicofo把TURN信息传给客户端。Jitsi官网文档有专门的TURN配置章节,跟着做一般不会错。但有TURN也意味着所有业务流量会经过服务器,会占用出站带宽,所以小规模部署时可以先不加,遇到特定网络问题再加。
5.4 性能相关的一个容易被忽视的点
大多数家庭宽带的上行带宽有限(比如只有30-50Mbps),而Jitsi视频通话的质量与带宽直接相关。一个30分钟的四人会议,每人720p可能需要1.5-2Mbps的码率,四个人同时发言,服务器上的转发流量会有多路叠加,出站带宽可能瞬间吃满。
如果发现会议卡顿,除了调整客户端的config.js里constraints(视频最大码率),还可以在服务端Jicofo配置里限制单路视频码率。常见做法是编辑/etc/jitsi/jicofo/sip-communicator.properties,加上:
org.jitsi.jicofo.MAX_BITRATE_720P=1200 org.jitsi.jicofo.MAX_BITRATE_1080P=2500具体数值根据你的带宽和会议规模来调。这个小改动在NAT环境下尤其重要,因为很多NAT路由器对连接数限制较严,带宽占用过高会导致NAT表溢出、连接被重置,表现就是会议中途全部掉线。
6. 部署完成后的收尾建议
整个NAT环境下的Jitsi部署流程,最核心的其实就三件事:端口映射放行、JVB的公网地址通告、证书与反代配置。只要这三件事做对,部署就成功了七成,其余都是锦上添花。
我个人在实际操作中体会最深的一点是:排障时一定要分层。页面打不开先查80/443;页面能开但信令不通就查Nginx反代和Prosody;信令通了但媒体不行就查UDP端口和JVB候选地址。不要一上来就怀疑Jitsi软件有问题,大多数问题都出在网络层。
还有一个建议:装好之后第一时间用手机流量从外网做一次完整测试,因为内网测试和真实NAT穿透场景差别很大。这个测试成本很低,却能提前发现百分之八九十的部署问题。
如果你接下来想在团队里正式启用这套系统,建议顺手把备份脚本加上——Prosody的数据库(/var/lib/prosody)和配置文件目录(/etc/jitsi、/etc/prosody)是必须备份的,视频文件本身不落盘,所以备份压力不大。后面如果有时间,我再写一篇关于Jitsi高可用和容器化部署的实践,把“多房间并发”和“单机性能瓶颈”这两块内容补上。