Tunnelto 源码解析 #13:自托管部署:Docker、环境变量、端口规划与单实例限制

前面几篇文章,我们主要分析 tunnelto 的源码运行机制:

复制代码
客户端如何启动
WebSocket 控制通道如何建立
ControlPacket 如何封装
远端请求如何转发到 localhost
服务端如何根据 Host 分发请求
StreamId 如何支持多路复用
鉴权和重连机制如何设计
本地调试面板如何记录请求

这一篇开始,我们从"源码阅读"转向"部署实战"。

也就是:

复制代码
如果不使用官方 tunnelto.dev 服务,
自己部署一套 tunnelto_server,
需要注意哪些端口?
需要配置哪些环境变量?
Dockerfile 是怎么设计的?
客户端如何连接自托管服务端?
单实例部署有什么限制?

这篇文章的标题是:

复制代码
Tunnelto 源码解析 #13:自托管部署:Docker、环境变量、端口规划与单实例限制

重点不是教你搭一个完整商业化内网穿透平台,而是从源码角度看清楚 tunnelto 自托管部署的关键点。


一、Tunnelto 自托管部署的整体模型

默认情况下,用户执行:

复制代码
tunnelto --port 8000

客户端会连接官方控制服务器。

默认控制地址类似:

复制代码
wss://wormhole.tunnelto.dev:10001/wormhole

如果要自托管,核心就是把这条控制连接改成你自己的服务端。

整体结构变成:

复制代码
外部浏览器
  ↓
你的公网服务器 remote port
  ↓
tunnelto_server
  ↓
WebSocket 控制通道
  ↓
本地 tunnelto 客户端
  ↓
localhost:8000

所以你需要准备两端:

复制代码
服务端:
  运行 tunnelto_server
  暴露公网访问端口
  暴露客户端控制端口

客户端:
  运行 tunnelto
  通过 CTRL_HOST / CTRL_PORT / CTRL_TLS_OFF 指向你的服务端

自托管的本质不是改客户端转发逻辑,而是把客户端连接的控制服务器换掉。


二、README 里的自托管说明

项目 README 中的 Host it yourself 部分给了非常简洁的自托管思路。

大致分为三步:

复制代码
1. 编译 musl target 的 tunnelto_server
2. 参考 Dockerfile 构建 Alpine 镜像
3. 把镜像部署到你的服务器

也就是说,官方自托管思路偏向:

复制代码
Rust 静态编译
  ↓
复制单个 tunnelto_server 二进制
  ↓
放进 Alpine 镜像运行

这是一种比较常见的 Rust 服务部署方式。

好处是镜像简单,运行时依赖少。


三、musl_build.sh:用 Docker 编译静态二进制

仓库里有一个:

复制代码
musl_build.sh

它的作用是用 clux/muslrust 镜像编译服务端:

复制代码
docker run \
  -v "cargo-cache:$HOME/.cargo/" \
  -v "$PWD:/volume" \
  --rm -it \
  clux/muslrust:stable \
  cargo build --bin tunnelto_server --release

它编译的是:

复制代码
tunnelto_server

而不是客户端 tunnelto

构建产物路径大致是:

复制代码
target/x86_64-unknown-linux-musl/release/tunnelto_server

后面的 Dockerfile 就会复制这个二进制。

这说明 tunnelto 自托管主要部署的是服务端,不是把客户端也打到服务器里。

客户端仍然运行在开发者本机。


四、Dockerfile:极简 Alpine 镜像

项目根目录的 Dockerfile 非常简单。

核心逻辑是:

复制代码
FROM alpine:latest

COPY ./target/x86_64-unknown-linux-musl/release/tunnelto_server /tunnelto_server

EXPOSE 8080
EXPOSE 5000
EXPOSE 10002

ENTRYPOINT ["/tunnelto_server"]

从这个 Dockerfile 可以看出几个点。

第一,它不在 Dockerfile 里编译 Rust。

也就是说,你需要先在外部把 tunnelto_server 编译好。

第二,镜像里只放服务端二进制。

这符合 Rust 静态编译后的部署风格。

