把跑在自己电脑上的服务,给外面的客户一个能直接打开的网址——这件事以前的标准答案是"弄台云服务器 + 配 Nginx + 申请证书 + 开端口"。一整套下来,最轻也要半天,还得持续付服务器的钱。
我们用另一个路子把内部服务透成了公网 HTTPS,过程中踩的坑比配置本身多得多。这篇把真正会卡住人的六类问题完整写出来,只写我们实际遇到的。
需求很具体:一台机器上跑着本地 HTTP 服务(比如 127.0.0.1:8780 的后端),外部客户需要访问它。这台机器在内网,没有公网 IP,也不想为了一个演示链接去买服务器。
解决方案是把本机端口通过隧道服务商的 ingress 节点代理出去,对外表现为一个 https://<节点名>.<网络名>.ts.net 的域名。三个"免"同时成立:免服务器、免公网 IP、免证书。配置持久化在服务自身的状态里,机器重启后自动恢复(前提是保持登录)。
| 维度 | 自建云服务器方案 | Funnel 方案 |
|---|---|---|
| 前置成本 | 买机器 + 备案/解析 + 证书 | 本机装客户端即可 |
| 配置工作量 | Nginx/反代 + 证书签发 | 一条命令 |
| 出口位置 | 自己选的机房 | 服务商的 ingress(我们实测在美国西海岸) |
| 适合场景 | 面向国内用户的正式业务 | 演示、验收、内部联调、临时交付 |
先把边界说清楚:它是"快速把内部服务拿出来给人看"的利器,不是面向国内正式业务的最终形态。后面第九节会讲原因。
$ts = "C:\Program Files\Tailscale\tailscale.exe" & $ts status # 看本节点名/IP/登录账号 & $ts serve status # "No serve config" = 从未配置过 & $ts funnel status # 同上
确认已登录、有节点名之后,开通就是一条命令:
& $ts funnel --bg 8780 # 把 443 代理到本地 8780,后台常驻
成功时会输出形如 Available on the internet: https://<节点名>.<网络名>.ts.net/ 加一行 |-- proxy http://127.0.0.1:8780,以及"已后台运行"的提示。关闭用 --https=443 off。
只有当你看到 "Funnel is not enabled on your tailnet" 时,才需要去服务商后台的这个节点页面点一下启用。多数情况下直接成功,不用绕这一步。
这是最先撞上的问题,也是最容易修错方向的一个。status、serve、funnel 全部报:
401 Unauthorized: Tailscale already in use by NT AUTHORITY\SYSTEM, pid <X>
注意,即使你是管理员身份照样会 401——它不是权限问题。
某次调用卡住没退出的客户端进程会一直占用本地接口,后续所有命令都被拒。修法是找到并结束这个进程:
Get-CimInstance Win32_Process -Filter "Name='tailscale.exe'" | Select-Object ProcessId,CreationDate,CommandLine Stop-Process -Id <PID> -Force
结束后命令立刻恢复。关键:不要误杀后台服务进程和托盘客户端进程——只杀那个命令行进程。
现象和上面一模一样,但成因完全不同:你通过远程命令行进来的是用户 A,而引擎进程跑在桌面会话下的用户 B(比如运维账号和你自己的账号不是同一个)。跨会话访问本地接口会被拒绝。
这个根因下,反复重试命令只会不断打断别人的登录态,越修越乱。正确做法是换视角验证(见下一节)。区分方法很简单:杀进程能好的是 A,杀进程也好不了的是 B。
链接开出来了,从本机 curl 却报 Could not resolve host,第一反应是"没通",其实不一定。部分内网机器的解析器就是解析不了那类域名,刷新缓存也没用,而公网 DNS 是完全正常的。
所以验证必须把域名强制指向 ingress 的实际 IP,绕开本机 DNS:
# 1) 看 DNS 记录(开通后约 60 秒生效)
nslookup <节点名>.<网络名>.ts.net 223.5.5.5
# 2) 端到端:强制走 443 指定 ingress IP
curl -s -o /dev/null -w '%{http_code} %{content_type}\n' --max-time 15 \
--resolve <节点名>.<网络名>.ts.net:443:<ingress_ip> \
https://<节点名>.<网络名>.ts.net/healthz
更稳妥的是从一台真正有外网的机器去测。我们主控机本身没有直连外网,就从集群里的 Linux 节点(有真外网)测;本机自测的结果不可信。
有台机器一直登不上,后台看到"刚上线又掉了",反复很多次。最后定位到原因不是网络,而是我们自己把它打断了。登录/在线期间执行下面任何一条,都会让节点在后台闪一下又掉:
铁律:一旦有人正在桌面上手动登录,远程命令行一侧就只许"读",不许"写"——不停止服务、不杀进程、不重复执行登录。这条纪律比任何参数都重要。
我们用一台新的 Windows 机器实操过一遍,直接给结论:让现场人员在这台机器的桌面会话里点托盘图标登录(浏览器完成),不要在远程命令行里硬推登录命令——那是逆着设计走的,会反复失败。
正确顺序:
远程命令行一侧永远查不到状态(原因见第三节根因 B),所以验证要靠"同伴视角"——在任何一台已入网的机器上执行状态查询,能看到它的 IP 和名字、且不带离线标记,才算在线。
一个很隐蔽的坑:状态查询的 JSON 输出里,同伴列表的字段名是单数的(Peer),值是一个以节点 ID 为键的字典,而不是复数形式(Peers)的数组。按直觉用复数去取,会得到一个空列表,然后你误判成"网络里没有其他机器",排查方向直接跑偏。
判定在线的三件套:在线标志为真、激活标志为真、并且有一条直连地址(中继地址有值只是备用通道)。补一条 ping 实测可达最稳。
HostName = DESKTOP-XXXXXXX DNSName = desktop-xxxxxxx.<网络名>.ts.net TailscaleIPs = 100.119.247.103 (+fd7a:...) Online = True Active = True CurAddr = [240e:...]:41641 Relay = sfo KeyExpiry = 2027-03-23T14:05:52Z
进程数也不是越多越有问题:正常就是两个后台服务进程(主进程 + 子进程)加一个托盘客户端进程,这不叫多实例冲突,别乱杀。
想确认"这台机器到底登录过没有",得看机器级的真实状态目录——它不在用户目录下。很多人在 %LOCALAPPDATA% 里翻半天,那里通常只有头像缓存,没有任何状态文件。
真正的状态在机器级目录(C:\ProgramData\Tailscale\),里面包含状态文件、配置档、中继地图和日志。注意:状态文件是 JSON 且含私钥,属于敏感信息,严禁回显、复制或外传。判断是否登录过,看它的修改时间是否变新就够了。
图形界面登录时不指定名称,节点名就等于计算机名的小写形式。如果你之前用别名参数试过登录,后台会多出一个带那个别名标签的空节点,需要去控制台手动删掉,否则以后自己都分不清哪个是活的。
给客户的地址必须以实际节点名和实际 IP 为准,不要沿用自己随口起的别名——否则交付的瞬间就是一次事故。
把这个方案的真实成本说清楚:
所以我们的用法是分层的:Funnel 用于演示、验收、内部联调和临时交付;面向国内客户长期运行的服务,换自建隧道或自有域名 + 反向代理。备选方案已经很成熟——自建隧道客户端,或者自有域名解析 + 反向代理 + 自动签发证书,两条路都能落地。
这套流程的价值不在于"几分钟开一个公网链接",而在于把六类看起来一样的故障拆成了可以独立定位的六种:401 有两种、打不开可能是 DNS 也可能是出口、上线又下线可能是别人打断、看不到同伴可能是字段名取错、找不到状态可能是目录错了、地址不对可能是有个垃圾节点没删。
如果你也遇到"本地跑得好好的服务,怎么安全地给外部访问"这类问题——包括数字人展示机、内部管理后台、大屏终端的远程交付,我们做过完整的从内网到公网、从演示到正式上线的分层方案,可以直接聊。
聊聊本地服务公网交付方案 →都不需要。它把本机端口通过 ingress 节点代理出去,对外是一个 https 域名,证书也由服务商处理。代价是流量绕一段公网出口,延迟取决于 ingress 位置。
两种根因:一是残留的命令行进程占着本地接口,结束该进程即恢复;二是 Windows 会话隔离——你在远程会话里,而引擎跑在桌面会话的另一个用户下。前者能修,后者只能换视角验证,别反复重试。
先排除本机 DNS 的锅。部分内网机器解析该域名会失败,直接 curl 报 Could not resolve host,容易误判为不通。用 --resolve 把域名强制指向 ingress 实际 IP 再测状态码。
多数是自己把登录态打断了。桌面登录期间,从远程侧停止服务、强杀进程、或反复执行登录命令,都会让节点闪一下又掉。规则:有人正在桌面登录时,远程侧只许读不许写。
公网 ingress 在海外,国内访问延迟偏高;其 IP 段存在被网络策略影响的可能。面向国内客户长期使用,应准备替代方案:自建隧道服务,或自有域名加反向代理。
把本地服务变成稳定公网链接,通常是两件事的前置:一是给客户演示,二是把内容或资产真正卖出去。如果你的目的是后者,站内已有一件现成的数字资产可以直接拿来用——3550 条苏州话标准词条(JSON + CSV),适合方言语料、数字人话术、语言学习产品。先下 100 条免费样本验证格式,再决定是否购买。
查看可直接交付的数字资产 →