早上打开浏览器,境外站点一个都打不开。代理软件明明在运行,图标亮着,界面里的开关也全是打开的。直觉是去调代理模式——从规则切到全局,还是不行;换节点,也不行;重启软件,依旧不行。
最后发现问题根本不在代理软件的模式设置上:它有 105 个节点全部可用、规则分流 151 条、订阅状态都正常,但 macOS 从来没有把任何流量交给它。这篇文章把这套排查顺序和修复方法完整记下来。
先分清四层,别一上来就调模式
代理能否生效,要穿过四层。每一层都有独立的判据,混在一起看就很容易误判。
这次的情况是前两层完全正常、第三层断了。它的迷惑性在于:你在命令行里手动指定代理去访问,是通的,于是很容易得出「代理没问题」的结论。实际上命令行手动指定代理,等于绕过了第三层,验证的只是前两层。
逐层判据
← 左右滑动查看完整表格 →
| 层 | 要确认什么 | 判断命令 / 位置 | 这次的结论 |
|---|---|---|---|
| ① 客户端进程 | 软件是否真的在跑 | 进程列表里能看到主程序 | 正常 |
| ② 代理内核 | 内核是否监听代理端口 | 端口处于监听状态,且手动指定代理能出网 | 正常 |
| ③ 系统代理 | 系统有没有把流量转给内核 | 系统代理快照中三个开关是否启用 | 未启用 |
| ④ 浏览器 / 应用 | 应用是否读到了代理设置 | 境外站点能否打开 | 受影响 |
排查顺序建议从第③层开始。因为第①②层在界面上有明确的状态显示,而第③层的真实状态界面上看不到——软件里的「系统代理」开关表达的是软件的意图,不是操作系统的实际状态,两者可以长期不一致。
第三层为什么会断:系统代理不是全局开关
这是这次问题的核心,也是最反直觉的一点。
在 macOS 上,代理设置并不存在「一个全局开关」。它是按每个网络服务(network service)分别保存的:Wi-Fi 有一份,有线网卡有一份,雷电桥有一份,iPhone USB 共享也各有一份。每一份里面再分 HTTP、HTTPS、SOCKS 三种。
这台机器上的实际状态是:其他三个接口都正确写入了代理地址,唯独 Wi-Fi 这一条没有。而当时正好在用 Wi-Fi(连的是手机热点),于是所有走系统的应用全部直连。
为什么会变成这样?因为代理软件写入系统代理时,写的是「当时检测到的、正在使用的接口」。插网线的时候写过,用 iPhone 共享的时候写过,Wi-Fi 这一条从来没被写过。所以会出现「昨天还好好的,今天全打不开」——不是软件坏了,是换了一条没配置过的网络服务。
确认方法
# 1. 系统代理的真实状态(三项 Enabled 全是 0 就说明没接管)
scutil --proxy
# 2. 逐个网络服务看代理配置
for s in "Wi-Fi" "USB 10/100 LAN" "iPhone USB" "Thunderbolt Bridge"; do
echo "=== $s ==="
networksetup -getwebproxy "$s"
done
# 3. 确认当前默认路由走的是哪个接口
route -n get default | grep interface另外有一个判断上的坑值得单独说:不要用 lsof 去判断代理端口是否在监听。代理内核通常以 root 身份运行,普通权限的 lsof 看不到它的监听端口,输出里一片空白,很容易误判成「端口没起来」。用 netstat 查才是准的:
netstat -an -p tcp | grep LISTEN | grep -E '\.(7890|9090) '
# 也可以直接测一次出网,这一步不依赖任何界面状态
curl -s -o /dev/null -w "%{http_code}\n" --max-time 8 \
-x http://127.0.0.1:7890 https://www.google.com/generate_204
# 返回 204 说明内核和节点都没问题,纯粹是系统没把流量转过来修复:给当前在用的接口补上代理
把其他接口上已有的配置原样复制过来即可。这一步不需要管理员权限,因为改的是当前用户自己的网络代理偏好:
networksetup -setwebproxy "Wi-Fi" 127.0.0.1 7890
networksetup -setsecurewebproxy "Wi-Fi" 127.0.0.1 7890
networksetup -setsocksfirewallproxy "Wi-Fi" 127.0.0.1 7890顺手把 bypass 列表也补上,否则本机服务、内网地址、局域网设备都会被绕进代理,出现「本地调试连不上」「内网设备访问不了」这类新问题。这个列表要和软件里已经配好的保持一致:
networksetup -setproxybypassdomains "Wi-Fi" \
localhost "*.local" "127.*" "10.*" \
"172.16.*" "172.17.*" "172.18.*" "172.19.*" \
"172.20.*" "172.30.*" "172.31.*" "192.168.*"改完立即验证,三个开关都应该是启用状态:
scutil --proxy | grep -E "HTTPEnable|HTTPSEnable|SOCKSEnable"
# 期望三项都是 1验证:规则分流是否真的按预期工作
系统代理接上之后,不能只看「网页能打开了」就结束。还要确认流量是进了代理内核并按规则分流,而不是全部硬走代理。分开测三组:
← 左右滑动查看完整表格 →
| 测试目标 | 期望结果 | 说明了什么 |
|---|---|---|
| 国内站点 | 直连可达,状态码 200 | 国内流量没被绕出去 |
| 境外站点(如 Google 的 204 探针) | 返回 204 | 境外流量能出网 |
| AI 服务接口 | 返回 401 / 403 这类业务响应 | 链路通,只是缺凭证——这本身就是成功 |
其中最容易被误判成失败的是第三条。接口返回 401 说明请求已经完整到达服务器,服务器在正常回应「你没带密钥」,这恰恰证明代理链路是通的。如果真有问题,表现会是超时或连接被重置。
想看到更细的分流路径,可以查代理内核的连接列表,每一行都会标出命中的规则和实际走的节点链路:
# 例:Google 组、AI 组各自走哪个节点
# chains 字段从内到外依次是:具体节点 > 自动选择 > 手动选择 > 内容分组顺带发现的性能问题:自动测速被钉死在单个节点
排查过程中还发现了一个独立的问题。分组类型是自动测速(url-test),但实际每次都走同一个节点。原因是这个分组被记录了一个「固定节点」,一旦固定,自动测速就不再参与挑选。
同组里 105 个节点全部可用,最快 76ms、原来固定的那个 172ms,差了一倍多。这类问题平时不会报错,只是慢,很难被注意到。
两个坑
坑一:只解除运行状态的固定,重启会回弹。固定节点的状态同时存在于两处——运行中的内核状态,和本地保存的分组选择记录。只把运行状态解除,软件下次启动或订阅自动更新时会从本地记录里读回来,又钉在旧节点上。所以必须两处都清。
坑二:解除固定之后,不会立即重新测速。状态显示已经是自动,但当前节点还是上一次的那个,要主动触发一轮测速才会重选。验证时要看最终选中的是哪个节点,而不是只看「是否处于自动模式」这个标志位。
另外三个值得提前处理的事项
← 左右滑动查看完整表格 →
| 事项 | 风险 | 建议 |
|---|---|---|
| 开机自启关闭 | 重启后内核不会自动启动,但系统代理仍指向本地端口,所有应用都连不上,表现为「完全没网」。 | 要么打开开机自启,要么不用时把系统代理一并关掉,不要留半开状态。 |
| 订阅自动更新 | 自动更新可能把分组选择、自定义规则等改动覆盖回默认值。 | 改完配置后,等一轮自动更新再复查一次,确认没被覆盖。 |
| 换网络环境 | 新接口不会自动继承已有接口的代理设置,这是本文问题的根因。 | 换了网络(插网线、连热点、手机共享)后,先查一次系统代理状态。 |
小结
代理「看起来在运行但其实没生效」,按这个顺序查:
① 系统代理是否真的启用了(不是看软件界面,是看系统快照)→ ② 当前在用的网络服务有没有被写入(系统代理按接口分别保存)→ ③ 内核端口是否监听、手动指定代理能否出网 → ④ 规则分流是否符合预期(国内直连、境外出网、接口有业务响应)。
最值得记住的一条:命令行手动指定代理能通,不代表浏览器也能通。两者之间隔着「系统代理」这一层,而这一层是按网络服务逐个写入的,换网络就会露出空白。下次遇到「代理开着但上不了网」,先跑一次系统代理快照,通常一眼就能看出问题。