第三,它暴露了三个端口:

复制代码
8080   远端公网请求入口
5000   客户端控制 WebSocket 入口
10002  内部网络服务入口

不过这里要特别注意:源码配置里的默认内部网络端口是 NET_PORT,默认值是 6000。而 Dockerfile 暴露的是 10002。如果你真的要使用内部网络服务,需要显式确认容器暴露端口、环境变量和实际监听端口是否一致。

单实例部署时,内部网络服务不是最关键的;真正必须关注的是:

复制代码
remote port
control port

五、服务端有三类端口

tunnelto_server/src/config.rsmain.rs 可以看出,服务端主要有三类端口。

1. Remote Port:远端公网请求端口

环境变量:

复制代码
PORT

默认值:

复制代码
8080

作用:

复制代码
外部浏览器访问 tunnel 域名时,请求进入这个端口。

例如本地测试时:

复制代码
curl -H 'abc.localhost' http://localhost:8080/some_path

这个 8080 就是 remote port。

它对应源码中的:

复制代码
CONFIG.remote_port

服务端会用 TcpListener 监听这个端口。


2. Control Port:客户端控制通道端口

环境变量:

复制代码
CTRL_PORT

默认值:

复制代码
5000

作用:

复制代码
tunnelto 客户端连接 /wormhole WebSocket 的端口。

例如客户端自托管测试时:

复制代码
CTRL_HOST=localhost CTRL_PORT=5000 CTRL_TLS_OFF=1 tunnelto -p 8000

客户端连接的是:

复制代码
ws://localhost:5000/wormhole

这个端口对应服务端的控制服务器,也就是前面分析过的:

复制代码
control_server.rs

3. Internal Network Port:实例间通信端口

环境变量:

复制代码
NET_PORT

默认值:

复制代码
6000

作用:

复制代码
多实例部署时,服务端实例之间查询某个 host 由哪个实例负责。

这个端口对应:

复制代码
network::spawn()

它是为了多实例 gossip / 内部发现服务准备的。

如果你只是单实例部署,可以先不用重点关注它。

但如果你部署多个 tunnelto_server 实例,它就会变得重要。


六、三个端口不要混淆

初次部署 tunnelto_server 时,最容易混淆的就是端口。

可以记住:

复制代码
PORT:
  给外部浏览器访问 tunnel 用

CTRL_PORT:
  给 tunnelto 客户端建立 WebSocket 控制通道用

NET_PORT:
  给多个 tunnelto_server 实例之间通信用

用一张图表示:

复制代码
外部浏览器
  ↓
PORT=8080
  ↓
tunnelto_server


本地 tunnelto 客户端
  ↓
CTRL_PORT=5000 /wormhole
  ↓
tunnelto_server


其他 tunnelto_server 实例
  ↓
NET_PORT=6000
  ↓
当前 tunnelto_server

如果你只有一个服务端实例,最核心的是:

复制代码
PORT
CTRL_PORT

七、本地测试命令如何理解?

README 里给了一个本地测试流程。

服务端启动:

复制代码
ALLOWED_HOSTS="localhost" cargo run --bin tunnelto_server

客户端连接本地服务端:

复制代码
CTRL_HOST="localhost" CTRL_PORT=5000 CTRL_TLS_OFF=1 cargo run --bin tunnelto -- -p 8000

测试远端请求:

复制代码
curl -H '.localhost' "http://localhost:8080/some_path?with=somequery"

这三步分别对应:

复制代码
第一步:
  启动自托管服务端

第二步:
  启动客户端,并让客户端连接本地控制服务器

第三步:
  模拟外部用户访问 remote port

这里最关键的是:

复制代码
CTRL_TLS_OFF=1

因为本地测试没有配置 TLS,所以客户端要使用:

复制代码
ws://localhost:5000/wormhole

而不是:

复制代码
wss://localhost:5000/wormhole

如果不设置 CTRL_TLS_OFF=1,客户端会尝试使用安全 WebSocket,连接本地非 TLS 服务就会失败。


