自建 Headscale(Tailscale 控制服务器)踩坑全记录
自建 Headscale(Tailscale 控制服务器)踩坑全记录
从 “Connection was forcibly closed” 到腾讯云备案拦截的完整排查过程
摘要
最近在 Ubuntu 服务器上部署 Headscale + Nginx 作为 Tailscale 的自建控制服务器时,Windows 客户端执行 tailscale up 命令长时间无响应,出现 “An existing connection was forcibly closed by the remote host” 等错误。
经过多轮排查,发现问题层层递进:Nginx 配置缺失 WebSocket 支持 → SSL/TLS 握手失败 → 最终根源是腾讯云对域名的备案拦截。
本文完整记录了定位思路、关键命令、修复方案和经验教训,供有同样需求的同学参考。
关键词:Headscale、Tailscale 自建控制服务器、Nginx 反向代理、WebSocket、腾讯云 ICP 备案、webblock.html
一、问题现象
在 Windows 主机使用 Git Bash 执行以下命令时,客户端长时间卡住无反应:
tailscale up --login-server=https://example.ltd \
--authkey=****** \
--reset --force-reauth
客户端日志显示类似错误:
An existing connection was forcibly closed by the remote host.
进一步测试 curl -v https://example.ltd 也报错:
schannel: failed to receive handshake, SSL/TLS connection failed
切换为 HTTP 测试:curl -v http://example.ltd
返回结果为腾讯云拦截页面:
HTTP/1.1 302 OK
Location: https://dnspod.qcloud.com/static/webblock.html?d=example.ltd
Tailscale 日志同时出现:
invalid character '<' looking for beginning of value
key hex has the wrong size, got 16776 want 64
二、初步分析与排除
TCP 连接能建立,但很快被远端 RST,说明问题不在本地防火墙或网络连通性,而是在服务器端 SSL/TLS 握手或应用层。
Nginx 日志无记录,请求很可能在到达 Nginx 前就被云厂商拦截,或 SSL handshake 在 Nginx 处理前失败。
HTTP 返回腾讯云 webblock.html,这是腾讯云(DNSPod)对未备案或备案不匹配域名的典型拦截页面。流量并未真正进入自己的 Nginx + Headscale 服务。
Tailscale 客户端收到的是 HTML 内容而非 JSON,导致解析失败(< 开头 + key 长度错误)。
结论:问题核心不在 Tailscale/Windows 客户端,也不在 Headscale 本身,而是 域名备案 + Nginx 配置 的双重问题。
三、排查步骤
步骤 1:检查并优化 Nginx 配置(重点修复 WebSocket)
原配置缺少 WebSocket 支持,导致即使流量到达也容易被关闭。推荐在 http {} 块中添加以下配置:
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 443 ssl http2;
server_name example.ltd;
server_tokens off;
keepalive_timeout 75;
ssl_certificate /path/to/your_cert_bundle.crt; # 使用绝对路径
ssl_certificate_key /path/to/your_key.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
ssl_prefer_server_ciphers on;
location / {
proxy_pass http://127.0.0.1:8081; # Headscale 实际监听端口
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off;
proxy_read_timeout 3600;
}
}
# HTTP 强制跳转到 HTTPS
server {
listen 80;
server_name example.ltd;
return 301 https://$host$request_uri;
}
执行命令测试并重载:nginx -t && systemctl reload nginx
步骤 2:验证证书与端口监听
# 检查证书文件
ls -l /etc/nginx/*.crt /etc/nginx/*.key
# 检查端口监听
ss -ltnp | grep -E ':80|:443|:8081'
# 查看当前生效配置
nginx -T | grep -A 30 "server_name example.ltd"
步骤 3:临时切换 HTTP 测试(绕过 SSL)
把 443 配置临时注释,只保留 80 端口 proxy 测试。此时发现流量仍被腾讯云拦截,返回 webblock.html。
步骤 4:确认备案情况
访问 https://beian.miit.gov.cn/ 查询域名 example.ltd 的 ICP 备案信息。
常见问题:
备案未通过或未同步
主体/接入商与腾讯云不匹配
仅域名实名,未完成网站备案
备案通过后仍需 24-48 小时生效
四、最终根因与解决方案
根因:
腾讯云服务器 + 未完成有效 ICP 备案的域名,会在网络层直接拦截 80/443 流量,返回 webblock.html 或 RST 连接。Nginx 配置问题(WebSocket 缺失、证书路径)加剧了早期握手失败。
推荐解决方案(优先级排序):
完成 ICP 备案(最彻底)
通过腾讯云控制台提交备案申请(准备身份证/营业执照、网站截图等)。
备案通过并同步后,拦截自动解除。
备案后请在网站底部添加备案号(合规要求)。
临时绕过方案
更换一个已备案的域名。
将域名 A 记录解析到非腾讯云 IP(香港服务器或海外 VPS),绕过大陆 ICP 拦截。
短期可使用 Tailscale 官方控制服务器 login.tailscale.com 作为过渡。
优化 Nginx + Headscale
Headscale config.yaml 中 server_url 必须使用 https:// + 正确域名。
确保 Headscale 监听端口与 Nginx proxy_pass 一致。
建议 ssl_protocols 只保留 TLSv1.2 TLSv1.3。
五、经验教训与建议
自建 Headscale 在国内服务器上,域名 ICP 备案是硬性门槛,不要以为“应该已经备案了”就一定没问题,必须亲自查询验证。
Nginx 代理 Tailscale/Headscale 时,WebSocket 支持是必备(Upgrade + Connection header)。
排查时优先使用 curl -v 测试,结合客户端日志,能快速定位是握手失败、应用层错误还是云拦截。
建议把证书路径改为绝对路径,避免相对路径加载失败。
如果使用腾讯云轻量应用服务器,拦截机制会更严格,提前规划备案时间。
整个排查过程从最初的 “远端强制关闭连接” 到发现腾讯云备案拦截,花了几个小时,但一步步缩小范围后定位非常清晰。希望这篇记录能帮到同样在自建 Tailscale 控制服务器的朋友。
参考资料
Headscale 官方反向代理文档(WebSocket 配置)
腾讯云 ICP 备案指南
Nginx proxy WebSocket 最佳实践
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)