为什么 VPN 站点的 403 不是代理故障
源站的 403、被拒绝的 CONNECT 和 407 挑战标志着不同的边界。先比较代理的出口路径与浏览器的 VPN 地址。
用户断开 VPN,在内部站点遇到 403,并报告代理坏了。能产生那个页面的有三件不同的事:源站拒绝了请求,正向代理拒绝打开隧道,或者代理索要它自己的凭据。每一件都是对不同问题的正确回答,把它们压平成一张通用故障页,会毁掉你唯一的证据。按做出拒绝的组件来分类,再比较每个组件实际使用的网络路径。
说出产生 403 的组件
HTTP 状态码属于发送它的那一方。源站返回 403 时,它已经解析了请求并判定调用方可能没有该资源,这是授权层面的回答,不是传输问题。正向代理对 CONNECT 回答 403 时,它根本没有建立隧道,上游也没有被接触过。它回答 407 时,要的是它自己的凭据,与应用的登录无关。
| What you observe | What it means |
|---|---|
| Origin replies 403 | The origin understood the request and denied it |
| Proxy refuses CONNECT with 403 | The proxy declined to open that tunnel |
| Proxy replies 407 | Proxy authentication is required first |
| Gateway replies 502 or 504 | Upstream failure or timeout, not a permission decision |
RFC 9110 定义了这些角色,包括代理特有的挑战。先读响应自身的状态和任何 Proxy-Authenticate 头,再读正文,因为友好的 HTML 错误页通常会掩盖是哪个组件写的。
CONNECT 隧道对字节中继隐藏了什么
代理对 CONNECT 返回 200 之后,它在转发字节,浏览器与源站保持自己的 TLS 会话。因此转发字节的代理读不到该会话里加密的 403,也永远不会成为拒绝的作者。隧道打开之前的拒绝则完全是另一件事,产生在更低一层。
终止 TLS 的反向代理处在不同位置:它拥有 TLS 边界,解析 HTTP,可以合法地改写或拦截响应。这种能力是配置选择,不是代理的普遍属性,也不授权绕过应用的访问策略。读响应是一种能力;决定调用方能看到什么仍然是源站的职责。
实际后果是,在解释 403 之前必须知道自己运行的是三种形态中的哪一种。给流代理增加 HTTP 检查规则不会让它获得可见性,而指望字节中继去解释源站的拒绝,会让你查错日志。
比较代理的出口路径与浏览器的隧道地址
连接到 VPN 的浏览器不会自动把单独的代理服务器搬进那条隧道。源站看到的是 代理的出口地址,如果代理仍从本地默认路由出去,受保护的站点看到的是一个从未被配置为受信任的地址。笔记本上的内层 VPN 地址对那个源站不可见。这是可用的隧道仍然从对其他人行为正常的站点拿到 403 的最常见原因。
分别测试两条路径,以免地址被混淆。
curl -sS -o /dev/null -w 'status=%{http_code}\n' --noproxy '*' https://198.51.100.20/private
curl -sS -o /dev/null -w 'status=%{http_code}\n' -x http://203.0.113.10:3128 https://198.51.100.20/private第一个请求显示源站对你自己的地址如何回答,第二个显示它对代理的地址如何回答。一对相同的 403 指向源站的授权规则;200 之后跟着 403 指向代理路径。去问源站所有者规则集实际放行哪些源地址,而不是假设 VPN 地址池被包含在内。
用能隔离边界的请求逐一测试
隔离代理层:向它请求一件它无法以应用理由拒绝的事。对代理允许到达的主机发起 CONNECT 并丢弃输出,就能看出隧道准入是否工作。
curl -sSv -o /dev/null -x http://203.0.113.10:3128 https://198.51.100.20/ 2>&1 | grep -E 'CONNECT|HTTP/1.1|Proxy-Authenticate'
如果 CONNECT 打印 200,随后是 TLS 握手,说明代理准入正常,后来的 403 来自源站。如果打印 407,用 --proxy-user 提供代理凭据并在对站点下任何结论之前重试。如果在 CONNECT 行上打印 403,要检查的边界就是代理自己的允许列表,应用并未参与。
把凭据挡在 shell 历史和粘贴的转录之外。在命令行输入的代理密码对其他本地用户可见,而被捕获的带凭据的 CONNECT 跟踪比没有跟踪更糟。
保持结论可读,而不是重写页面
一旦知道是哪个组件拒绝的,就保留那个回答。把源站的 403 重新标成 504 超时、或替换成成功页的反向代理,会把授权决定藏在可用性故事后面,并把下一个响应者送到错误的团队。自定义错误页在保留状态并补充上下文时是好的。
缓存也值得同样的谨慎。RFC 9111 规定了共享缓存可以如何复用响应,为某个调用方产生的拒绝不得当作许可交给另一个人。如果确实存储拒绝响应,保持它们私有且限定范围,绝不要仅仅为了减少上游流量而添加公共新鲜度规则。
以书面结论收尾,写明组件、它产生的状态以及源站看到的源地址。记录确切的请求、生效的代理设置和时间,好让下一份报告能与同一批证据比对,而不是凭记忆重新推导。那份记录能在几秒内回答下一份报告,并阻止同一个 403 被再次诊断为 VPN 中断。