2407 字
12 分钟
PortLoom 教程:用一台 VPS 把 NAS 服务发布到公网

家里的 NAS 跑了 Jellyfin、相册、下载工具和各种管理页面,但家庭宽带没有稳定公网入口。常见处理办法是手写 SSH 反向隧道,再配一层 Nginx 或 Caddy。服务少的时候还能维护,服务一多,端口、域名、隧道状态和配置文件很快就会乱。

PortLoom 把这套流程做成了一个 Web 控制台。公网 VPS 负责接收 HTTPS 请求,NAS 上的 Agent 主动连接 VPS,再把流量送回本地服务。NAS 不需要开放入站端口,也不用在路由器上逐个做端口转发。

本文使用当前正式版 v0.2.1。下面的域名和 IP 都是示例,部署时换成自己的值。

PortLoom 的工作方式#

PortLoom 需要两台能运行 Docker Compose 的 Linux 主机:

位置安装内容用途
公网 VPSServer、专用 SSHD、CaddyWebUI、HTTPS 入口、路由网关和隧道入口
NAS 或内网服务器Agent主动连接 VPS,并把请求转发给本地服务

一次访问大致会经过这条链路:

浏览器
│ HTTPS
公网 VPS
Caddy → PortLoom Gateway
│ SSH 反向隧道
内网 NAS
PortLoom Agent → Jellyfin / 相册 / 管理页面

Agent 只向外连接 VPS 的 HTTPS 和 SSH 端口。公网请求沿已经建立的加密隧道返回 NAS,因此家庭网络不需要公网 IPv4,也不需要把 NAS 的服务端口直接暴露出去。

PortLoom 当前完整管理的是 HTTP/HTTPS 域名路由。它不会替你发布任意原始 TCP 端口。如果要暴露数据库、游戏端口或其他 TCP 服务,需要另找合适的方案。

开始前要准备什么#

1. 公网 VPS#

VPS 需要:

  • 一个稳定的公网 IP
  • Linux 系统
  • Docker Engine
  • Docker Compose v2
  • TCP 804432222 可以从公网访问

80/443 由 Caddy 处理 HTTPS,2222 是 PortLoom 专用 SSH 隧道端口。

2. NAS 或内网服务器#

NAS 需要能运行 Docker Compose,并且可以主动访问 VPS 的 4432222 端口。NAS 端不需要放行公网入站端口。

3. 域名和 DNS#

假设:

  • VPS 公网 IP:203.0.113.10
  • PortLoom 管理域名:portloom.example.com
  • 准备发布的 Jellyfin 域名:jellyfin.example.com

添加两条 DNS 记录:

portloom.example.com A 203.0.113.10
*.example.com A 203.0.113.10

通配符记录不是强制的,也可以给每个服务单独添加 A 记录。使用 Cloudflare 时,第一次部署建议先设为“仅 DNS”(灰云),避免 SSH 端口和证书签发被代理状态干扰。

确认解析结果:

Terminal window
nslookup portloom.example.com
nslookup jellyfin.example.com

两次查询都应该返回 VPS 的公网 IP。

在 VPS 安装 PortLoom Server#

先下载脚本并查看内容,再执行安装。这里固定使用 0.2.1,避免日后 latest 更新后教程与实际版本对不上。

Terminal window
curl -fsSLo install-server.sh https://docs.961121.xyz/install-server.sh
less install-server.sh
chmod 0700 install-server.sh
./install-server.sh \
--domain portloom.example.com \
--version 0.2.1

安装器会启动三个容器:

  • portloom-server:WebUI、API 和路由网关
  • portloom-sshd:专门用于反向隧道的受限 SSH 服务
  • portloom-caddy:自动申请和续期 HTTPS 证书

默认安装目录是:

~/.portloom/server

安装完成后,终端会显示:

  • WebUI 地址
  • 随机生成的管理员 Token
  • 配置文件目录
WARNING

管理员 Token 相当于控制台密码。不要放进截图、博客、聊天记录或公开仓库。建议立即保存到密码管理器,服务器上的 .env 也要保持私有。

用下面的命令查看容器状态:

Terminal window
cd ~/.portloom/server
docker compose --env-file .env -f compose.yml ps

三个容器都正常后,在浏览器打开:

https://portloom.example.com

输入安装器输出的管理员 Token 登录。

在 WebUI 添加 NAS#

登录后进入“添加代理”页面,填写:

字段示例
代理名称home-nas
Server URLhttps://portloom.example.com
公网 Server 主机portloom.example.com
SSH 隧道端口2222

点击“生成命令”。WebUI 会生成一条完整安装命令,其中包含一次性注册令牌和已经核验的 SSH 主机公钥。

这条命令只显示一次,不要自己拆开修改,也不要把它发到公开的地方。

在 NAS 安装 Agent#

SSH 登录 NAS,把 WebUI 生成的完整命令原样执行。它会下载 install-agent.sh,并安装与 Server 相同版本的 Agent。

安装过程会自动完成这些事情:

  1. 生成独立 Ed25519 密钥
  2. 写入 Server 的 SSH 主机公钥
  3. 使用一次性令牌注册 Agent
  4. 上传 Agent 公钥并更新受限授权文件
  5. 建立反向隧道
  6. 注册成功后,从环境文件中删除一次性令牌

默认安装目录是:

~/.portloom/agent

查看 Agent 状态:

Terminal window
cd ~/.portloom/agent
docker compose --env-file .env -f compose.yml ps
docker compose --env-file .env -f compose.yml logs --tail=100 agent

回到 WebUI 的“客户端”页面,应该能看到 home-nas,并且心跳时间会持续更新。

发布第一个服务#

