先用显式代理请求定位故障,再调整系统代理或 DNS。客户端显示“已连接”,通常只说明内核或接管开关已经启动,不代表浏览器请求经过了该内核,也不代表当前节点能够访问目标网站。按“监听端口 → 显式请求 → 应用代理 → DNS → 规则与节点 → TUN”的顺序检查,每次只改一项。

1. 确认内核正在监听,而不是只有界面在运行

先查看进程与端口。下面两条命令只读取状态,不修改配置。ss 显示进程信息可能需要管理员权限;没有权限时,仍可先确认监听地址和端口。

pgrep -af 'mihomo|clash'
sudo ss -lntp 'sport = :7890'
  • 没有监听结果:检查内核是否启动失败、配置是否解析出错,或实际端口是否并非 7890。
  • 监听在 127.0.0.1:7890:本机程序可以访问,局域网其他设备不能直接使用此入口。
  • 监听在 0.0.0.0:7890:入口覆盖 IPv4 本地网卡,应同时检查局域网访问权限与防火墙。
  • 端口由其他进程占用:先确认进程归属,再停用冲突实例,不要直接批量结束所有代理进程。

mixed-port 同时接受 HTTP 和 SOCKS 请求。若配置只使用 port 或 socks-port,协议与端口必须分别对应;external-controller 则是管理接口,不能作为浏览器代理端口。

# 仅检查这个目录下的配置,不会启动第二个常驻实例
mihomo -t -d "$HOME/.config/mihomo"

测试通过只说明该配置能被当前二进制解析,不保证服务正在使用它。自建 systemd 用户服务可以运行以下命令核对启动参数;系统级服务应改用 sudo systemctl,去掉 --user。

systemctl --user cat mihomo.service
systemctl --user status mihomo.service --no-pager
journalctl --user -u mihomo.service -b -n 80 --no-pager

若提示服务不存在,回到图形客户端的内核日志查看,不要为排障额外启动一套同端口服务。重点搜索 address already in use、配置解析失败、权限不足和 Provider 加载错误。

2. 用 curl 显式指定代理,切开应用与内核问题

先测试 HTTP 代理入口。--noproxy "" 用于清空本次请求的绕过列表,防止环境变量中的 NO_PROXY 让测试意外直连。示例使用同一 HTTPS 地址,便于比较结果;实际排查也应对打不开的目标执行同样测试。

curl --noproxy "" -x http://127.0.0.1:7890 \
  --connect-timeout 5 --max-time 20 \
  -v -o /dev/null https://example.com/
结果说明下一步
Connection refused本地 TCP 连接被拒绝核对监听端口和地址
407代理要求认证核对配置中的认证与应用凭据
已建立 CONNECT,随后超时入口已响应,后续链路未完成查规则、节点与目标连接日志
certificate verify failedTLS 验证失败检查系统时间、证书库及中间代理
收到目标 HTTP 响应该请求路径已能往返检查浏览器与系统代理设置

HTTP 403 或 429 说明目标或中间服务返回了响应,不应直接归类为端口失效。一次成功也只证明当前目标和当前规则路径可用,不能据此认定所有网站、UDP 或 IPv6 都正常。

再比较一次不走显式代理的请求

curl --proxy "" --noproxy "*" \
  --connect-timeout 5 --max-time 20 \
  -I https://example.com/

这条命令禁用 curl 的显式代理,但不会绕过系统中的 TUN、策略路由或透明转发。若需要比较真正的直连基线,应先在本地可恢复的环境中关闭 TUN,再重复测试。显式代理成功、浏览器失败时,优先进入下一节,不要立即更换 DNS。

3. 对齐系统代理、浏览器设置与终端环境变量

Linux 桌面代理、终端环境变量和浏览器自身配置是不同入口。开启其中一个,不意味着另外两个自动生效。以 GNOME 桌面为例,在「设置」→「网络」→「网络代理」中选择「手动」,将 HTTP 与 HTTPS 的代理主机设为 127.0.0.1、端口设为 7890;不同发行版的入口名称可能略有变化。

