tunnel-doctor
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTunnel Doctor
隧道问题诊断与修复指南
Diagnose and fix conflicts when Tailscale coexists with proxy/VPN tools on macOS, with specific guidance for SSH access to WSL instances.
诊断并修复macOS系统上Tailscale与代理/VPN工具共存时的冲突,包含连接WSL实例的SSH访问专属指导。
Diagnostic Workflow
诊断流程
Step 1: Identify the Symptom
步骤1:确认症状
Determine which scenario applies:
- Tailscale ping works, SSH works, but curl/HTTP times out → HTTP proxy env var conflict (Step 2A)
- Tailscale ping works, SSH/TCP times out → Route conflict (Step 2B)
- SSH connects but → Tailscale SSH config issue (Step 4)
operation not permitted - SSH connects but exits code 1 → WSL snap sandbox issue (Step 5)
be-child ssh
Key distinction: SSH does NOT use / env vars, but curl/wget/Python requests/Node.js fetch do. If SSH works but HTTP doesn't, it's almost always a proxy env var issue, not a route issue.
http_proxyNO_PROXY判断符合以下哪种场景:
- Tailscale ping正常,SSH可连接,但curl/HTTP请求超时 → HTTP代理环境变量冲突(步骤2A)
- Tailscale ping正常,但SSH/TCP请求超时 → 路由冲突(步骤2B)
- SSH可连接但返回→ Tailscale SSH配置问题(步骤4)
operation not permitted - SSH可连接但返回退出码1 → WSL snap沙箱问题(步骤5)
be-child ssh
关键区别:SSH不会使用/环境变量,但curl/wget/Python requests/Node.js fetch会使用。如果SSH正常但HTTP请求异常,几乎可以确定是代理环境变量问题,而非路由问题。
http_proxyNO_PROXYStep 2A: Fix HTTP Proxy Environment Variables
步骤2A:修复HTTP代理环境变量
Check if proxy env vars are intercepting Tailscale HTTP traffic:
bash
env | grep -i proxyBroken output — proxy is set but doesn't exclude Tailscale:
NO_PROXYhttp_proxy=http://127.0.0.1:1082
https_proxy=http://127.0.0.1:1082
NO_PROXY=localhost,127.0.0.1 ← Missing Tailscale!Fix — add Tailscale MagicDNS domain + CIDR to :
NO_PROXYbash
export NO_PROXY=localhost,127.0.0.1,.ts.net,100.64.0.0/10,192.168.*,10.*,172.16.*| Entry | Covers | Why |
|---|---|---|
| MagicDNS domains ( | Matched before DNS resolution |
| Tailscale IPs ( | Precise CIDR, no public IP false positives |
| RFC 1918 private networks | LAN should never be proxied |
Two layers complement each other: handles domain-based access, handles direct IP access.
.ts.net100.64.0.0/10NO_PROXY syntax pitfalls — see references/proxy_fixes.md for the compatibility matrix.
Verify the fix:
bash
undefined检查代理环境变量是否拦截了Tailscale的HTTP流量:
bash
env | grep -i proxy异常输出 — 已设置代理但未排除Tailscale相关配置:
NO_PROXYhttp_proxy=http://127.0.0.1:1082
https_proxy=http://127.0.0.1:1082
NO_PROXY=localhost,127.0.0.1 ← 缺少Tailscale相关配置!修复方案 — 将Tailscale MagicDNS域名+CIDR添加到:
NO_PROXYbash
export NO_PROXY=localhost,127.0.0.1,.ts.net,100.64.0.0/10,192.168.*,10.*,172.16.*| 配置项 | 覆盖范围 | 原因 |
|---|---|---|
| MagicDNS域名( | 在DNS解析前匹配 |
| Tailscale IP段( | 精确的CIDR范围,不会误匹配公网IP |
| RFC 1918私有网络 | 局域网流量永远不应被代理 |
两层配置互补:处理基于域名的访问,处理直接IP访问。
.ts.net100.64.0.0/10NO_PROXY语法陷阱 — 查看references/proxy_fixes.md获取兼容性矩阵。
验证修复效果:
bash
undefinedBoth must return HTTP 200:
以下两个命令均需返回HTTP 200:
NO_PROXY="...(new value)..." curl -s --connect-timeout 5 http://<host>.ts.net:<port>/health -w "HTTP %{http_code}\n"
NO_PROXY="...(new value)..." curl -s --connect-timeout 5 http://<tailscale-ip>:<port>/health -w "HTTP %{http_code}\n"
Then persist in shell config (`~/.zshrc` or `~/.bashrc`).NO_PROXY="...(新值)..." curl -s --connect-timeout 5 http://<host>.ts.net:<port>/health -w "HTTP %{http_code}\n"
NO_PROXY="...(新值)..." curl -s --connect-timeout 5 http://<tailscale-ip>:<port>/health -w "HTTP %{http_code}\n"
然后将配置持久化到shell配置文件(`~/.zshrc`或`~/.bashrc`)。Step 2B: Detect Route Conflicts
步骤2B:检测路由冲突
Check if a proxy tool hijacked the Tailscale CGNAT range:
bash
route -n get <tailscale-ip>Healthy output — traffic goes through Tailscale interface:
destination: 100.64.0.0
interface: utun7 # Tailscale interface (utunN varies)Broken output — proxy hijacked the route:
destination: 100.64.0.0
gateway: 192.168.x.1 # Default gateway
interface: en0 # Physical interface, NOT TailscaleConfirm with full route table:
bash
netstat -rn | grep 100.64Two competing routes indicate a conflict:
100.64/10 192.168.x.1 UGSc en0 ← Proxy added this (wins)
100.64/10 link#N UCSI utun7 ← Tailscale route (loses)Root cause: On macOS, (Static Gateway) takes priority over (Cloned Static Interface) for the same prefix length.
UGScUCSI检查代理工具是否劫持了Tailscale的CGNAT网段:
bash
route -n get <tailscale-ip>正常输出 — 流量通过Tailscale接口传输:
destination: 100.64.0.0
interface: utun7 # Tailscale接口(utunN编号不固定)异常输出 — 代理劫持了路由:
destination: 100.64.0.0
gateway: 192.168.x.1 # 默认网关
interface: en0 # 物理接口,而非Tailscale接口通过完整路由表确认:
bash
netstat -rn | grep 100.64出现两条竞争路由表示存在冲突:
100.64/10 192.168.x.1 UGSc en0 ← 代理添加的路由(优先级更高)
100.64/10 link#N UCSI utun7 ← Tailscale路由(优先级更低)根本原因:在macOS系统中,相同前缀长度的路由中,(静态网关)优先级高于(克隆静态接口)。
UGScUCSIStep 3: Fix Proxy Tool Configuration
步骤3:修复代理工具配置
Identify the proxy tool and apply the appropriate fix. See references/proxy_fixes.md for detailed instructions per tool.
Key principle: Do NOT use to exclude . This causes the proxy to add a route that overrides Tailscale. Instead, let the traffic enter the proxy TUN and use a DIRECT rule to pass it through.
tun-excluded-routes100.64.0.0/10→ en0Universal fix — add this rule to any proxy tool:
IP-CIDR,100.64.0.0/10,DIRECT
IP-CIDR,fd7a:115c:a1e0::/48,DIRECTAfter applying fixes, verify:
bash
route -n get <tailscale-ip>识别代理工具并应用对应的修复方案。查看references/proxy_fixes.md获取各工具的详细说明。
核心原则:不要使用排除。这会导致代理添加一条的路由,覆盖Tailscale的路由。正确做法是让流量进入代理TUN,然后使用DIRECT规则放行。
tun-excluded-routes100.64.0.0/10→ en0通用修复方案 — 向任意代理工具添加以下规则:
IP-CIDR,100.64.0.0/10,DIRECT
IP-CIDR,fd7a:115c:a1e0::/48,DIRECT应用修复后验证:
bash
route -n get <tailscale-ip>Should show Tailscale utun interface, NOT en0
应显示Tailscale的utun接口,而非en0
undefinedundefinedStep 4: Configure Tailscale SSH ACL
步骤4:配置Tailscale SSH ACL
If SSH connects but returns , the Tailscale ACL may require browser authentication for each connection.
operation not permittedAt Tailscale ACL admin, ensure the SSH section uses :
"action": "accept"json
"ssh": [
{
"action": "accept",
"src": ["autogroup:member"],
"dst": ["autogroup:self"],
"users": ["autogroup:nonroot", "root"]
}
]Note: requires browser authentication each time. Change to for non-interactive SSH access.
"action": "check""accept"如果SSH可连接但返回,说明Tailscale ACL可能要求每次连接都通过浏览器认证。
operation not permitted在Tailscale ACL管理页面中,确保SSH部分配置为:
"action": "accept"json
"ssh": [
{
"action": "accept",
"src": ["autogroup:member"],
"dst": ["autogroup:self"],
"users": ["autogroup:nonroot", "root"]
}
]注意:要求每次连接都进行浏览器认证。如需非交互式SSH访问,请改为。
"action": "check""action": "accept"Step 5: Fix WSL Tailscale Installation
步骤5:修复WSL上的Tailscale安装
If SSH connects and ACL passes but fails with exit code 1 in tailscaled logs, the snap-installed Tailscale has sandbox restrictions preventing SSH shell execution.
be-child sshDiagnosis — check WSL tailscaled logs:
bash
undefined如果SSH可连接且ACL验证通过,但tailscaled日志中显示退出码1,说明通过snap安装的Tailscale存在沙箱限制,无法执行SSH shell。
be-child ssh诊断方法 — 查看WSL的tailscaled日志:
bash
undefinedFor snap installs:
针对snap安装版本:
sudo journalctl -u snap.tailscale.tailscaled -n 30 --no-pager
sudo journalctl -u snap.tailscale.tailscaled -n 30 --no-pager
For apt installs:
针对apt安装版本:
sudo journalctl -u tailscaled -n 30 --no-pager
Look for:access granted to user@example.com as ssh-user "username"
starting non-pty command: [/snap/tailscale/.../tailscaled be-child ssh ...]
Wait: code=1
**Fix** — replace snap with apt installation:
```bashsudo journalctl -u tailscaled -n 30 --no-pager
查找以下日志内容:access granted to user@example.com as ssh-user "username"
starting non-pty command: [/snap/tailscale/.../tailscaled be-child ssh ...]
Wait: code=1
**修复方案** — 替换snap安装版本为apt安装版本:
```bashRemove snap version
移除snap版本
sudo snap remove tailscale
sudo snap remove tailscale
Install apt version
安装apt版本
curl -fsSL https://tailscale.com/install.sh | sh
curl -fsSL https://tailscale.com/install.sh | sh
Start with SSH enabled
启动并启用SSH
sudo tailscale up --ssh
**Important**: The new installation may assign a different Tailscale IP. Check with `tailscale status --self`.sudo tailscale up --ssh
**重要提示**:新安装的Tailscale可能会分配不同的Tailscale IP。使用`tailscale status --self`查看。Step 6: Verify End-to-End
步骤6:端到端验证
Run a complete connectivity test:
bash
undefined运行完整的连通性测试:
bash
undefined1. Check route is correct
1. 检查路由是否正确
route -n get <tailscale-ip>
route -n get <tailscale-ip>
2. Test TCP connectivity
2. 测试TCP连通性
nc -z -w 5 <tailscale-ip> 22
nc -z -w 5 <tailscale-ip> 22
3. Test SSH
3. 测试SSH连接
ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no <user>@<tailscale-ip> 'echo SSH_OK && hostname && whoami'
All three must pass. If step 1 fails, revisit Step 3. If step 2 fails, check WSL sshd or firewall. If step 3 fails, revisit Steps 4-5.ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no <user>@<tailscale-ip> 'echo SSH_OK && hostname && whoami'
三项测试必须全部通过。如果步骤1失败,返回步骤3重新检查。如果步骤2失败,检查WSL的sshd服务或防火墙。如果步骤3失败,返回步骤4-5重新检查。References
参考资料
- references/proxy_fixes.md — Detailed fix instructions for Shadowrocket, Clash, and Surge
- references/proxy_fixes.md — Shadowrocket、Clash和Surge的详细修复说明