
1. 项目概述为什么在 Ubuntu 上亲手搭一个 RTMP 流媒体服务器比直接用现成云服务更值得花这三小时你是不是也经历过——想做个内部培训直播、给家里老人演示智能设备操作、或者只是单纯想把手机摄像头画面实时投到客厅电视上结果打开某款“一键推流”App要么要开会员才能解锁高清码率要么推上去的流在 VLC 里卡成 PPT再一查日志全是“connection refused”或者“no data received”。这时候你就得明白一件事流媒体这东西表面看是点一下“开始直播”背后其实是网络协议、编解码器、文件系统权限、HTTP 服务与 RTMP 协议的协同作战。而 Ubuntu作为最贴近生产环境的 Linux 发行版之一恰恰提供了最透明、最可控的战场。我从 2015 年起就在各种嵌入式设备、树莓派、边缘服务器上搭 RTMP 服务光是nginx-rtmp-module这个模块就亲手编译过 17 个版本踩过的坑足够写本小册子。今天这篇不是照着官网文档念一遍的“安装指南”而是我把过去八年里在客户现场、实验室、甚至自己家阳台对真有次因为路由器 NAT 设置不对在阳台调试了俩小时反复验证过的最小可行路径。它只做三件事稳定接收一路 RTMP 推流、自动生成 HLS 自适应切片、通过标准 HTTP 提供跨平台播放能力。不碰 Docker、不搞 Kubernetes、不依赖任何第三方 SaaS所有配置都在/etc/nginx/下改一行、 reload 一次、立刻生效。关键词里的“ubuntu系统入门教程”不是指给完全没碰过终端的人看的“双击安装包”教程而是给已经能敲ls和cd但还不清楚sudo systemctl restart nginx到底干了什么的中级新手准备的实战手册。它适合刚配好 Ubuntu 22.04 桌面版、手边有台旧笔记本想废物利用的开发者也适合需要给非技术人员提供稳定内网直播能力的 IT 支持人员甚至适合想搞懂“为什么我的 OBS 推流老是断”的内容创作者。核心就一条让你在两小时内亲眼看到自己的摄像头画面从/dev/video0出发穿过 Nginx 的 RTMP 模块变成/var/www/html/hls/stream.m3u8最后在手机 Safari 里丝滑播放——这个闭环一旦打通后面所有高级玩法都是在这个地基上添砖加瓦。2. 整体设计思路与方案选型为什么是 Nginx RTMP 模块而不是 FFmpeg 直推、Node.js 或 Wowza很多人第一次接触流媒体第一反应是“FFmpeg 不就能推流吗为啥还要装 Nginx”这个问题问到了根子上。FFmpeg 确实是个万能瑞士军刀但它本质是个单向命令行工具你告诉它“从摄像头读编码推到某个地址”它就执行执行完就退出。它不监听端口不管理连接不处理多个观众同时请求同一个流时的带宽分配更不会自动把 FLV 流切成.ts片段并生成.m3u8播放列表。换句话说FFmpeg 是个优秀的“快递员”但你要建的是一个“物流中转站”得有分拣线、仓储区、取件窗口——这个中转站就是 Nginx 的角色。那为什么不选 Node.js 写个rtmp-server我试过。用node-rtsp-stream或fluent-ffmpeg搭的简易服务在 3 个观众同时观看时CPU 就飙到 95%延迟从 2 秒涨到 8 秒。原因很简单Node.js 的单线程事件循环模型在处理高吞吐、低延迟的音视频二进制数据流时天然存在瓶颈。RTMP 协议要求服务器在收到一个完整的音频/视频帧后必须在毫秒级时间内完成解析、时间戳校验、缓冲区管理然后分发给所有订阅者。Nginx 的架构则完全不同它基于epollLinux或 kqueueBSD的异步非阻塞 I/O 模型每个 worker 进程可以轻松维持数万个并发 TCP 连接而内存占用极低。它的 RTMP 模块是用 C 写的直接操作 socket 缓冲区没有 JavaScript 的 GC 停顿也没有 Python 的 GIL 锁争用。这就是为什么在一台 2 核 4G 的旧笔记本上NginxRTMP 能稳稳支撑 50 路并发观看而同等配置下 Node.js 服务在 10 路时就开始掉帧。至于 Wowza、Red5 这类商业或老牌开源方案它们功能确实强大支持 DVR、集群、DRM 加密但代价是复杂。Wowza 的配置文件动辄上千行启动一个服务要等半分钟出问题查日志得翻五六个不同目录。而我们今天要搭的是一个“能用、够用、好维护”的最小系统。Ubuntu 官方源里的libnginx-mod-rtmp包是经过严格测试的预编译模块它和系统自带的nginx完美兼容apt install之后模块自动注册nginx -t就能验证配置语法systemctl reload nginx立刻生效。没有编译错误没有依赖地狱没有版本冲突。这正是“入门教程”的核心价值用最短的学习路径获得最确定的运行结果。你不需要理解 RTMP 握手协议的 7 个字节分别代表什么但你需要知道当你把listen 1935;这行写进配置Nginx 就会在那个端口竖起耳朵等着 OBS、FFmpeg 或任何 RTMP 客户端来敲门。这种“所见即所得”的掌控感是任何云服务都无法替代的。3. 核心细节解析与实操要点从apt install到nginx.conf每一行配置背后的深意现在我们进入真正的“手术室”。别急着复制粘贴命令先理解每一步在干什么。整个搭建过程我把它拆成三个不可跳过的“检查点”软件安装、配置编写、权限与路径校验。跳过任何一个后面都会出现“配置看起来没错但就是不工作”的玄学问题。3.1 软件安装为什么顺序和参数如此关键sudo apt update sudo apt install -y libnginx-mod-rtmp sudo apt install -y ffmpeg sudo apt install -y nginx这段命令看似简单但顺序和-y参数藏着门道。sudo apt update必须放在最前面这是 Ubuntu 的铁律。很多新手会漏掉这步直接apt install结果装的是几个月前的旧包而libnginx-mod-rtmp对nginx主版本有强依赖旧包可能根本不兼容。-y参数在这里不是为了省事而是为了避免交互式提示中断自动化流程。想象一下你在写一个部署脚本apt install到一半弹出“是否确认安装[Y/n]”脚本就卡死了。所以-y是生产环境的标配。重点来了libnginx-mod-rtmp这个包名。它不是nginx-rtmp-module也不是nginx-plus-module-rtmp。Ubuntu 官方源里所有 Nginx 第三方模块都遵循libnginx-mod-*的命名规范。这个包的作用是把 RTMP 功能以“动态模块”的形式注入到系统默认的nginx二进制中。它不会覆盖你的nginx也不会新建一个nginx-rtmp命令而是让原有的nginx命令在加载配置时能识别rtmp { ... }这个块。你可以用nginx -V 21 | grep -o with-http_rtmp_module来验证模块是否已加载如果输出为空说明模块没装对或者你装的是nginx-full而不是nginx后者才是官方推荐的基础版。ffmpeg的安装同样重要。它不只是用来测试推流的“玩具”更是后续做转码、截图、生成缩略图的核心工具。apt install ffmpeg装的是 Ubuntu 维护的稳定版虽然不是最新但胜在和系统库如libx264、libfdk-aac完美匹配不会出现“找不到 codec”的报错。如果你用snap install ffmpegSnap 包是沙盒化的它访问/dev/video0这类硬件设备时需要额外授权徒增麻烦。3.2 配置文件编写nginx.conf里那些被忽略的“空格”和“分号”/etc/nginx/nginx.conf是 Nginx 的心脏。很多人复制了rtmp块却忘了它必须放在http块之外且必须是顶级块。这是 RTMP 协议的特性决定的RTMP 是基于 TCP 的独立协议它不走 HTTP 的 80/443 端口而是走自己的 1935 端口因此它的配置不能嵌套在http { ... }里面否则 Nginx 启动时会直接报错“unknown directive rtmp”。再看rtmp块里的细节rtmp { server { listen 1935; application live { live on; hls on; hls_path /var/www/html/hls; hls_fragment 3s; hls_playlist_length 60s; } } }live on;这行是开关必须有。它告诉 Nginx这个应用是“直播模式”即不存储历史流只转发实时帧。如果你删掉它Nginx 会默认进入“录制模式”试图把所有流写入磁盘而你的/var/www/html/hls目录根本没给它写权限结果就是日志里满屏的Permission denied。hls_path /var/www/html/hls;这个路径必须绝对路径且末尾不能有斜杠。我见过太多人写成/var/www/html/hls/多了一个/结果 Nginx 会尝试创建一个叫hls/的子目录而实际它要写入的是hls目录下的文件。更关键的是这个目录的所有者必须是www-data用户因为 Nginx 的 worker 进程是以www-data身份运行的。如果你用sudo mkdir /var/www/html/hls目录所有者是root那么 Nginx 就写不进去.ts切片永远不会生成。解决方法是sudo chown -R www-data:www-data /var/www/html/hls。hls_fragment 3s;和hls_playlist_length 60s;这两个参数决定了 HLS 的体验。3s是每个.ts文件的时长太短如1s会导致 HTTP 请求过于频繁增加服务器负担太长如10s会导致观众点击播放后要等很久才出画面首屏时间变长。60s是.m3u8播放列表里最多保留多少秒的切片。设为60s意味着列表里最多有 20 个3s的.ts文件。当新切片生成最老的那个就会被踢出列表。这个值不能无限大否则列表文件会越来越大客户端解析变慢。3.3 HTTP 服务配置为什么location /hls/的斜杠位置决定成败http块里的server配置是为 HLS 播放服务的。关键在这一行location /hls/ { root /var/www/html/; ... }注意location后面的/hls/结尾有一个斜杠而root后面的/var/www/html/结尾也有一个斜杠。这两个斜杠的组合决定了 URL 到文件系统的映射关系。假设用户在浏览器里访问http://localhost/hls/stream.m3u8Nginx 会这样解析location /hls/匹配成功root /var/www/html/表示“根目录是/var/www/html/”那么stream.m3u8就会被映射到/var/www/html/stream.m3u8。但我们的 HLS 切片是生成在/var/www/html/hls/目录下的所以正确的root应该是/var/www/html不带结尾斜杠这样location /hls/root /var/www/html的组合才会把/hls/stream.m3u8映射到/var/www/html/hls/stream.m3u8。这是一个经典的 Nginx “root vs alias” 陷阱。root是拼接路径alias是替换路径。这里必须用root且root路径不能带结尾斜杠否则路径就错了。我建议你直接用alias来避免混淆location /hls/ { alias /var/www/html/hls/; add_header Cache-Control no-cache; ... }alias的意思是“把/hls/这个 URL 前缀直接替换成/var/www/html/hls/这个文件系统路径”。这样逻辑清晰不易出错。提示add_header Access-Control-Allow-Origin *;这行是为了让网页前端比如用video.js写的播放器能跨域请求.m3u8和.ts文件。但生产环境请务必改成具体的域名如https://yourcompany.com*在涉及 Cookie 或认证时会失效。4. 实操过程与核心环节实现从零开始一步步见证流媒体诞生现在我们把理论付诸实践。我会带你走一遍完整的、可复现的操作链每一步都附带验证方法和预期输出。请打开你的 Ubuntu 终端跟着节奏来。4.1 环境初始化与基础检查首先确保系统干净# 查看 Ubuntu 版本确认是 20.04 或 22.04LTS 版本最稳 lsb_release -a # 检查 1935 和 80 端口是否空闲避免被其他程序占用 sudo ss -tuln | grep :1935\|:80如果看到:1935或:80被nginx、apache2或其他进程占用先停掉它们sudo systemctl stop nginx apache2。端口冲突是新手失败的第一大原因。4.2 安装与模块验证执行安装命令sudo apt update sudo apt install -y libnginx-mod-rtmp ffmpeg nginx安装完成后立刻验证 RTMP 模块是否就位# 检查 nginx 是否认识 rtmp 指令 sudo nginx -t 21 | grep -i rtmp # 如果输出类似 nginx: [emerg] unknown directive rtmp说明模块没装对重装 libnginx-mod-rtmp # 查看 nginx 编译参数确认包含 rtmp nginx -V 21 | grep -o with-http_rtmp_module如果with-http_rtmp_module出现在输出里恭喜第一步成功。4.3 创建 HLS 存储目录并设置权限# 创建目录 sudo mkdir -p /var/www/html/hls # 设置所有者为 www-dataNginx 工作用户 sudo chown -R www-data:www-data /var/www/html/hls # 设置目录权限确保可读可写 sudo chmod -R 755 /var/www/html/hls # 验证切换到 www-data 用户尝试创建一个测试文件 sudo -u www-data touch /var/www/html/hls/test.txt echo 权限OK || echo 权限失败如果输出“权限OK”说明 Nginx 有权限往这个目录写文件。这一步绝不能跳过否则你永远看不到.ts切片。4.4 编辑主配置文件/etc/nginx/nginx.conf用你喜欢的编辑器如nano或vim打开sudo nano /etc/nginx/nginx.conf找到文件末尾在http { ... }块之前插入rtmp块rtmp { server { listen 1935; chunk_size 4096; application live { live on; hls on; hls_path /var/www/html/hls; hls_fragment 3s; hls_playlist_length 60s; hls_cleanup on; } } }chunk_size 4096;是一个隐藏的性能优化项它设置了 RTMP 数据块的大小默认是 128太小会导致网络包过多。4096 是一个平衡值能减少包数量提升传输效率。然后在http { ... }块内部server { ... }块里添加location配置http { ... server { listen 80 default_server; listen [::]:80 default_server; root /var/www/html; index index.html; # 这里是新增的 HLS 访问配置 location /hls { types { application/vnd.apple.mpegurl m3u8; video/mp2t ts; } alias /var/www/html/hls/; add_header Cache-Control no-cache; add_header Access-Control-Allow-Origin *; add_header Access-Control-Allow-Methods GET, POST, OPTIONS; add_header Access-Control-Allow-Headers Origin, X-Requested-With, Content-Type, Accept, Range; try_files $uri $uri/ 404; } location / { try_files $uri $uri/ 404; } } }注意types块是必须的它告诉 Nginx.m3u8文件应该用application/vnd.apple.mpegurl这个 MIME 类型返回否则 iOS Safari 会拒绝播放。.ts文件同理。保存文件后最关键的一步# 语法检查确保配置无误 sudo nginx -t # 如果输出 syntax is ok 和 test is successful就可以重载 sudo systemctl reload nginx # 检查 Nginx 是否在监听 1935 端口 sudo ss -tuln | grep :1935 # 应该看到类似 LISTEN 0 511 *:1935 *:* users:((nginx,pid1234,fd6))4.5 推流与播放全流程实测现在我们用 FFmpeg 模拟一个真实的推流源。如果你有 USB 摄像头用/dev/video0如果没有可以用一个测试视频文件# 方案一用摄像头需确保摄像头已识别 ffmpeg -f v4l2 -framerate 30 -video_size 640x480 -i /dev/video0 \ -c:v libx264 -preset fast -b:v 1500k -maxrate 1500k -bufsize 3000k \ -c:a aac -b:a 128k -ar 44100 -f flv rtmp://localhost/live/stream # 方案二用测试视频推荐新手先用这个排除硬件问题 ffmpeg -re -i ~/Downloads/test.mp4 \ -c:v libx264 -preset fast -b:v 1500k -maxrate 1500k -bufsize 3000k \ -c:a aac -b:a 128k -ar 44100 -f flv rtmp://localhost/live/stream-re参数表示“按原始帧率读取”模拟真实流。-maxrate和-bufsize是关键的码率控制参数防止突发流量打爆带宽。-ar 44100强制音频采样率为 44.1kHz这是 HLS 兼容性最好的值。推流启动后立刻去/var/www/html/hls/目录下查看ls -la /var/www/html/hls/你应该能看到stream.m3u8文件以及一堆stream-00001.ts,stream-00002.ts这样的文件。如果 10 秒内没出现说明推流没成功回去检查nginx -t和端口监听。最后打开 VLC 播放器Media→Open Network Stream→ 输入rtmp://localhost/live/stream→ Play。这是 RTMP 原生播放。或者打开浏览器访问http://localhost/hls/stream.m3u8。如果配置正确VLC 会自动启动并播放 HLS 流。实操心得我第一次搭的时候在hls_path里多写了一个/导致.ts文件生成在/var/www/html/hls//两个斜杠这个奇怪的路径下花了 40 分钟才用strace跟踪到 Nginx 的 open() 系统调用路径。所以永远用ls -la直接看文件是否真的生成在你认为的位置这是最朴实、最有效的调试方法。5. 常见问题与排查技巧实录那些让我在凌晨两点抓狂的“灵异事件”即使严格按照上面步骤操作你也可能会遇到一些“看似无解”的问题。别慌这些问题我都经历过而且找到了最直接的解决路径。下面这张表是我整理的高频问题速查手册按发生频率排序问题现象可能原因排查命令解决方案nginx -t报错unknown directive rtmplibnginx-mod-rtmp未正确安装或未加载nginx -V 21 | grep rtmp重装sudo apt install --reinstall libnginx-mod-rtmp重启sudo systemctl restart nginx推流后/var/www/html/hls/目录下无任何文件hls_path权限错误或路径错误sudo -u www-data ls -la /var/www/html/hls/sudo chown -R www-data:www-data /var/www/html/hls确认hls_path路径末尾无/VLC 播放rtmp://localhost/live/stream黑屏无声音FFmpeg 推流命令参数错误如缺少-c:a aacffmpeg -re -i test.mp4 -f flv -vcodec copy -acodec copy rtmp://localhost/live/stream简化测试用最简命令测试逐步加参数确保-c:a aacHLS 不支持 MP3 音频浏览器访问http://localhost/hls/stream.m3u8显示 404location配置错误或root/alias路径不匹配curl -I http://localhost/hls/stream.m3u8检查location块是否在server内alias路径是否精确匹配文件系统路径播放卡顿、频繁缓冲网络带宽不足或 FFmpeg 码率设置过高sudo iftop -P 1935监控 1935 端口流量降低-b:v如1000k增加-bufsize如2000k启用-g 60关键帧间隔除了表格里的问题还有几个“幽灵级”陷阱值得单独强调陷阱一systemctl reload nginx不生效必须restartNginx 的reload信号有时无法正确加载新的rtmp模块配置尤其是当你修改了rtmp块的结构比如加了新application。这时sudo systemctl reload nginx看似成功但ss -tuln | grep :1935可能发现端口没起来。解决方案一律用sudo systemctl restart nginx。虽然会短暂中断服务但能确保配置 100% 生效。陷阱二OBS 推流时显示“Stream Starting”但 Nginx 日志无任何记录这通常不是 Nginx 的问题而是 OBS 的“服务”设置错误。在 OBS 的“设置”→“推流”里服务要选“自定义”服务器填rtmp://localhost/live串流密钥填stream。很多人填成rtmp://localhost/live/stream导致 OBS 尝试连接一个不存在的live/stream应用Nginx 根本不响应。正确写法是服务器rtmp://localhost/live密钥stream。陷阱三手机 Safari 播放.m3u8时白屏PC 端正常iOS 对 HLS 有更严格的 MIME 类型要求。curl -I http://localhost/hls/stream.m3u8的输出里Content-Type必须是application/vnd.apple.mpegurl。如果不是说明types块没生效。解决方案把types块从location里拿出来放到http块的顶层确保全局生效。最后分享一个小技巧Nginx 的 RTMP 模块日志非常详细但默认不开启。在rtmp块里加上log_format rtmp_log $remote_addr - $app/$name $status $bytes_sent $bytes_received; access_log /var/log/nginx/rtmp_access.log rtmp_log;然后sudo touch /var/log/nginx/rtmp_access.log sudo chown www-data:www-data /var/log/nginx/rtmp_access.log。这样每次推流、播放、断开都会在日志里留下痕迹比盲猜高效十倍。我在客户现场解决一个“间歇性断流”问题就是靠分析这个日志里的时间戳间隔最终定位到是客户的 WiFi 路由器开启了“节能模式”自动关闭了空闲 TCP 连接。这个 Ubuntu 下的 RTMP 服务器它不是一个终点而是一个起点。当你看着自己的摄像头画面从命令行里流淌出来变成手机屏幕上的一帧帧画面那种亲手构建数字世界的掌控感是任何云服务的“一键开通”都无法给予的。它教会你的不仅是如何配置 Nginx更是如何阅读日志、如何理解协议、如何在“黑屏”和“404”之间用最朴素的ls、cat、curl去逼近真相。接下来你可以给它加上 HTTPS、加上用户鉴权、加上自动录制归档甚至把它打包成一个.deb包一键部署到十台机器上。但所有这些扩展都建立在今天这个坚实的基础上一个能稳定运行、配置透明、问题可溯的最小系统。而这个系统你现在就已经拥有了。