HTTPS 栏表示“HTTPS 请求使用哪个代理”,并不要求把代理协议写成 HTTPS。使用本地 mixed-port 时,终端里的 https_proxy 通常仍填写 http://127.0.0.1:7890。

# 查看当前 shell 中的代理变量;输出可能包含凭据,不要直接公开
env | grep -iE '^(http|https|all|no)_proxy='

# 仅对这一次 curl 请求设置代理
http_proxy=http://127.0.0.1:7890 \
https_proxy=http://127.0.0.1:7890 \
curl --noproxy "" --max-time 20 -I https://example.com/

检查绕过列表与重复接管

  • NO_PROXY=* 会让支持该变量的程序绕过代理。测试时清空它,再逐项恢复本机和内网例外。
  • Firefox 可在「设置」→「常规」→「网络设置」→「设置」中核对是使用系统代理还是手动代理。扩展也可能接管此设置。
  • 旧 shell、已启动的 IDE 和后台服务不会自动继承新终端里的变量。修改后应在对应进程环境中重新验证。
  • 容器中的 127.0.0.1 指向容器本身,不是宿主机。宿主端口正常不能证明容器可以连接。

若怀疑旧变量干扰,可用下面的命令开启一次干净测试。它仅影响本次子进程,不会删除 shell 配置文件里的持久设置。

env -u http_proxy -u https_proxy -u all_proxy -u no_proxy \
  -u HTTP_PROXY -u HTTPS_PROXY -u ALL_PROXY -u NO_PROXY \
  curl --noproxy "" -x http://127.0.0.1:7890 \
  --max-time 20 -I https://example.com/

4. 区分系统 DNS、内核 DNS 与 fake-ip

域名解析失败不一定出在系统 DNS。HTTP CONNECT 可以把目标主机名交给代理;SOCKS 则能通过选项选择在哪一端解析。用以下两条命令比较:第一条让 curl 在本地解析,第二条把域名交给 SOCKS 代理处理。

curl --noproxy "" --socks5 127.0.0.1:7890 \
  --max-time 20 -I https://example.com/

curl --noproxy "" --socks5-hostname 127.0.0.1:7890 \
  --max-time 20 -I https://example.com/

如果本地解析路径失败,而代理侧解析成功,系统解析器值得优先检查;这仍是线索,不是唯一结论,因为两次连接可能命中不同地址与规则。继续查看系统实际使用的解析配置。

getent ahosts example.com
ls -l /etc/resolv.conf
cat /etc/resolv.conf

# 仅适用于启用了 systemd-resolved 的系统
resolvectl status
resolvectl query example.com

如果 /etc/resolv.conf 指向 systemd-resolved 或 NetworkManager 管理的文件,直接覆盖它可能在重连后失效。应在对应网络管理器中修改 DNS,而不是一边由 DHCP 更新,一边手工写入。

检查 mihomo 的 DNS 监听与 fake-ip 路径

假设实际运行配置中已有 dns.listen: 127.0.0.1:1053,可以用安装了 dig 的终端直接查询内核 DNS。若监听端口不同,命令也应随之调整。

dig @127.0.0.1 -p 1053 example.com A
dig @127.0.0.1 -p 1053 example.com AAAA

enhanced-mode: fake-ip 下,域名可能返回配置范围内的合成地址,常见范围为 198.18.0.1/16。这不是公网目标地址,后续连接需要回到保有域名映射的代理内核。如果应用拿到 fake-ip,却通过未接管的路由直连,就可能持续超时。

上游 DNS 自身也可能需要域名解析或代理链路。检查 default-nameserver、nameserver、proxy-server-nameserver 的用途与当前配置,避免节点域名解析依赖尚未建立的代理链路。字段说明可对照配置参考,不要同时更换全部上游后再猜测是哪项生效。

5. 查规则命中与节点可用性,不只看延迟数字