八、ALLOWED_HOSTS:允许哪些根域名建立 tunnel

服务端配置里有:

复制代码
ALLOWED_HOSTS

它表示允许作为 tunnel 根域名的 host。

例如:

复制代码
ALLOWED_HOSTS="localhost"

就表示允许:

复制代码
xxx.localhost

这样的 tunnel host。

如果你部署到自己的域名,比如:

复制代码
tunnel.example.com

可能会配置:

复制代码
ALLOWED_HOSTS="tunnel.example.com"
TUNNEL_HOST="tunnel.example.com"

这样服务端才能从 Host Header 中识别:

复制代码
abc.tunnel.example.com

并提取:

复制代码
abc

作为子域名。

如果 ALLOWED_HOSTS 没配对,远端请求可能会被认为是非法 Host。


九、TUNNEL_HOST:服务端生成公网域名时使用

服务端还有一个配置:

复制代码
TUNNEL_HOST

默认值:

复制代码
tunnelto.dev

它用于生成返回给客户端的完整公网 hostname。

比如客户端请求子域名:

复制代码
demo

如果:

复制代码
TUNNEL_HOST="tunnel.example.com"

那么服务端返回的 hostname 可能就是:

复制代码
demo.tunnel.example.com

所以:

复制代码
ALLOWED_HOSTS 影响服务端接收哪些 Host
TUNNEL_HOST   影响服务端告诉客户端哪个公网域名

这两个值通常应该保持一致或至少逻辑一致。

否则可能出现:

复制代码
客户端看到的 hostname 是 demo.xxx
但远端入口校验的 allowed host 是 yyy

导致访问失败。


十、BLOCKED_SUB_DOMAINS:保留子域名

服务端配置中还有:

复制代码
BLOCKED_SUB_DOMAINS

它是一个逗号分隔列表。

例如:

复制代码
BLOCKED_SUB_DOMAINS="www,api,admin,dashboard,wormhole"

这些子域名不能被普通用户占用。

原因很简单:它们通常有系统用途。

例如:

复制代码
www.tunnel.example.com
api.tunnel.example.com
admin.tunnel.example.com
wormhole.tunnel.example.com

如果允许普通 tunnel 用户申请这些名字,就可能和系统入口冲突。

在服务端鉴权阶段,如果用户请求的 subdomain 命中这个列表,会被视为不可用。


十一、MASTER_SIG_KEY:签名密钥

服务端配置里有:

复制代码
MASTER_SIG_KEY

它用于生成和验证 ReconnectToken。

如果没有设置,服务端会生成临时签名密钥。

这会带来一个问题:

复制代码
服务端重启后,旧 token 全部失效。

如果你只是本地测试,可以不配置。

如果是生产部署,建议配置稳定的 MASTER_SIG_KEY

它应该是符合源码要求的 hex 字符串,并且长度正确。

可以理解为:

复制代码
MASTER_SIG_KEY 是服务端签发短期恢复凭证的根密钥。

注意,不要把它提交到公开仓库。


十二、BLOCKED_IPS:屏蔽客户端 IP

服务端还支持:

复制代码
BLOCKED_IPS

它也是逗号分隔。

作用是阻止某些 IP 连接控制服务器。

例如:

复制代码
BLOCKED_IPS="1.2.3.4,5.6.7.8"

控制服务器在处理 /wormhole 连接时,会读取客户端 IP,并检查是否在 blocked list 中。

如果在,就关闭连接。

这属于比较基础的访问控制能力。


十三、HONEYCOMB_API_KEY:可观测性配置

服务端配置里还有:

复制代码
HONEYCOMB_API_KEY

如果设置了这个值,服务端会配置 Honeycomb tracing。

如果没有设置,也可以正常运行,只是没有 Honeycomb 上报。

这说明可观测性是可选能力,不是自托管运行的必要条件。

本地部署或个人使用可以先忽略它。


十四、FLY_APP_NAME 与多实例内部发现

服务端配置中还有:

复制代码
FLY_APP_NAME
FLY_ALLOC_ID

如果设置了 FLY_APP_NAME,源码会生成:

