返回博客

OpenRouter 中转也救不了:X-Forwarded-For 透传让境外模型在国内依然 403

项目需要一个 LLM 备选通道:主力模型偶尔不稳定时自动切换,不能让用户感知。选了 OpenRouter——一个聚合了几十家模型的统一接口,看起来很完美:账号在美国,服务器也在美国,从国内请求过去应该没问题吧?

结果部署上去第一次冒烟测试就挂了。

现象

从国内云服务器(阿里云北京 region)通过 OpenRouter 调任何一个美系厂商模型,稳定收到:

HTTP 403
{"error":{"message":"This model is not available in your region.","code":403}}

试了 google/gemini-2.5-flash-liteopenai/gpt-4oanthropic/claude-*,全部 403。

第一反应:是不是 OpenRouter 账户配置问题?打开 Privacy 设置,把 Allowed Providers 逐一确认——配置没问题。重新调,还是 403。

根因

OpenRouter 有一个平台级的抗滥用机制:把客户端的真实 IP 通过 X-Forwarded-For 请求头原样透传给上游模型 API

请求链路实际是这样的:

国内服务器(中国 IP)
  → OpenRouter 美国机房(OpenRouter 自己接受这个请求)
    → 上游 API(OpenAI / Google / Anthropic),Header 里带着:
      X-Forwarded-For: <你的中国大陆公网 IP>
        → 上游基于 X-Forwarded-For 做 region 检查 → 拒绝 → 403

OpenRouter 自己虽然在美国,但它不替你隐藏来源 IP。上游厂商拿到的不是 OpenRouter 的 IP,而是你服务器的 IP。

坑:OpenRouter 中转 ≠ 境外出口,X-Forwarded-For 照样暴露你的国内 IP

经过 OpenRouter 的请求,上游(OpenAI / Google / Anthropic)看到的 X-Forwarded-For 仍然是你的国内服务器 IP。等价于直连 api.openai.com——同样 403。这是 OpenRouter 平台故意设计的抗滥用机制,没有开关可以关。

正解

从国内部署的服务直接放弃调美系模型,改用中国厂商系模型——它们通过 OpenRouter 路由时上游不做 region 限制:

| OpenRouter 路径 | 是否可用 | |---|---| | qwen/qwen3.6-flash(阿里通义) | ✅ | | moonshotai/kimi-k2.6(Moonshot) | ✅ | | z-ai/glm-5(智谱 GLM) | ✅ | | google/gemini-* | ❌ | | openai/gpt-* | ❌ | | anthropic/claude-* | ❌ |

实测 qwen/qwen3.6-flash 立刻 200,把上面的 openai/gpt-4o 换回去,还是 403——这步反验很重要,可以排除 key 或账户问题,锁定根因就是 region 拦截。

验证命令:

# 正验:应该 200
curl https://openrouter.ai/api/v1/chat/completions \
  -H "Authorization: Bearer sk-or-v1-..." \
  -H "Content-Type: application/json" \
  -d '{"model":"qwen/qwen3.6-flash","messages":[{"role":"user","content":"ping"}],"max_tokens":8}'
 
# 反验:应该 403
curl https://openrouter.ai/api/v1/chat/completions \
  -H "Authorization: Bearer sk-or-v1-..." \
  -H "Content-Type: application/json" \
  -d '{"model":"openai/gpt-4o","messages":[{"role":"user","content":"ping"}],"max_tokens":8}'

如果业务上非美系不可

如果必须用 GPT / Claude,唯一可行方案是在服务器端架一层真正的代理出口,让 OpenRouter 上游看到的 X-Forwarded-For 是美国 IP:

  1. 开一台美国 ECS,跑 shadowsocks-rust 服务端
  2. 国内部署机跑 sslocal,同时监听 SOCKS 和 HTTP 两个 local 端口:
{
  "locals": [
    { "protocol": "socks", "local_address": "127.0.0.1", "local_port": 51080 },
    { "protocol": "http",  "local_address": "127.0.0.1", "local_port": 51081 }
  ]
}
  1. Node.js 服务端 fetch 时走 HTTP CONNECT 代理(undici ProxyAgent):
import { ProxyAgent } from 'undici'
const dispatcher = new ProxyAgent('http://127.0.0.1:51081')
fetch(url, { dispatcher } as any)  // dispatcher 不在 lib.dom 类型里,需要 as any

注意:undici 没有内置 SOCKS dispatcher,必须走 HTTP 端口(51081),不能直接用 SOCKS(51080)。

正解:国内部署首选中国厂商模型,非美系不可则必须配真实出口代理

国内服务器 → OpenRouter → 中国厂商模型(Qwen / Kimi / GLM)可以直接通,不需要任何代理。非要调美系就必须在服务端搭真实的境外出口,让上游拿到的 X-Forwarded-For 是境外 IP,OpenRouter 本身的中转解决不了这个问题。

一句话外卖

OpenRouter 是模型路由聚合层,不是 IP 匿名化层——它的 X-Forwarded-For 透传是设计,不是 bug,国内部署选模型时直接跳过所有美系厂商。