前言
在 Debian 上配置好开发环境和桌面工具后,为了正常访问 GitHub 等外部服务,我又通过 Clash 给日常使用的程序配置了代理。平时习惯把这个需求叫作“全局代理”,希望开着代理工作时,不用再为每个命令单独切换网络。
随后却遇到了另一个问题:Obsidian、Zotero 使用的内网服务突然访问不顺了。这些服务通过域名访问,在内网 DNS 中解析到局域网地址。原本以为已经把内网网段写进了代理排除列表,就不会受到影响,实际请求却还是进了代理。
同一段时间,ChatGPT 桌面端也出现了启动白屏。从终端指定代理后能很快打开,直接点击桌面图标却不行。这两件事刚好把代理配置的两头都碰了一遍:内网服务需要绕过代理,外部服务又需要让桌面启动器明确带上代理参数。
本文接着 Homelab 搭建手记(5)Obsidian 与 Zotero Linux 部署 记录这次调整。下面的域名和内网地址均已替换为示例;排障日志以 WebDAV 为例,不涉及修改笔记库、文献库或服务端数据。
一、先看请求到底发到了哪里
1.1 DNS 能解析,不代表请求会直连
假设内网 WebDAV 使用 webdav.home.example.com,本机解析结果为 192.168.50.10。当时代理排除列表的关键问题是只有回环名称和 IP 网段,缺少访问时使用的域名:
bash12export https_proxy=http://127.0.0.1:7890 export NO_PROXY="localhost,192.168.0.0/16"
我先对同一个地址做了两次请求:
bash12345678# 替换为自己的内网服务域名 getent ahosts webdav.home.example.com # 按当前终端代理配置访问 curl -Iv --connect-timeout 10 https://webdav.home.example.com # 本次请求明确绕过 curl 的代理配置 curl --noproxy '*' -Iv --connect-timeout 10 https://webdav.home.example.com
第一次请求的关键日志如下,域名已脱敏,省略了证书细节:
text123456* Uses proxy env variable https_proxy == 'http://127.0.0.1:7890' * Trying 127.0.0.1:7890... > CONNECT webdav.home.example.com:443 HTTP/1.1 < HTTP/1.1 200 Connection established ... < HTTP/2 401
加上 --noproxy '*' 后,连接对象变成了内网服务:
text12345* Host webdav.home.example.com:443 was resolved. * IPv4: 192.168.50.10 * Trying 192.168.50.10:443... ... < HTTP/2 401
这组结果能确认:默认请求进入了本地 HTTP 代理,强制直连时则使用了本机解析得到的内网地址。两次请求都收到了 401,不能把第一条日志写成“代理连接失败”,也不能据此判断 DNS 已经损坏。
这里没有提供 WebDAV 账号密码,401 是服务要求认证的响应。它说明请求到达了某个要求认证的 HTTP 服务;是否为预期服务,还要结合证书和服务端信息确认。它也不代表 WebDAV 的目录访问、上传和同步已经通过验证。
1.2 网段排除没有覆盖域名访问
我原先把“域名解析到内网 IP”和“命中代理绕过规则”当成了一件事。但这次请求使用的是 webdav.home.example.com,排除列表里写的却是 192.168.0.0/16。
NO_PROXY 的匹配细节取决于客户端。不能假定程序都会先解析域名,再拿结果匹配其中的网段。对于这里的 curl 请求,已有日志已经说明网段规则没有实现预期的域名绕过,因此应直接把内网域名加入列表。
curl 支持逗号分隔的排除项,.home.example.com 这样的后缀可用于匹配该域下的主机;CIDR 网段写法从 curl 7.86.0 开始支持。其他应用是否支持相同语法,需要分别确认,不能把 curl 的配置当成所有桌面程序的统一标准。curl 代理环境变量说明
二、补全内网域名的代理绕过配置
2.1 调整终端中的代理变量
我的代理配置放在 ~/.bashrc.d/proxy.sh。这是自己组织 Bash 配置时使用的文件,Bash 不会自动扫描这个目录,仍需由 ~/.bashrc 加载。
编辑前先备份已有文件,再把代理变量整理为下面这样:
bash12# 已有配置时先保留副本 cp -p ~/.bashrc.d/proxy.sh ~/.bashrc.d/proxy.sh.bak-$(date +%Y%m%d-%H%M%S)
bash1234567891011121314# ~/.bashrc.d/proxy.sh # 端口按本机代理软件实际监听情况填写 export http_proxy="http://127.0.0.1:7890" export https_proxy="$http_proxy" export HTTP_PROXY="$http_proxy" export HTTPS_PROXY="$https_proxy" # 仅当该端口也提供 SOCKS5 服务时使用 export all_proxy="socks5://127.0.0.1:7890" export ALL_PROXY="$all_proxy" # 示例内网域名和网段,请自行替换 export no_proxy="localhost,127.0.0.1,::1,.home.example.com,192.168.0.0/16" export NO_PROXY="$no_proxy"
这里真正新增的是内网域名后缀,并让大小写两份排除列表保持一致。只应加入确定需要直连的范围;如果自己的主域名同时承载公网和内网服务,使用专门的内网子域或精确主机名,避免把公网服务也一并排除。
HTTP 和 SOCKS5 是否共用 7890,取决于代理软件的监听配置。数字相同不代表协议一定都可用。https_proxy 写成 http://127.0.0.1:7890,表示连接的是 HTTP 代理,访问 HTTPS 目标时可通过 CONNECT 建立隧道,不是把目标网站降级成 HTTP。
保留小写 http_proxy 很有必要:curl 不接受大写 HTTP_PROXY 作为 HTTP 代理变量。协议专用变量又优先于 ALL_PROXY,因此本例 HTTPS 请求首先使用 https_proxy。curl 代理环境变量说明
2.2 加载配置并验证访问路径
如果还没有加载入口,可以在 ~/.bashrc 中加入一次:
bash123if [ -f "$HOME/.bashrc.d/proxy.sh" ]; then . "$HOME/.bashrc.d/proxy.sh" fi
当前终端直接加载后,再运行不带强制绕过参数的请求:
bash12. ~/.bashrc.d/proxy.sh curl -Iv --connect-timeout 10 https://webdav.home.example.com
这时要看连接目标是否变成内网 IP,以及是否还出现连接本地代理的 CONNECT 请求。再选一个确实需要代理的外部站点检查,确认没有把所有请求都改成直连:
bash1curl -Iv --connect-timeout 10 https://github.com
当时补上域名排除项后,内网访问问题得到了解决。不过终端测试只覆盖当前 shell;回到 Obsidian 和 Zotero,还要分别执行同步检查。可以用一个临时笔记或测试附件确认读写结果,不能只凭 curl 收到 HTTP 响应就认定应用同步正常。
三、终端配置与桌面程序之间还有一层
从 XFCE 菜单启动应用,通常不会经过交互式 Bash。刚刚在终端执行的 source,也不会更新已经运行的桌面程序。
遇到“curl 已经直连,应用仍然失败”的情况,我会继续检查应用的启动方式、继承环境以及自身代理设置。Obsidian 的同步插件可能使用不同的请求实现,Zotero 也有自己的网络配置,不能因为它们在同一个桌面里运行就假定行为相同。
如需检查进程环境,先找到并人工确认主进程 PID,再仅筛选代理相关变量:
bash1234pgrep -af 'obsidian|zotero' # 用实际确认的 PID 替换 12345 tr '\0' '\n' < /proc/12345/environ | grep -iE '^(http_proxy|https_proxy|all_proxy|no_proxy)='
这些变量能帮助判断启动时继承了什么,但不能证明应用一定采用它们。输出也可能带有代理认证信息,排障时不要直接贴出未经检查的完整环境。
另外,“全局代理”这个叫法需要拆开理解:环境变量只影响采用它们的程序;桌面系统代理是否生效取决于应用;TUN 则可能在路由层接管流量。curl --noproxy '*' 只能取消 curl 自己使用的显式代理,不能绕过系统中的 TUN。
如果使用 TUN 或代理内核统一接管流量,就要在对应配置中同时检查内网直连和 DNS 策略;代理客户端真正处于 Global 模式时,也不能默认普通分流规则会执行。本次记录中的直接修复是补全环境变量排除项,没有把代理内核切换模式当成已经完成的操作。
四、ChatGPT 桌面端白屏与 .desktop 启动参数
4.1 先在终端确认代理参数有效
ChatGPT 桌面端的表现刚好相反:点击图标停在启动白屏,用下面的命令却能很快打开:
bash1chatgpt --proxy-server="socks5://127.0.0.1:7890"
这里记录的是我在 Debian + XFCE 环境中安装、命令名为 chatgpt 的客户端。这个结果说明显式指定代理改善了该次启动的网络访问,但单凭这一点,还不能断言白屏一定由环境变量缺失导致,更不能推广成所有平台、所有客户端版本的通用处理。
--proxy-server 是 Electron 提供的代理参数之一;具体安装包是否接受或透传参数仍需实测。本例已经有终端启动成功的对照,接下来把同样的参数放进桌面入口即可。Electron 启动参数文档
4.2 修改用户级启动器
先找当前应用的 .desktop 文件。常见位置是 ~/.local/share/applications/、/usr/local/share/applications/ 和 /usr/share/applications/,文件名不一定就是 chatgpt.desktop:
bash12find ~/.local/share/applications /usr/local/share/applications /usr/share/applications \ -iname '*chatgpt*.desktop' 2>/dev/null
如果已经存在用户级文件,先备份再编辑它。如果只有系统级文件,可以复制到用户级目录,保留同名入口。下面以 chatgpt.desktop 为例,实际路径以查找结果为准:
bash12345678mkdir -p ~/.local/share/applications # -i 会在目标已存在时询问,避免覆盖原有用户配置 cp -i /usr/share/applications/chatgpt.desktop ~/.local/share/applications/chatgpt.desktop cp -p ~/.local/share/applications/chatgpt.desktop \ ~/.local/share/applications/chatgpt.desktop.bak-$(date +%Y%m%d-%H%M%S) nano ~/.local/share/applications/chatgpt.desktop
在主 [Desktop Entry] 段中,原先的启动命令是:
ini1Exec=chatgpt %U
修改为:
ini1Exec=chatgpt --proxy-server="socks5://127.0.0.1:7890" %U
这也是我目前启动器里保留的配置。其他字段和原有参数沿用安装包提供的内容,不需要重新手写整份 MimeType 列表。
%U 表示启动器传入的 URL 列表,每个 URL 作为独立参数传给应用,因此要保留为单独一项。chatgpt 没写绝对路径时,启动器使用桌面环境的 PATH 查找它;如果原文件已经使用绝对路径,就保留原路径。Desktop Entry 的 Exec 规范
这里的 Exec 也不是普通 shell 脚本。不要直接在其中拼接 source ~/.bashrc && ...。若启动器对引号解析有差异,上面不含空格的代理地址也可写成 --proxy-server=socks5://127.0.0.1:7890,%U 仍单独保留。
4.3 从实际使用的图标重新验证
保存后,先从应用菜单正常退出 ChatGPT,再点击菜单图标重新打开。只关闭窗口时应用可能仍留在后台,新一次启动也可能复用旧进程,导致改过的参数没有真正用于启动主进程。
如果终端有效、图标仍白屏,继续检查:
- 点击的是应用菜单入口,还是桌面上另存的一份
.desktop文件?后者需要检查自己的Exec。 - 面板固定的启动器是否保存了旧命令?必要时重新固定正确的菜单项。
- SOCKS5 监听是否已启动,端口是否与配置一致?
- 用户级覆盖文件是否仍保留旧版本启动参数?应用升级后应与新的系统入口比较。
update-desktop-database 主要更新 MIME 关联缓存,不能把它当成所有菜单和面板启动器的强制刷新命令。修改后应以实际点击结果为准。
如果需要撤销这次调整,恢复备份中的 Exec 即可。用户级覆盖文件也应持续维护,避免安装包更新了系统启动器,而自己的旧副本一直遮住新配置。
五、保留下来的配置边界
这次调整之后,我保留了两处明确配置:内网服务域名进入 NO_PROXY/no_proxy,ChatGPT 桌面入口带上已经在终端验证有效的代理参数。
以后再遇到类似问题,可以沿用同一组检查:内网域名解析是否符合预期、请求实际连接的是服务还是代理、应用同步是否可用,以及退出后从菜单启动是否仍然正常。这样既能保留访问外部服务所需的代理,也能让内网服务按预期直连。
参考
支付宝
微信