nginx-proxy-manager Stream 完全指南:用 TCP/UDP 四层转发托管游戏、FTP 与 SSH 服务器
【免费下载链接】nginx-proxy-managerDocker container for managing Nginx proxy hosts with a simple, powerful interface项目地址: https://gitcode.com/GitHub_Trending/ng/nginx-proxy-manager
Stream 是 nginx-proxy-manager 中相对较新的一项功能,它借助 Nginx 的 stream 模块,把进入指定端口的 TCP/UDP 数据流原样转发到网络中另一台主机,让非 HTTP 协议的服务也能统一纳入可视化代理管理。读完本文,你将掌握 Stream 的适用场景与局限、Web 界面中的完整配置方法、每个字段的取值约束,以及它在后端如何生成 Nginx 配置、通过 REST API 完成增删改查的底层实现原理,并了解仓库中用于验证 TCP、UDP、TCP+UDP 与 SSL 终止四条链路的自动化测试证据。
本文以 前端帮助文档 cs/Streams.md 为主体骨架展开,并结合 backend/internal/stream.js、backend/templates/stream.conf、frontend/src/modals/StreamModal.tsx 等源码进行纵深讲解。
什么是 Stream
按官方帮助文档的定义:
Stream 是 Nginx 相对较新的一项功能,用于把 TCP/UDP 数据流直接转发到网络中另一台计算机。
与 nginx-proxy-manager 中其他"Host"(如 Proxy Host)不同,Stream不做 HTTP 协议解析、也不按域名路由。它工作在传输层(第四层),只关心"哪个端口进来、转发到哪台机器的哪个端口"。在 backend/internal/stream.js 的创建逻辑中可以看到一段关键注释:
// streams aren't routed by domain name so don't store domain names in the DB const data_no_domains = structuredClone(data); delete data_no_domains.domain_names;源码直接印证了 Stream 的核心定位:不以域名路由,因此域名数据根本不会写入数据库。
适用场景:游戏、FTP 与 SSH 服务器
帮助文档明确指出了 Stream 最典型的三种使用场景:
- 游戏服务器:多数游戏使用自定义 TCP/UDP 端口通信(如 Minecraft 的 25565、CS 系游戏的 27015 等),协议并非 HTTP,普通反向代理无法处理;
- FTP 服务器:FTP 控制连接(21 端口)与数据连接都是原始 TCP 流,需要四层透传;
- SSH 服务器:SSH(22 端口)是二进制协议,同样需要原样转发。
凡是"非 HTTP/HTTPS 的 TCP 或 UDP 服务",都可以用 Stream 统一入口、集中管理。它的本质是 Nginx 的stream {}模块能力,由 nginx-proxy-manager 在前端表单与配置模板中做了封装,让用户无需手写 Nginx 配置即可完成端口级转发。
Stream 与 Proxy Host 的区别
理解两者差异有助于判断何时该用哪种"Host":
| 对比维度 | Stream | Proxy Host |
|---|---|---|
| 工作层次 | 四层(TCP/UDP),透传原始数据流 | 七层(HTTP/HTTPS),解析应用协议 |
| 路由依据 | 端口(incoming_port) | 域名(domain_names) |
| 适用协议 | 游戏、FTP、SSH、RDP 等任意 TCP/UDP | HTTP、WebSocket 等 Web 服务 |
| 是否支持 SSL 终止 | 支持(TCP 场景) | 支持 |
| 是否支持访问控制/路径转发 | 不支持 | 支持 |
从数据库模型 backend/models/stream.js 看,stream 表的核心字段包括incoming_port、forwarding_host、forwarding_port、tcp_forwarding、udp_forwarding、enabled、certificate_id、meta,完全没有域名列,进一步佐证了"按端口路由"的设计。
Stream 配置字段详解
创建 Stream 时,Web 表单(StreamModal.tsx)与后端校验 stream-object.json 共同定义了以下字段:
| 字段 | 类型 | 取值范围 | 说明 |
|---|---|---|---|
incoming_port | 整数 | 1~65535 | 监听入口端口,示例 9090 |
forwarding_host | 字符串 | 域名 / IPv4 / IPv6 | 目标主机地址,示例example.com |
forwarding_port | 整数 | 1~65535 | 目标端口,示例 80 |
tcp_forwarding | 布尔 | true / false | 是否启用 TCP 转发 |
udp_forwarding | 布尔 | true / false | 是否启用 UDP 转发 |
enabled | 布尔 | true / false | 是否启用该 Stream |
certificate_id | 整数 | 证书 ID 或 0 | 用于 TCP 的 SSL 终止,可选 |
meta | 对象 | 任意 JSON | 附加元数据,默认{} |
owner_user_id | 整数 | 用户 ID | 归属用户,由后端自动写入 |
其中tcp_forwarding、udp_forwarding、enabled、is_deleted是布尔字段,在模型层通过convertBoolFieldsToInt/convertIntFieldsToBool在数据库(0/1)与 API(true/false)之间转换(见 stream.js 模型)。
关于协议开关的交互细节:前端表单保证 TCP/UDP 至少启用其一——当你关闭 TCP 开关时,UDP 会被自动置为开启,反之亦然(见 StreamModal.tsx 的onChange逻辑)。这是因为一个 Stream 至少要转发一种协议才有意义。
通过 Web 界面创建 Stream 的步骤
在 nginx-proxy-manager 管理界面中,进入Streams页面(该页面受STREAMS权限节控制,见 pages/Nginx/Streams/index.tsx),点击新增即可打开表单,包含两个标签页:
Details(详情)标签页,需要填写:
- Incoming Port(入口端口):对外监听端口,范围 1~65535,例如
8080; - Forward Host(转发主机):目标机器的主机名或 IP,例如内网服务器地址
192.168.1.10,长度 1~255 字符; - Forward Port(转发端口):目标端口,范围 1~65535;
- Protocols(协议):TCP / UDP 两个开关,至少开启一个。
SSL 标签页,可选配置:
- SSL Certificate(SSL 证书):下拉选择已有证书,或现场新建(
allowNew); - SSL 选项:包括 HTTP/2、HSTS 等针对 TCP 流场景的 TLS 选项(
forHttp={false}表明这是为四层流量而非 HTTP 场景准备)。
保存后,前端通过useSetStream调用后端 API 完成创建或更新(见 StreamModal.tsx),成功后会弹出提示并关闭弹窗。
底层原理:生成的 Nginx 配置长什么样
Stream 的配置由模板 backend/templates/stream.conf 渲染生成。当enabled为真时,按协议开关分别生成两个独立的server块:
# 入站端口 TCP: 1 UDP: 1 server { listen {{ incoming_port }} reuseport {%- if certificate %} ssl {%- endif %}; # listen [::]:{{ incoming_port }} reuseport ssl; # 启用 IPv6 时取消注释 # SSL 相关配置(_certificates_stream.conf) proxy_pass {{ forwarding_host }}:{{ forwarding_port }}; access_log /data/logs/stream-{{ id }}_access.log stream; error_log /data/logs/stream-{{ id }}_error.log warn; # Custom include /data/nginx/custom/server_stream[.]conf; include /data/nginx/custom/server_stream_tcp[.]conf; }UDP 块结构类似,但listen指令带有udp关键字:
server { listen {{ incoming_port }} udp reuseport; proxy_pass {{ forwarding_host }}:{{ forwarding_port }}; }几个值得注意的实现细节:
reuseport:允许同一端口上的多个 worker 进程共享监听 socket,提升并发接入能力;- IPv6:模板默认注释掉
[::]监听,仅当ipv6参数启用时才会写入 IPv6 地址; - 可扩展性:模板预留了
/data/nginx/custom/server_stream[.]conf、server_stream_tcp[.]conf、server_stream_udp[.]conf三个自定义 include 点,高级用户可以注入额外的 Nginx 指令; - 日志分离:每个 Stream 独立记录
stream-{id}_access.log与stream-{id}_error.log,便于问题排查。
当为 TCP Stream 配置了证书后,backend/templates/_certificates_stream.conf 会被引入,根据证书类型写入证书路径:
- Let's Encrypt 证书:引用
/etc/letsencrypt/live/npm-{{ certificate_id }}/fullchain.pem与privkey.pem,并 includessl-cache-stream.conf与ssl-ciphers.conf; - 自定义证书:引用
/data/custom_ssl/npm-{{ certificate_id }}/下的 fullchain 与 privkey。
这意味着TCP Stream 支持 TLS 终止(SSL 终结在 Nginx 上,与后端之间仍是明文或自选协议),UDP Stream 不支持 SSL。
后端实现:创建、更新、启停与审计
backend/internal/stream.js 是 Stream 的后端业务核心,围绕数据表操作与 Nginx 配置同步实现了完整生命周期:
- create:先做
streams:create权限校验,剥离域名数据后insertAndFetch入库;若certificate_id === "new",会先走internalCertificate.createQuickCertificate快速签发证书再回填;随后调用internalNginx.configure(streamModel, "stream", row)重新生成 Nginx 配置,最后写入审计日志(action 为created); - update:
streams:update权限校验 → 查回原记录做 ID 一致性兜底 → 更新数据库 → 重新 configure 并刷新meta→ 审计日志(updated); - delete:软删除(
is_deleted: 1),随后internalNginx.deleteConfig("stream", row)删除配置并reload()重载 Nginx,审计日志记录deleted; - enable / disable:更新
enabled标志;启用时重新生成配置,停用时删除配置并重载 Nginx,分别记录enabled/disabled审计动作; - getAll:按
incoming_port升序返回全部未删除 Stream,支持expand(如owner、certificate)与按端口模糊搜索。
值得留意的两点:
- 软删除设计:
is_deleted标志而非物理删除,所有查询(get / getAll / getCount)都带where("is_deleted", 0)过滤; - 端口冲突预检的 TODO:create 与 update 流程中都有 "existing ports should have been checked" 的 TODO 注释,说明业务层显式端口占用预检仍留待完善,实际冲突会在配置写入与 Nginx 重载环节暴露。
REST API 一览
路由定义在 backend/routes/nginx/streams.js,全部接口需要 JWT 鉴权:
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/nginx/streams | 列出所有 Stream,支持expand、query参数 |
| POST | /api/nginx/streams | 新建 Stream,返回 201 |
| GET | /api/nginx/streams/:stream_id | 获取单个 Stream |
| PUT | /api/nginx/streams/:stream_id | 更新 Stream |
| DELETE | /api/nginx/streams/:stream_id | 删除 Stream |
| POST | /api/nginx/streams/:host_id/enable | 启用 Stream |
| POST | /api/nginx/streams/:host_id/disable | 停用 Stream |
以创建一个 TCP Stream 为例,等价于发送如下请求(字段与 Streams.cy.js 测试中的 payload 一致):
{ "incoming_port": 1500, "forwarding_host": "127.0.0.1", "forwarding_port": 80, "certificate_id": 0, "meta": {}, "tcp_forwarding": true, "udp_forwarding": false }自动化测试如何验证 Stream 可用性
仓库在 test/cypress/e2e/api/Streams.cy.js 中为 Stream 提供了完整的端到端验证,是理解功能行为的直接证据:
- TCP Stream(端口 1500):创建后执行
curl --noproxy -- http://website1.example.com:1500,断言返回默认站点内容yay it works,证明 TCP 转发链路真实可用; - UDP Stream(端口 1501):创建
tcp_forwarding: false, udp_forwarding: true的组合并断言响应字段; - TCP+UDP 双协议(端口 1502):两个开关同时为 true,随后同样用 curl 验证 TCP 通路;
- SSL TCP Stream(端口 1503):先上传自定义证书(
provider: "other"+/upload接口),再创建绑定该证书的 Stream,最后调用testssl.sh对website1.example.com:1503做 TLS 安全检查,验证 SSL 终止配置正确。
这些测试同时也验证了 API 响应符合 Swagger Schema(validateSwaggerSchema),即前后端契约一致。
使用注意事项
结合源码与测试,使用 Stream 时建议留意:
- 每个端口对应一个 Stream:由于按端口监听,同一入口端口不能被多个 Stream 复用(测试中 1500~1503 各自独立);
- 端口范围受限于 1~65535,且监听端口不能与容器内其他服务冲突;
- UDP 不支持 SSL:只有 TCP 块支持证书终止;
- Stream 不做协议级处理:没有重写、路径转发、访问列表等七层能力,如需这些功能应改用 Proxy Host;
- 高级定制:可在
/data/nginx/custom/下放置server_stream.conf、server_stream_tcp.conf、server_stream_udp.conf注入自定义 Nginx 指令,模板会自动 include。
综上,Stream 是 nginx-proxy-manager 面向四层流量场景的关键能力。无论是把游戏服、FTP、SSH 暴露到公网,还是为内网非 HTTP 服务统一入口,它都能以可视化表单 + 自动生成 Nginx 配置的方式,显著降低手写stream {}配置的维护成本。
【免费下载链接】nginx-proxy-managerDocker container for managing Nginx proxy hosts with a simple, powerful interface项目地址: https://gitcode.com/GitHub_Trending/ng/nginx-proxy-manager
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考