复制代码
global.{app_name}.internal

作为内部 gossip DNS host。

这是为了 Fly.io 的私有网络设计的。

如果没有 FLY_APP_NAME,network 模块会提示 gossip mode disabled。

也就是说,默认自托管单实例时,这部分不会真正工作。

这也解释了为什么 README 里说官方托管版本是基于 Fly.io Private Networking 做了分布式系统,而简单自托管版本不支持多个服务器的集中协调。


十五、客户端如何连接自托管服务端?

客户端配置在:

复制代码
tunnelto/src/config.rs

它支持三个控制服务器相关环境变量:

复制代码
CTRL_HOST
CTRL_PORT
CTRL_TLS_OFF

默认情况下:

复制代码
CTRL_HOST 默认指向 wormhole.tunnelto.dev
CTRL_PORT 默认是 10001
没有 CTRL_TLS_OFF 时使用 wss

自托管时,你需要覆盖它们。

本地测试:

复制代码
CTRL_HOST=localhost \
CTRL_PORT=5000 \
CTRL_TLS_OFF=1 \
tunnelto --port 8000

如果你的服务端部署在公网服务器:

复制代码
CTRL_HOST=your-server.example.com \
CTRL_PORT=5000 \
CTRL_TLS_OFF=1 \
tunnelto --port 8000

如果你给控制端口配置了 TLS 和反向代理,则可以不设置 CTRL_TLS_OFF,让客户端使用:

复制代码
wss://your-server.example.com:5000/wormhole

但如果没有 TLS,就必须设置:

复制代码
CTRL_TLS_OFF=1

否则客户端会尝试 wss,连接可能失败。


十六、客户端连接控制端口,浏览器访问远端端口

这里再强调一次,因为很容易搞错。

客户端连的是:

复制代码
CTRL_HOST:CTRL_PORT

外部浏览器访问的是:

复制代码
你的域名:PORT

例如:

复制代码
CTRL_PORT=5000
PORT=8080

那么:

复制代码
tunnelto 客户端连接:
  ws://your-server:5000/wormhole

外部请求访问:
  http://abc.your-domain:8080

如果你前面接了 Nginx 或 Caddy,可以把外部 80/443 转发到容器的 8080,把控制通道域名转发到 5000。

一种常见规划是:

复制代码
公网 80/443:
  处理 *.tunnel.example.com
  反代到 tunnelto_server:8080

控制通道:
  wormhole.tunnel.example.com 或单独端口
  反代到 tunnelto_server:5000

不过具体反代配置要结合你的域名和 TLS 方案。


十七、单实例 Docker 部署示例

假设你已经编译好了:

复制代码
target/x86_64-unknown-linux-musl/release/tunnelto_server

可以先构建镜像:

复制代码
docker build -t my-tunnelto-server .

然后运行:

复制代码
docker run -d \
  --name tunnelto-server \
  -p 8080:8080 \
  -p 5000:5000 \
  -e ALLOWED_HOSTS="localhost" \
  -e TUNNEL_HOST="localhost" \
  -e CTRL_PORT="5000" \
  -e PORT="8080" \
  -e BLOCKED_SUB_DOMAINS="www,api,admin,wormhole" \
  my-tunnelto-server

本地客户端连接:

复制代码
CTRL_HOST=localhost \
CTRL_PORT=5000 \
CTRL_TLS_OFF=1 \
tunnelto --port 8000

然后你可以用 curl 模拟访问:

复制代码
curl -H 'abc.localhost' http://localhost:8080/

这里的核心是:

复制代码
客户端连接 5000
远端请求访问 8080
Host Header 中包含子域名

十八、为什么测试时要手动传 Host Header?

本地测试时,你可能没有真的配置 DNS。

例如:

复制代码
abc.localhost

未必能自动解析到你的服务端。

所以 README 里的测试方式是:

复制代码
curl -H '.localhost' "http://localhost:8080/some_path?with=somequery"

本质上是:

复制代码
TCP 连接到 localhost:8080
但 HTTP Host Header 伪装成某个 tunnel host