在客户端连接记录中找到测试域名,确认最终出站是 DIRECT、某个代理节点还是拒绝动作。规则从上向下匹配,末尾的 MATCH 是兜底;前面的域名或 IP 规则已经命中时,修改兜底组不会改变该请求。

  1. 记录测试时间和目标域名,重新执行一次显式代理 curl 请求。
  2. 在连接记录或日志中查找对应域名,确认命中的规则和策略组。
  3. 检查策略组当前选择,尤其是嵌套组最终落到的实际节点。
  4. 临时选择另一个已知可用节点,保持目标地址、请求方式和模式不变,再做比较。

延迟测试成功仅表示配置的测试 URL 在当时可达,不等于所有站点可用。节点可能只在某些目标、协议或地址族上失败。订阅更新成功也只说明取回了配置,不能证明节点的认证、额度、服务端口都正常。

短时提高日志级别,再恢复

必要时将实际配置中的 log-level 临时设为 debug,复现一次后恢复 info。图形客户端应通过它提供的配置覆盖或日志设置修改,避免生成的运行配置被下次更新覆盖。分享日志前删除订阅地址、认证信息与私人访问域名。

# 自建用户服务:修改配置后先测试,成功才重启
mihomo -t -d "$HOME/.config/mihomo" && \
  systemctl --user restart mihomo.service

journalctl --user -u mihomo.service --since "5 minutes ago" --no-pager

遇到证书错误,先运行 timedatectl status 核对时间和同步状态,再检查证书库与节点域名。不要把关闭证书验证当成常规修复,否则可能掩盖地址错误或中间链路问题。

6. 显式代理正常但 TUN 失败:检查权限、路由与 IPv6

当 curl -x 正常,而开启 TUN 后浏览器失败,端口和节点已有可用证据,重点应转向透明接管。TUN 依赖虚拟网卡、相应权限和路由;配置里的 tun.enable: true 不代表这些条件已经满足。

ls -l /dev/net/tun
ip -br link
ip rule show
ip route show table all
ip -6 route show table all
  • 没有 /dev/net/tun:检查系统是否支持并启用了 TUN。容器或受限虚拟化环境还需要宿主授权。
  • 日志提示创建接口或修改路由失败:使用客户端提供的服务模式,或核对自建服务权限,不要随意扩大可执行文件权限。
  • 同时运行其他 VPN:对比双方的策略路由、默认路由和 DNS 接管。一次只保留一套接管路径进行验证。
  • IPv4 正常、IPv6 失败:分别检查 IPv6 默认路由、DNS AAAA 响应与当前内核的 IPv6 处理配置。
# 关闭 TUN 后,比较宿主机原始 IPv4 / IPv6 连通性
curl --proxy "" --noproxy "*" -4 --max-time 15 -I https://example.com/
curl --proxy "" --noproxy "*" -6 --max-time 15 -I https://example.com/

这里的 -4 与 -6 用于检查直连基线。对本地 HTTP 代理执行同样选项,并不能可靠地控制远端节点访问目标时使用的地址族。若网络本身没有 IPv6 出口,IPv6 测试失败不代表节点损坏。

7. 按最小改动恢复,并保留复现记录

排查结束后,用同一个目标重新验证终端、浏览器和 TUN 三条路径。不要只凭托盘图标或一次测速判断恢复。建议保留下列记录,方便复发时直接比较。

  1. 运行实例:记录内核版本、实际配置路径、监听地址和 mixed-port。版本可用 mihomo -v 查看。
  2. 应用入口:记录浏览器使用系统代理还是手动代理,以及终端中是否保留代理变量。
  3. 解析路径:记录系统 DNS、内核 DNS 监听端口、是否使用 fake-ip,以及浏览器是否开启安全 DNS。
  4. 出站结果:记录测试域名命中的规则、实际节点及错误时间,不记录订阅密钥。
  5. 恢复设置:把 debug 日志恢复为日常级别,撤销临时全局模式和不再需要的测试改动。

最有区分度的判断是:显式代理失败,先查内核与出站;显式代理成功而浏览器失败,先查应用入口;只有 TUN 失败,先查透明接管;只有本地解析失败,再沿系统 DNS 路径排查。需要重新建立基础配置时,按快速上手完成最小可用链路,再逐项恢复规则、Provider 和 TUN。