Gluetun 如何为 Torrent 应用配置端口转发
下载正常却没有入站连接?本文介绍 gluetun 端口转发配置、重连后自动同步新端口到 torrent 客户端,并验证端口是否真正可用。
为什么没有端口转发就无法建立入站连接
Gluetun 端口转发会请求 VPN 提供商,将其出口地址上的一个公网端口映射回您的容器。这是其他对等方向 torrent 客户端发起连接的唯一方式。没有此映射时,隧道状态正常,下载也能运行,但不会自动收到任何入站连接。每个正常建立的连接,都是由您的客户端先发起的。
其工作机制是 NAT(网络地址转换)。您的容器与其他许多客户共享提供商的出口地址。当客户端向外建立连接时,提供商会记录该连接,并通过隧道将响应发回。外部对等方发起的入站连接与任何已记录的连接都不匹配,因此数据包到达出口地址后会在那里被丢弃。您的客户端仍可连接到本身能够接受入站连接的每个对等方,因此下载可以完成,问题也不明显。做种时问题会暴露,因为做种客户端需要接受其他人的连接。
开放入站端口会带来两项变化。您可以更快加入 swarm,因为无法自行接受连接的对等方现在也能连接到您;同时,您也可以向这些对等方上传数据。
为什么大多数 VPN 提供商不提供端口转发
端口转发是共享地址上的稀缺资源。提供商会在一个出口 IP 上为一位客户保留一个端口号,并对该客户通过此端口执行的操作负责。多家大型提供商已移除这一功能,并将滥用处理称为原因。应将端口转发视为需要确认的服务类别,而不是一个简单的功能选项:请询问提供商当前是否提供端口转发、您的套餐是否支持,以及您能否实际选择支持该功能的服务器。
如果支持端口转发,该端口通常是动态的。端口属于 VPN 会话,而不是您的账户,因此每次重新连接后都可能获得不同的端口号。Private Internet Access 会分配一个签名端口,由 gluetun 刷新;上游文档说明,只要将 /gluetun 目录绑定挂载,使状态在重启后保留,您就可以在 60 天内保持使用同一个端口。ProtonVPN 会通过 NAT-PMP(NAT 端口映射协议)分配一个随机端口,但租期较短,必须持续续期。因此,在客户端中只设置一次端口通常无法长期生效。
gluetun 可以向哪些提供商请求端口
截至 gluetun v3.41.3(发布于 30 July 2026),原生集成会验证四个提供商名称:Private Internet Access、ProtonVPN、Perfect Privacy 和 PrivateVPN。使用 VPN_PORT_FORWARDING=on 启用该功能,默认值为 off。较早的指南使用 PORT_FORWARDING 或 PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING。在此版本中,这两个名称仍作为向后兼容名称有效,但即将移除。
两个提供商设置决定请求是否能够成功。ProtonVPN 需要付费套餐,并且必须启用 NAT-PMP:生成 WireGuard 配置时,在 VPN 选项中启用 NAT-PMP (Port Forwarding);使用 OpenVPN 时,则将 +pmp 附加到用户名后。Private Internet Access 的 OpenVPN 配置支持 PORT_FORWARD_ONLY,它会将服务器选择限制为支持端口转发的服务器,避免连接到从未支持该功能的服务器。WireGuard 和 OpenVPN 请求端口的方式不同,因此请先阅读提供商的页面,再进行选择。
如果 gluetun 使用自定义配置,而不是内置提供商配置,则 VPN_PORT_FORWARDING_PROVIDER 用于指定 gluetun 应调用的 API。Private Internet Access 的上游页面会将该变量与 VPN_PORT_FORWARDING_USERNAME 和 VPN_PORT_FORWARDING_PASSWORD 配对使用;这些变量提供端口请求所需的账户凭据。
在 Docker Compose 中启用 gluetun 端口转发
本节假设隧道已经正常工作。如果尚未正常工作,请先参阅通过 gluetun 路由 Docker 容器流量,确认下载可以运行后再返回本节。
services:
gluetun:
image: qmcgaw/gluetun:v3.41.3
container_name: gluetun
cap_add:
- NET_ADMIN
devices:
- /dev/net/tun:/dev/net/tun
ports:
- 8080:8080/tcp
- 8000:8000/tcp
volumes:
- ./gluetun:/gluetun
environment:
- VPN_SERVICE_PROVIDER=protonvpn
- VPN_TYPE=wireguard
- WIREGUARD_PRIVATE_KEY=${WIREGUARD_PRIVATE_KEY}
- VPN_PORT_FORWARDING=on
- TZ=Etc/UTC
restart: unless-stopped
qbittorrent:
image: lscr.io/linuxserver/qbittorrent:5.2.3
container_name: qbittorrent
network_mode: "service:gluetun"
environment:
- PUID=1000
- PGID=1000
- TZ=Etc/UTC
- WEBUI_PORT=8080
volumes:
- ./qbittorrent:/config
- ./downloads:/downloads
depends_on:
- gluetun
restart: unless-stopped固定镜像标签。qmcgaw/gluetun:latest跟踪 master 分支,而该分支中的端口转发内部实现正在为 v4 进行变更,因此未固定标签的镜像可能会在下一次 docker compose pull时改变行为。通过使用用于 Compose 密钥的环境文件将私钥移出 Compose 文件。
Gluetun 写入转发端口的位置
Gluetun 会在三个位置公开该端口,三者的值相同。
每次获取端口时,它都会记录一次日志。成功时日志行显示 port forwarded is 45678;请求未返回端口时显示 no port forwarded。
docker logs gluetun 2>&1 | grep -i "port forwarded"它会将端口号写入由 VPN_PORT_FORWARDING_STATUS_FILE 指定的文件,默认文件为 /tmp/gluetun/forwarded_port。该文件每行包含一个端口,文件权限为 0644,并设置为容器的 PUID 和 PGID 所有者。停止端口转发时,gluetun 会清空文件而不是删除文件,因此使用方读取到的是空文件,而不会遇到文件不存在的错误。
docker exec gluetun cat /tmp/gluetun/forwarded_port它还会通过控制服务器提供该值。控制服务器默认监听 :8000,并由 HTTP_CONTROL_SERVER_ADDRESS 设置。
curl -s http://127.0.0.1:8000/v1/portforward{"port":45678,"ports":[45678]}Gluetun 还会在 VPN 接口对应的自身防火墙中放行该端口,因此使用原生集成处理端口转发时不需要 FIREWALL_VPN_INPUT_PORTS。该变量用于另一种情况:提供商无法由 gluetun 查询,而您通过其他渠道获得了一个静态端口,必须手动放行该端口。
这三个位置中,只有一个具有持久性,另外两个没有。上游文档已将状态文件标记为在 v4.0.0 中弃用,而 GET /v1/openvpn/portforwarded 已经返回 301 Moved Permanently,并指向 /v1/portforward。新开发应读取控制服务器。
客户端每次重新连接时都必须接收端口
Torrent 客户端会将监听端口保存到自身配置中,并在重启后继续使用该端口。转发端口属于 VPN 会话。重新连接后,这两个端口号会不一致。因此,提供商会将端口映射到没有任何进程监听的端口,而客户端监听的端口也没有对应映射。
重新连接并不罕见,可能由容器重启、服务器变更、连接中断后由 gluetun 健康检查重启隧道,或租约续期失败导致。结果是,配置昨天还可以访问,今天却在没有任何日志报错的情况下无法访问。
因此,必须在 gluetun 获取端口时立即应用该端口。有两种接入方式,区别在于由哪个进程执行此操作。
选项 1:gluetun 使用 up 命令推送端口
VPN_PORT_FORWARDING_UP_COMMAND 在端口转发建立时运行,VPN_PORT_FORWARDING_DOWN_COMMAND 在端口转发断开时运行。Gluetun 会在运行命令前替换 {{PORT}}(第一个端口)、{{PORTS}}(所有端口,以逗号分隔)和 {{VPN_INTERFACE}}(隧道接口名称,默认为 tun0)。Shell 语法需要显式使用 /bin/sh -c 包装器。以下是上游 qBittorrent 示例,写成两个 compose 环境变量条目:
- VPN_PORT_FORWARDING_UP_COMMAND=/bin/sh -c 'wget -O- -nv --retry-connrefused --post-data "json={\"listen_port\":{{PORT}},\"current_network_interface\":\"{{VPN_INTERFACE}}\",\"random_port\":false,\"upnp\":false}" http://127.0.0.1:8080/api/v2/app/setPreferences'
- VPN_PORT_FORWARDING_DOWN_COMMAND=/bin/sh -c 'wget -O- -nv --retry-connrefused --post-data "json={\"listen_port\":0,\"current_network_interface\":\"lo\"}" http://127.0.0.1:8080/api/v2/app/setPreferences'该调用中的每个字段都有特定作用。listen_port 是新端口。current_network_interface 将 qBittorrent 绑定到隧道。将 random_port 设置为 false,可阻止 qBittorrent 在下次启动时选择自己的端口。将 upnp 设置为 false,可阻止它尝试通过不存在的路由器映射端口。
这种方法有两个要求。qBittorrent 的 Web UI 必须能从 gluetun 容器内部通过 127.0.0.1:8080 响应;当客户端共享 gluetun 的网络命名空间时,这通常会自动满足。还必须启用 Bypass authentication for clients on localhost(bypass_local_auth),因为该命令不会发送凭据。保留 down 命令是因为 qBittorrent 断开连接后并不总能重新建立端口转发。
该命令在 gluetun 容器内运行。该容器基于 Alpine,并包含 wget。该镜像中没有 curl。如果命令调用镜像中不存在的二进制文件,那么每次端口转发建立时都会失败。
选项 2:由 gluetun 外部的进程读取端口
另一种模式是在 gluetun 旁边运行一个小进程。该进程获取端口,再通过客户端自身的 API 将端口写入客户端。通过控制服务器读取:
port=$(curl -s http://127.0.0.1:8000/v1/portforward | jq -r .port)如果该进程能够访问文件,也可以直接读取文件。/tmp/gluetun/forwarded_port 位于 gluetun 容器内,因此 sidecar 需要在两个容器中都将共享卷挂载到 /tmp/gluetun;或者,将 VPN_PORT_FORWARDING_STATUS_FILE 指向你已经挂载的卷中的某个路径。
这里必须处理身份验证。在 v3.41.3 中,路由 GET /v1/portforward 属于名为 public 的默认角色,该角色具有 auth = "none",因此无需凭据即可响应;gluetun 会记录一条以 route GET /v1/portforward is unprotected by default, please set up authentication 开头的警告。上游项目将在后续版本中关闭这一入口。现在就定义一个角色,并将其写入绑定挂载到 /gluetun/auth/config.toml 的文件中:
roles = [
{ name = "qbittorrent", routes = ["GET /v1/portforward"], auth = "apikey", apikey = "myapikey" }
]使用 docker run --rm qmcgaw/gluetun:v3.41.3 genkey 生成密钥,并将其放入 X-API-Key 请求头。若不想挂载文件,HTTP_CONTROL_SERVER_AUTH_DEFAULT_ROLE 可通过一个 JSON 编码的环境变量完成相同的工作。未配置角色却发布了 8000 端口时,任何能够访问该端口的人都可以控制 VPN 状态。因此,在确定 如何从主机和其他容器访问 gluetun 时,应明确决定该端口的可达范围。
当客户端提供可由一次 wget 调用驱动的 API 时,选择 up 命令。它会在每个事件发生时准确执行一次,不会额外保持运行。当客户端需要登录流程、重写配置文件或重启时,选择外部进程。在 一个 gluetun 容器后运行的 arr 栈 中,通常只需要一个小型轮询器,因为只有 torrent 客户端需要该端口。
陷阱:共享网络命名空间不会设置监听端口
这是最浪费时间的问题。network_mode: "service:gluetun"会将客户端置于 gluetun 的网络命名空间中,因此客户端可以使用 VPN 地址、隧道路由和 gluetun 的防火墙规则。但这些设置都不会设置客户端的监听端口。gluetun 会在 VPN 接口上开放转发端口,发往该端口的数据包会到达此命名空间;如果客户端监听的是另一个端口,内核就没有目标可以交付这些数据包。此时连接会被拒绝或超时,但所有出站连接检查看起来都正常。转发端口和客户端监听端口是两个独立的数字,确保二者相同就是全部工作。
不要猜测,直接比较二者。以下两个命令针对同一个命名空间运行:
docker exec gluetun cat /tmp/gluetun/forwarded_port
docker exec gluetun wget -qO- http://127.0.0.1:8080/api/v2/app/preferences | grep -o '"listen_port":[0-9]*'还有一个设置会让人排查到错误方向。VPN_PORT_FORWARDING_LISTENING_PORT会使用 iptables 将转发端口上的入站流量重定向到固定的本地端口。上游文档建议不要将其用于 torrent 客户端,因为客户端会向 tracker 和对等节点公布自己的监听端口,因此整个 swarm 会获知错误的端口号。
如何证明转发端口可访问
客户端自身的连接指示器反映的是出站 tracker 连接,因此即使没有任何入站连接,指示器也可能显示为绿色。请使用您控制的监听器,并从隧道外部的网络进行测试。上游提供了一个小型工具。请先停止 torrent 客户端,因为两个进程不能绑定同一个端口。
docker stop qbittorrent
docker exec -it gluetun /bin/sh在容器内,将 amd64 替换为适用于您 CPU 架构的值,将 4567 替换为转发端口:
wget -qO port-checker https://github.com/qdm12/port-checker/releases/download/v0.4.0/port-checker_0.4.0_linux_amd64
chmod +x port-checker
./port-checker --listening-address=":4567"现在查找 gluetun 正在使用的出口地址。响应格式为 JSON,地址位于 public_ip 字段中。
curl -s http://127.0.0.1:8000/v1/publicip/ip从不在同一 VPN 上的设备打开 http://<that address>:4567。使用移动数据网络的手机即可。页面显示您的浏览器 IP 地址和用户代理,并且 port-checker 记录了匹配的请求,这表示入站 TCP 已到达该命名空间。超时表示入站连接未到达,原因位于客户端之外。使用 CTRL+C 停止该工具,使用 exit 退出 shell,然后重新启动客户端。此检查仅测试 TCP。DHT(分布式哈希表)和 uTP 流量使用同一端口号上的 UDP,而此测试不覆盖 UDP。
故障模式及日志中的提示
日志中完全没有端口行。 没有请求任何端口。使用 docker exec gluetun printenv | grep PORT_FORWARDING 确认变量确实已传入容器,因为将变量设置在错误的 compose 服务中是常见原因。
Gluetun 无法启动,并提示 provider 错误。 VPN_PORT_FORWARDING_PROVIDER 会根据支持的 4 个名称进行校验,因此拼写错误会导致容器停止,而不是静默运行且不进行端口转发。
日志显示 no port forwarded。 Gluetun 发出了请求,但 provider 没有返回任何内容。在 ProtonVPN 中,这通常表示生成的配置未启用 NAT-PMP,或者套餐不包含端口转发。在 Private Internet Access 中,这通常表示所选服务器不提供端口转发。
端口已分配,但无法建立入站连接。 使用上面的两个命令,将转发端口与客户端监听端口进行比较。如果两者一致,请确认客户端绑定到隧道接口,并关闭随机端口选项,因为该选项会在每次启动时改写监听端口。
up 命令似乎没有任何作用。 在容器内运行确切的命令以查看错误:docker exec gluetun /bin/sh -c '<your command>'。通常会得到 curl: not found,因为该镜像只提供 wget。
控制服务器返回 401 Unauthorized。 你定义了 auth 配置,但该角色没有列出你调用的路由。路由按方法和路径匹配,因此仅列出 /v1/portforward 的角色不包含 GET /v1/portforward。
Private Internet Access 在每次重启后分配不同的端口。 绑定挂载 /gluetun,使保存的端口状态在重启后仍然存在。没有该卷时,gluetun 每次都会请求新端口。
FAQ
为什么我的种子可以下载,却始终没有入站连接?
如果没有转发端口,VPN 提供商就没有 NAT 规则将任意端口上的入站数据包转发到您的隧道。因此,您未主动发起的连接会在出口地址处被丢弃。下载仍然正常,是因为客户端会主动建立这些连接,并且可以连接到任何具备可连接性的对等端。做种和加入 swarm 会受到影响,因为这两者都依赖其他节点连接到您。解决方法是选择提供端口转发的 VPN 提供商,在 gluetun 中配置 VPN_PORT_FORWARDING=on,然后将得到的端口设置为客户端的监听端口。
gluetun 是否支持任意 VPN 提供商的端口转发?
不支持。Gluetun v3.41.3 原生集成了 4 家提供商:Private Internet Access、ProtonVPN、Perfect Privacy 和 PrivateVPN。列表之外的提供商会导致 VPN_PORT_FORWARDING_PROVIDER 验证失败,容器也会在启动时停止。如果您的提供商通过自有控制面板分配静态端口,gluetun 无法代您请求该端口,但 FIREWALL_VPN_INPUT_PORTS 可以允许该固定端口通过 gluetun 的防火墙。提供商的政策可能变化,因此在购买套餐前,请查看当前的提供商页面。
每次重新连接后都必须更新端口吗?
是,而且应自动完成更新。转发端口属于 VPN 会话,因此容器重启、服务器变更或租约续期失败都可能产生新的端口号,而客户端仍会使用自身配置中保存的端口。您可以让 gluetun 通过 VPN_PORT_FORWARDING_UP_COMMAND 推送端口;该操作会在端口转发建立后立即运行。也可以运行一个小型进程,从控制服务器读取 GET /v1/portforward,再通过客户端 API 将该值写入客户端。
如何检查转发端口确实处于开放状态?
在 gluetun 的网络命名空间中,使用该端口运行监听程序,然后从 VPN 外部连接到它。先停止 torrent 客户端以释放端口,再使用 --listening-address=":<port>" 在 gluetun 容器内运行上游端口检查程序。通过 curl -s http://127.0.0.1:8000/v1/publicip/ip 获取出口地址,然后使用移动数据网络,在手机上打开 http://<address>:<port>。如果端口检查程序日志中出现请求,说明入站 TCP 已到达。如果请求超时,说明入站连接未到达,无论客户端自身的状态图标显示什么。