Nginx Proxy Manager 端口转发(Streams)完全指南:TCP/UDP 流代理原理与配置实战
【免费下载链接】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(NPM)中基于 Nginx stream 模块实现的核心能力之一,用于将 TCP/UDP 流量直接转发到网络中的另一台主机,适合游戏服务器、FTP、SSH 等非 HTTP 服务。本文以 Streams 帮助文档 为骨架,结合 stream.conf 模板、internal/stream.js、stream 数据模型 与前端 StreamModal 等仓库源码,系统讲解 Stream 的适用场景、配置字段、前端操作、后端生命周期与最终生成的 Nginx 配置,帮助读者在 NPM 中正确、安全地搭建 TCP/UDP 转发规则。
什么是 Stream:Nginx 的传输层流代理
按照官方帮助文档的定义:Stream 是 Nginx 的一项相对较新的功能,它可以把 TCP/UDP 流量直接转发到网络中的另一台计算机(对应俄语文档原文 "поток позволяет напрямую проксировать TCP/UDP‑трафик на другой компьютер в сети")。
与 Proxy Host(HTTP/HTTPS 反向代理)不同,Stream 工作在传输层(L4),不关心上层协议内容,只负责把原始字节流在端口之间搬运。因此它非常适合承载那些不是 Web 服务的网络应用。
NPM 中 Stream 的核心特征可以从源码中得到印证:
- 在 internal/stream.js 中有一句关键注释:
streams aren't routed by domain name so don't store domain names in the DB(Stream 不按域名路由,因此数据库中不存储域名)。这正是它区别于 Proxy Host 的本质——Stream 只按"端口"寻址,不按"域名"寻址。 - stream 列表接口 按
incoming_port升序排序,并支持按incoming_port模糊搜索,再次印证端口是 Stream 的唯一标识维度。
典型使用场景:游戏服务器、FTP 与 SSH
官方帮助文档明确指出:如果你在运行游戏服务器、FTP 或 SSH 服务器,这个功能就会很有用。
从技术角度看,这些场景的共性在于:
| 场景 | 协议 | 为什么需要 Stream |
|---|---|---|
| 游戏服务器 | TCP/UDP | 游戏流量通常是非 HTTP 的自定义协议,且需要 UDP 支持 |
| FTP 服务器 | TCP | 控制连接与数据连接均为 TCP,且可能占用多个端口 |
| SSH 服务器 | TCP | 需要把外部某个端口安全地透传到内网 SSH 服务 |
其典型部署形态是:Nginx Proxy Manager 运行在公网入口主机上,监听一个公网端口,把流量转发到内网某台机器(或同一内网中的另一台服务器)的对应端口,实现无需暴露整台内网机器即可对外提供 TCP/UDP 服务。
Stream 配置字段全解
创建或编辑 Stream 时涉及的核心字段,在 stream-object.json 接口定义 中有完整的类型约束,同时 StreamModal.tsx 展示了前端表单的输入规则。汇总如下:
| 字段 | 类型 | 取值范围/说明 | 来源 |
|---|---|---|---|
incoming_port | integer | 1~65535,NPM 监听的外部端口,必填 | stream-object.json、StreamModal |
forwarding_host | string | 目标主机,支持域名、IPv4、IPv6 三种格式 | stream-object.json |
forwarding_port | integer | 1~65535,目标端口,必填 | stream-object.json |
tcp_forwarding | boolean | 是否启用 TCP 转发 | stream-object.json |
udp_forwarding | boolean | 是否启用 UDP 转发 | stream-object.json |
enabled | boolean | 是否启用该 Stream | stream-object.json |
certificate_id | integer | 关联的 SSL 证书 ID(0 表示无证书) | stream-object.json |
meta | object | 附加元数据(默认{}) | stream-object.json |
前端表单 StreamModal.tsx 中对应的输入校验逻辑也完全一致:
- Incoming Port:
type="number",min=1、max=65535,校验函数validateNumber(1, 65535),占位符示例8080; - Forward Host:
validateString(1, 255),支持填域名或 IP; - Forward Port:同样限定 1~65535;
- TCP/UDP 转发开关:通过
tcpForwarding、udpForwarding两个布尔字段控制。
值得注意的是,tcp_forwarding和udp_forwarding可以同时开启——模板会为同一incoming_port分别生成 TCP 与 UDP 两个server块,实现"同一端口同时支持 TCP 和 UDP"的效果(详见下文模板分析)。
前端操作:创建与编辑 Stream
在 NPM 管理界面中,Streams 的入口位于左侧菜单的Streams页面(页面组件见 Streams/TableWrapper.tsx)。页面顶部提供了:
- 新增按钮:调用
showStreamModal("new")打开新建弹窗; - 帮助按钮:调用
showHelpModal("Streams", ...)打开本文对应的帮助文档; - 每行的编辑/删除/启停操作:删除与启停分别调用 deleteStream / toggleStream 对应接口。
弹窗内部(StreamModal.tsx)分为两个标签页:
- Details(详情):填写 Incoming Port、Forward Host、Forward Port,以及 TCP/UDP 转发开关;
- SSL:通过 SSLCertificateField 选择证书、SSLOptionsFields 配置 SSL 选项。
提交后前端调用useSetStream()(对应 useStream.ts hook),最终命中后端的创建/更新接口。列表页在操作成功后还会失效["streams"]与["stream", id]查询缓存,保证界面即时刷新(见 TableWrapper.tsx)。
后端生命周期:从接口到 Nginx 配置
Stream 的所有后端逻辑集中在 internal/stream.js,其完整生命周期如下:
创建(create)
流程为:权限校验streams:create→ 设置owner_user_id→ 初始化meta→ 写入数据库(此时特意剥离domain_names字段)→ 若certificate_id === "new"则先走createQuickCertificate快速签发证书并回填 → 调用internalNginx.configure(streamModel, "stream", row)生成 Nginx 配置 → 写入审计日志(action 为created)。
更新(update)
先读取现有记录做一致性校验,更新数据库后再次get并重新configure,同时把变更写入审计日志(action 为updated)。
启停(enable / disable)
enable:把enabled置 1,重新调用configure生成配置;disable:把enabled置 0,调用internalNginx.deleteConfig("stream", row)删除配置并reload()。
两者都会在审计日志中记录enabled/disabled动作。
删除(delete)
采用软删除策略:仅将is_deleted置 1,然后删除 Nginx 配置并 reload,审计日志记录deleted。
整个链路表明:NPM 对 Stream 的每一次增删改都会即时重写并重载 Nginx 配置,这也是管理界面操作能够"所见即所得"的底层保证。
模板解析:生成的 Nginx 配置长什么样
NPM 使用 Nunjucks 模板引擎渲染 Stream 配置,核心模板是 backend/templates/stream.conf。渲染后 TCP 转发块大致如下:
# ------------------------------------------------------------ # 8080 TCP: 1 UDP: 0 # ------------------------------------------------------------ server { listen 8080 reuseport ssl; listen [::]:8080 reuseport ssl; # Let's Encrypt SSL include conf.d/include/ssl-cache-stream.conf; include conf.d/include/ssl-ciphers.conf; ssl_certificate /etc/letsencrypt/live/npm-3/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/npm-3/privkey.pem; proxy_pass 192.168.1.10:22; access_log /data/logs/stream-3_access.log stream; error_log /data/logs/stream-3_error.log warn; # Custom include /data/nginx/custom/server_stream[.]conf; include /data/nginx/custom/server_stream_tcp[.]conf; }模板的关键设计点(全部有源码依据):
listen {{ incoming_port }} reuseport:使用reuseport提升多 worker 下的端口分发性能;当绑定了证书时追加ssl关键字;- IPv6 支持:
{% unless ipv6 -%} # {%- endunless -%} listen [::]:{{ incoming_port }}...——只有当ipv6开关开启时才会启用 IPv6 监听,否则整行被注释; proxy_pass {{ forwarding_host }}:{{ forwarding_port }}:即目标主机与端口;- 独立日志:
access_log /data/logs/stream-{{ id }}_access.log stream;和error_log /data/logs/stream-{{ id }}_error.log warn;,每个 Stream 拥有独立的访问/错误日志,便于排查; - 自定义配置挂载点:模板末尾会 include
/data/nginx/custom/server_stream[.]conf与server_stream_tcp[.]conf(UDP 块则 includeserver_stream_udp[.]conf),用户可以在不修改核心配置的前提下追加自定义指令(如proxy_timeout、proxy_buffer_size等)。注意这里的[.]写法是 Nginx 的 glob 语法,用于避免与点号通配冲突。
当 UDP 转发也开启时,模板会再生成一个listen {{ incoming_port }} udp reuseport的server块,逻辑与 TCP 块平行,但监听的是 UDP 流量。
SSL/TLS:为 TCP 流加密
Stream 同样支持 SSL 终止。SSL 配置由 _certificates_stream.conf 模板渲染:
- 若证书来自 Let's Encrypt(
provider == "letsencrypt"),证书路径为/etc/letsencrypt/live/npm-{{ certificate_id }}/fullchain.pem与对应的privkey.pem,并 includessl-cache-stream.conf(SSL 会话缓存)与ssl-ciphers.conf(密码套件); - 若是自定义证书,则从
/data/custom_ssl/npm-{{ certificate_id }}/目录读取。
值得说明的是,Stream 的证书支持是后来加入的能力:数据库迁移 20240427161436_stream_ssl.js 为stream表新增了certificate_id列(默认 0,即无证书)。因此在创建 Stream 时,SSL 标签页中的证书是可选的。
数据模型与权限控制
数据模型
models/stream.js 定义了stream表的 ORM 模型,要点如下:
- 布尔字段
is_deleted、enabled、tcp_forwarding、udp_forwarding在数据库中以 0/1 存储、在 API 层转换为 true/false; - 与
user(owner 所有者)和certificate(证书)建立关系映射(defaultAllowGraph = "[owner,certificate]"); - 默认排序为
incoming_port ASC。
权限
访问 Stream 相关接口需要对应权限:从 streams-create.json 可见,创建 Stream 需要管理员角色,或拥有permission_streams管理权限的user角色;其余操作(streams:get、streams:update、streams:delete、streams:list)在 internal/stream.js 中均通过access.can(...)校验。此外,当权限可见性不是all时,查询会自动加上owner_user_id过滤(internal/stream.js),即普通用户只能看到自己创建的 Stream。
实战注意事项与排查建议
- 端口冲突:NPM 会在同一主机上为所有 Stream 分配监听端口,如果两个 Stream 使用相同
incoming_port会导致 Nginx 启动失败。从 internal/stream.js 的 TODO 注释("At this point the existing ports should have been checked")可以看出,当前版本在创建阶段尚未对端口占用做强制校验,因此配置前应先确认端口未被其他服务或 Stream 占用。 - TCP 与 UDP 可并行:同一个
incoming_port可以同时勾选 TCP 与 UDP,模板会分别生成两个server块,互不冲突;但也意味着该端口上的 TCP 和 UDP 流量都会被接管,请确认没有其他服务依赖该端口。 - 查看日志:每个 Stream 的日志独立存放在
/data/logs/stream-{{ id }}_access.log与stream-{{ id }}_error.log,排查连接失败问题时优先查看对应 ID 的错误日志。 - 自定义配置:需要调优(如超时、缓冲区、
proxy_protocol等)时,可在容器的/data/nginx/custom/目录放置server_stream_tcp[.]conf或server_stream_udp[.]conf文件,模板会自动 include。 - 域名不参与路由:Stream 不按域名匹配,
domain_names不会被写入数据库(internal/stream.js),因此一个端口只能服务一个转发目标,多目标分流需要占用多个端口。
小结
Nginx Proxy Manager 的 Stream 功能,本质上是把 Nginx 的 stream 模块能力封装成了可视化的"端口转发"管理入口:你在界面上填写监听端口、目标主机与端口、选择 TCP/UDP 与可选证书,后端随即渲染出标准 Nginx stream 配置并热加载。对于游戏服务器、FTP、SSH 这类非 HTTP 服务,它提供了一条比手动维护 Nginx 配置更直观、更可控的运维路径。理解 stream.conf 模板与 internal/stream.js 的生命周期逻辑,是在生产环境中排障与深度定制的关键。
【免费下载链接】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),仅供参考