下面以 Jellyfin 为例。假设 Jellyfin 在 NAS 上监听 8096

进入“路由”,点击“添加 HTTP 路由”,填写:

字段示例
名称Jellyfin
客户端home-nas
协议HTTP
公网域名jellyfin.example.com
本地主机127.0.0.1
本地端口8096

Agent 使用 host network,因此这里的 127.0.0.1 指 NAS 宿主机。如果目标服务在另一台局域网设备上,也可以填写对应的 LAN 地址,例如 192.168.1.20

保存后,PortLoom 会分配 VPS 回环端口、建立 SSH 反向转发,并把域名加入 Gateway。Caddy 只会为 WebUI 中已经启用的 HTTP 路由提供入口。

等待状态收敛后访问:

https://jellyfin.example.com

如果页面能打开,说明第一条路由已经打通。

怎么看懂三层状态#

PortLoom 没有把所有问题压成一个绿色圆点,而是分成三层:

Local#

Agent 能否访问 local_host:local_port

如果 Local down,先在 NAS 上检查:

Terminal window
curl -I http://127.0.0.1:8096

如果这里就不通,问题通常在本地服务、监听地址、端口或 NAS 防火墙,与 VPS 和域名无关。

Tunnel#

SSH 反向转发是否已经建立。Tunnel down 常见原因包括:

  • NAS 无法访问 VPS 的 2222
  • SSH 密钥或 known_hosts 权限不正确
  • VPS 防火墙没有放行 2222
  • 远程端口冲突

Public#

Agent 是否已经应用最新配置,以及内置 Gateway 是否已经收敛。

Public 显示 published,不代表外部 DNS 和 TLS 一定正确。如果 Local、Tunnel、Public 都正常,但公网仍打不开,就继续检查域名解析、VPS 的 80/443 防火墙和 Caddy 日志。

常见故障怎么排#

按链路从近到远检查,通常比反复重装更快。

1. 控制台打不开#

Terminal window
cd ~/.portloom/server
docker compose --env-file .env -f compose.yml ps
docker compose --env-file .env -f compose.yml logs --tail=100 server
docker compose --env-file .env -f compose.yml logs --tail=100 caddy

确认 DNS 指向 VPS,并检查 80/443 是否放行。

2. Agent 无法注册#

检查:

  • 一次性令牌是否已经使用或过期
  • NAS 和 VPS 的系统时间是否正确
  • NAS 是否能访问管理域名的 HTTPS
  • Agent 数据目录是否持久化且可写

注册命令失败后不要删除 Agent 状态目录。修好网络后,先重新执行同一条安装命令,安装器会尝试安全恢复。

3. Gateway 返回 404#

一般是 Host 没有匹配到路由。检查公网域名是否填写正确、路由是否启用,以及请求使用的域名是否和 WebUI 完全一致。

4. Gateway 返回 502#

这通常表示 VPS 侧没有可用的隧道监听。先看 Tunnel 状态和 Agent 日志,不要先删除路由或数据库。

5. 页面能开,但视频或大文件异常#

首页返回 200 只说明基础链路可用。媒体服务还要验证:

  • 大文件连续下载
  • HTTP Range 请求
  • WebSocket(如果应用需要)
  • 长连接
  • 实际上行带宽

备份和升级#

Server 使用 SQLite 保存配置。零停机备份时应该使用 SQLite 在线备份 API;如果直接复制数据库文件,应先停止 Server,或者同时一致地保存数据库、WAL 和 SHM 文件。

至少备份这些内容:

  • Server 数据目录
  • Agent 的 data/agent.json
  • Agent SSH 私钥和 known_hosts
  • Server 与 Agent 的 .envcompose.yml

升级前先看对应版本的 Release Notes,并固定明确的镜像标签。不要在生产环境长期依赖浮动的 latest。一次只升级一个组件,先 Server,再普通 Web Agent,最后处理高流量 Agent;每一步都检查心跳、Revision 和公网请求。

官方升级与回滚说明:

https://docs.961121.xyz/operations/backup-upgrade

几个安全细节#

PortLoom 已经把 Server、SSHD 和 Agent 拆开,并对容器做了非 root 运行、只读根文件系统和能力裁剪,但部署者仍需要保护好凭据:

  • 不要公开管理员 Token
  • 不要把一次性注册命令发给别人
  • .env、SSH 私钥和备份目录使用严格权限
  • 不要让 PortLoom 容器访问 Docker Socket
  • 管理域名只使用 HTTPS
  • 删除路由或重建数据前先备份
  • 不要把删除 SQLite、Agent 状态目录或重新注册当成首选排错手段

PortLoom 的管理域名同时承载 Agent API。如果还要叠加来源 IP 限制、VPN 或身份验证网关,必须保证 NAS 仍能访问 Agent 所需的 API;不要直接给整个域名套一层只适合浏览器的登录页,否则心跳和配置同步也会被拦截。

适合哪些场景#

PortLoom 适合把这些 Web 服务发布出去:

  • Jellyfin、Emby 等媒体服务
  • 自建博客和静态站点
  • NAS 相册、文件管理页面
  • Home Assistant 等家庭服务
  • 内网开发环境和测试页面

如果只发布一个临时页面,手写一条 SSH 隧道可能更快。服务多、域名多,或者希望在一个界面里看清本地、隧道和公网状态时,PortLoom 会省下不少重复配置。

项目和文档地址:

PortLoom 教程:用一台 VPS 把 NAS 服务发布到公网
https://blog.961121.xyz/posts/portloom-nas-public-access-tutorial/
作者
LOOK
发布于
2026-07-17
许可协议
CC BY-NC-SA 4.0