因为服务端分发请求依赖的是:

复制代码
Host Header

而不是 TCP 连接目标地址本身。

这也是第 #7 篇讲过的内容:

复制代码
Host Header -> subdomain -> ConnectedClient

本地测试时,只要 Host Header 正确,服务端就可以走同样的分发逻辑。


十九、域名部署时需要通配符 DNS

如果你想真正公网使用,一般需要配置通配符 DNS。

例如你想使用:

复制代码
*.tunnel.example.com

那么 DNS 里需要配置:

复制代码
*.tunnel.example.com -> 你的服务器 IP

这样:

复制代码
abc.tunnel.example.com
demo.tunnel.example.com
test.tunnel.example.com

都会解析到你的 tunnelto_server。

然后服务端根据 Host Header 提取:

复制代码
abc
demo
test

再分发给不同客户端。

如果没有通配符 DNS,用户访问随机子域名时,DNS 层就可能已经失败,根本到不了 tunnelto_server。


二十、反向代理与 TLS

tunnelto_server 自身的 remote TCP listener 是一个 TCP 入口。

如果你希望用户通过:

复制代码
https://abc.tunnel.example.com

访问,通常需要在前面放一个支持 TLS 的反向代理或负载均衡器。

例如:

复制代码
Caddy / Nginx / Traefik / 云负载均衡
  ↓
tunnelto_server:8080

同时控制通道也可以通过反向代理暴露为:

复制代码
wss://wormhole.tunnel.example.com/wormhole

然后客户端就可以不设置 CTRL_TLS_OFF

但如果你只是本地或内网测试,没有 TLS,则使用:

复制代码
CTRL_TLS_OFF=1

这会让客户端使用:

复制代码
ws://...

二十一、单实例限制:README 里的关键提醒

README 明确提醒:简单自托管实现不支持多个运行中的服务端实例之间的集中协调。

这句话非常重要。

原因是 tunnelto 的运行依赖两张内存表:

复制代码
Connections
ACTIVE_STREAMS

它们都在当前服务端实例内存中。

客户端连接到实例 A 后:

复制代码
实例 A:
  abc -> client_A

如果外部请求却被负载均衡分发到实例 B:

复制代码
实例 B:
  没有 abc -> client_A

实例 B 就找不到这个 tunnel。

于是请求可能返回:

复制代码
Tunnel Not Found

所以单实例部署时没有这个问题。

但多实例部署时,必须保证:

复制代码
客户端连接的实例
和远端请求进入的实例
能够找到彼此

要么请求始终打到同一个实例,要么实现跨实例发现与代理。


二十二、为什么多实例会复杂?

多实例难点在于:客户端长连接是有状态的。

假设:

复制代码
client_A 连接到了实例 A

那么实例 A 内存里有:

复制代码
host -> client_A
client_id -> client_A
WebSocket tx

如果请求进入实例 B,实例 B 本地没有这条 WebSocket。

它只能做两件事:

复制代码
1. 返回 Tunnel Not Found
2. 找到实例 A,并把 TCP 流代理过去

tunnelto 源码里确实有 network 模块用于实例发现和代理。

但 README 也提示,官方托管版本依赖 Fly.io Private Networking 做分布式协调。

对于普通自托管用户来说,最稳妥的是先部署单实例。


二十三、network 模块的作用

network/mod.rs 中有一个函数:

复制代码
instance_for_host(host)

它会尝试查找哪个实例服务某个 host。

大致逻辑是:

复制代码
1. 根据 FLY_APP_NAME 生成 gossip DNS host
2. 通过 DNS 查找所有实例 IP
3. 向每个实例的 internal network port 发请求
4. 询问它是否服务某个 host
5. 找到后返回该实例 IP 和 client_id

如果没有配置 FLY_APP_NAME,源码会提示:

复制代码
gossip mode disabled

这意味着普通环境默认不会启用多实例发现。

这也是为什么自托管单实例更简单。


二十四、单实例部署的实际建议

如果你只是自己用,或者初期做 MVP,我建议这样部署:

复制代码
1 台 VPS
1 个 tunnelto_server 实例
1 个通配符域名
1 个反向代理处理 TLS
remote port 走 80/443 反代到 8080
control port 走 wss 反代到 5000

例如:

复制代码
*.tunnel.example.com -> VPS IP
wormhole.tunnel.example.com -> VPS IP

然后:

复制代码
Caddy/Nginx:
  *.tunnel.example.com -> tunnelto_server:8080
  wormhole.tunnel.example.com -> tunnelto_server:5000

客户端:

复制代码
CTRL_HOST=wormhole.tunnel.example.com tunnelto --port 8000

如果没有 TLS:

复制代码
CTRL_HOST=你的服务器IP CTRL_PORT=5000 CTRL_TLS_OFF=1 tunnelto --port 8000

二十五、自托管时的鉴权问题

当前源码默认使用:

复制代码
AuthDbService

它会连接 DynamoDB 进行 key、账号、域名、订阅校验。

如果你只是自托管测试,可能不想接 DynamoDB。

源码中在 main.rs 里有注释:

复制代码
To disable all authentication:
pub static ref AUTH_DB_SERVICE: crate::auth::NoAuth = crate::auth::NoAuth;

也就是说,可以把真实鉴权服务换成:

复制代码
NoAuth

NoAuth 会直接返回:

复制代码
AuthResult::Available

这样本地测试就不需要 DynamoDB。

不过,如果你准备正式开放给别人使用,还是应该设计自己的鉴权系统。

否则任何人只要能连接你的控制服务器,就可能创建 tunnel。


二十六、部署时容易踩的坑

1. CTRL_PORT 和 PORT 搞反

客户端应该连:

复制代码
CTRL_PORT

浏览器应该访问:

复制代码
PORT

搞反就会连不上。

2. 忘记设置 CTRL_TLS_OFF

本地没有 TLS 时,客户端默认可能尝试 wss

要设置:

复制代码
CTRL_TLS_OFF=1

3. ALLOWED_HOSTS 没配

如果 ALLOWED_HOSTS 没包含你的根域名,远端请求可能被判定为 invalid host。

4. DNS 没有通配符

随机子域名访问不到服务器。

5. TUNNEL_HOST 和实际域名不一致

客户端看到的公网 URL 和服务端实际可访问域名不一致。

6. MASTER_SIG_KEY 未固定

如果你依赖 ReconnectToken,服务重启后旧 token 会全部失效。

7. 多实例负载均衡没有粘性或内部发现

客户端连接实例 A,请求进入实例 B,导致 Tunnel Not Found。

8. Dockerfile 暴露端口和源码默认端口不一致

Dockerfile 暴露了 internal network 端口 10002,但源码默认 NET_PORT6000

如果你使用内部网络服务,要显式统一。


二十七、一个简化版 docker-compose 示例

仓库里没有现成的 docker-compose 文件,但可以根据 Dockerfile 和环境变量自己写一个简化版本。

例如:

复制代码
version: "3.8"

services:
  tunnelto-server:
    image: my-tunnelto-server:latest
    container_name: tunnelto-server
    restart: unless-stopped
    ports:
      - "8080:8080"
      - "5000:5000"
    environment:
      ALLOWED_HOSTS: "localhost"
      TUNNEL_HOST: "localhost"
      PORT: "8080"
      CTRL_PORT: "5000"
      NET_PORT: "6000"
      BLOCKED_SUB_DOMAINS: "www,api,admin,wormhole"
      MASTER_SIG_KEY: "替换成你自己的hex密钥"

本地测试客户端:

复制代码
CTRL_HOST=localhost \
CTRL_PORT=5000 \
CTRL_TLS_OFF=1 \
tunnelto --port 8000

注意,这只是单实例测试配置。

生产环境还需要考虑:

复制代码
TLS
域名
通配符 DNS
反向代理
鉴权
日志
监控
安全

二十八、从源码角度看自托管部署链路

现在把自托管完整链路串起来。

服务端启动

复制代码
Config::from_env()
  ↓
读取 ALLOWED_HOSTS / TUNNEL_HOST / PORT / CTRL_PORT / NET_PORT
  ↓
control_server::spawn(CTRL_PORT)
  ↓
network::spawn(NET_PORT)
  ↓
TcpListener::bind(PORT)
  ↓
等待远端请求

客户端启动

复制代码
Config::get()
  ↓
读取 CTRL_HOST / CTRL_PORT / CTRL_TLS_OFF
  ↓
拼接 control_url
  ↓
ws://CTRL_HOST:CTRL_PORT/wormhole
  ↓
发送 ClientHello

请求转发

复制代码
浏览器访问 abc.TUNNEL_HOST
  ↓
请求进入 PORT
  ↓
remote.rs 解析 Host
  ↓
Connections::find_by_host("abc")
  ↓
通过客户端 WebSocket 转发
  ↓
客户端写入 localhost:8000

所以自托管时要保证三件事同时正确:

复制代码
客户端能连上控制端口
浏览器能访问远端端口
Host Header 能匹配 ALLOWED_HOSTS / TUNNEL_HOST

二十九、这一篇的核心结论

tunnelto 自托管部署的核心可以总结成一句话:

复制代码
服务端运行 tunnelto_server,
通过 PORT 暴露远端公网请求入口,
通过 CTRL_PORT 暴露客户端 WebSocket 控制入口,
通过 ALLOWED_HOSTS 和 TUNNEL_HOST 决定可用域名,
客户端再用 CTRL_HOST、CTRL_PORT、CTRL_TLS_OFF 指向这个自托管控制服务器。

更简单地说:

复制代码
PORT 是给浏览器访问的
CTRL_PORT 是给 tunnelto 客户端连接的
NET_PORT 是给多实例内部通信准备的
ALLOWED_HOSTS 决定哪些 Host 可以被路由
TUNNEL_HOST 决定返回给客户端的公网域名

自托管初期,建议先做单实例。

因为 tunnelto 的简单自托管模式不提供完整的多实例集中协调。

如果你直接上多个实例,又没有粘性路由或内部发现,请求很容易进入没有对应客户端连接的实例,最终出现:

复制代码
Tunnel Not Found

所以最稳妥的路径是:

复制代码
单实例跑通
  ↓
配置域名和 TLS
  ↓
接入鉴权
  ↓
再考虑多实例和内部发现

三十、下一篇预告

下一篇我们继续分析多实例扩展:

Tunnelto 源码解析 #14:多实例扩展:Fly.io Private Networking、内部发现与跨实例代理

下一篇会重点研究:

复制代码
network/mod.rs
network/server.rs
network/proxy.rs
FLY_APP_NAME
global.{app}.internal
instance_for_host()
serves_host()
proxy_stream()
为什么官方托管版本可以多实例运行
为什么普通自托管需要谨慎处理多实例

如果说本篇讲的是"单实例如何部署",下一篇讲的就是"多个服务端实例之间如何互相发现并转发请求"。

相关推荐
AI科技星1 小时前
第三卷:质数王朝志(全卷定稿)
c语言·开发语言·汇编·electron·概率论
kyle~2 小时前
DDS分布式实时系统---自省机制
开发语言·分布式·机器人·c#·接口·ros2
yujunl2 小时前
Integrated Security=True(Windows 集成身份验证)
开发语言
右耳朵猫AI2 小时前
Python周刊2026W23 | Polars 1.41、PyPy v7.3.23、Python 3.15、httpx2、dj-lite-tenant
开发语言·python
昭昭颂桉a2 小时前
TypeScript 前端的必修课,从 JS 到 TS
开发语言·前端·javascript·typescript
何以解忧,唯有..2 小时前
Go 语言安装与环境配置完整指南
开发语言·后端·golang
Java面试题总结2 小时前
MarkItDown 再次登顶GitHub榜
开发语言·c#·github
学逆向的2 小时前
C++模板
开发语言·c++·网络安全
nwsuaf_huasir2 小时前
matlab绘制尺寸和字体合适的图片插入到latex的方法
android·开发语言·matlab