Cursor / Codex Remote SSH 代理排障
来源:一次 Cursor / Codex 侧边栏在 Remote SSH 服务器上无法联网的排查记录。
核心场景:本地电脑有代理端口 10808,希望通过 SSH RemoteForward 共享给远程服务器使用。
问题现象
常见报错包括:
unexpected status 403 Forbidden
url: https://chatgpt.com/backend-api/codex/responses
cf-ray: ...-HKG
Connection refused (os error 111)
stream disconnected before completion
这些错误看起来都像“Codex 不能用”,但实际原因不同:
| 报错 | 大致含义 | 常见原因 |
|---|---|---|
403 Forbidden | 请求到了服务端,但被拒绝 | 代理节点地区/IP 被 Cloudflare 或 OpenAI 拦截 |
Connection refused | 连本机代理端口都没连上 | SSH 隧道没建立、端口没监听、端口写反 |
stream disconnected | 流开始后被中途断开 | 节点被拦、代理规则问题、长连接被切 |
| 终端能用但侧边栏不能用 | shell 环境和插件宿主环境不同 | 侧边栏进程没有读取 .bashrc 或插件不走代理 |
整体网络链路
目标链路应该是:
远程服务器程序
-> 127.0.0.1:7897
-> SSH RemoteForward
-> 本地电脑 127.0.0.1:10808
-> 本地代理软件
-> 目标网站
其中:
10808:本地电脑代理软件监听端口。7897:远程服务器上暴露出来的代理入口。RemoteForward:把远程端口转发到本地代理端口。
SSH RemoteForward 配置
RemoteForward 的语法是:
RemoteForward <远程服务器监听端口> <本地地址>:<本地端口>
如果本地代理在:
127.0.0.1:10808
希望远程服务器通过:
127.0.0.1:7897
访问本地代理,那么本地 ~/.ssh/config 应写:
Host 154
HostName your.server.ip
User root
RemoteForward 7897 127.0.0.1:10808
不要写反成:
RemoteForward 10808 127.0.0.1:7897
这会变成“远程监听 10808,转到本地 7897”,方向不符合目标。
远程服务器环境变量
远程服务器终端里应配置:
export http_proxy="http://127.0.0.1:7897"
export https_proxy="http://127.0.0.1:7897"
export HTTP_PROXY="http://127.0.0.1:7897"
export HTTPS_PROXY="http://127.0.0.1:7897"
export all_proxy="http://127.0.0.1:7897"
export ALL_PROXY="http://127.0.0.1:7897"
这里要注意协议。
如果本地 10808 提供的是 HTTP 代理,那么远程也要写:
http://127.0.0.1:7897
不要写成:
socks5://127.0.0.1:7897
协议不匹配时,某些 Rust / Node 工具会直接报:
Connection refused (os error 111)
为什么 .bashrc 不够
远程终端能联网,不代表 Cursor 侧边栏也能联网。
原因是:
- 终端通常会读取
~/.bashrc。 - Cursor / VS Code Remote Server 的后台进程经常是非交互式 shell。
- 非交互式远程服务更可能读取
~/.profile或~/.bash_profile。 - 侧边栏插件运行在 Extension Host,不一定继承当前终端环境。
因此,代理变量建议同时写入:
~/.bashrc
~/.profile
尤其是 Cursor / VS Code 侧边栏依赖远程 Extension Host 时,~/.profile 很关键。
推荐配置位置
服务器上的 ~/.profile 末尾加入:
export http_proxy="http://127.0.0.1:7897"
export https_proxy="http://127.0.0.1:7897"
export HTTP_PROXY="http://127.0.0.1:7897"
export HTTPS_PROXY="http://127.0.0.1:7897"
export all_proxy="http://127.0.0.1:7897"
export ALL_PROXY="http://127.0.0.1:7897"
修改后只 source ~/.profile 不一定能让侧边栏生效,因为旧的远程服务进程可能还在。
需要重启远程服务。
重启 Cursor / VS Code 远程服务
修改 .profile 后,执行:
Ctrl + Shift + P
-> Kill VS Code Server on Host
-> 选择对应服务器
-> 重新连接
这个操作会杀掉远程的旧后台服务,让它重新启动并读取新的环境变量。
只执行:
Reload Window
有时不够,因为远程 server 进程可能仍然复用旧环境。
验证隧道是否打通
在远程服务器终端执行:
curl -I -v -x http://127.0.0.1:7897 https://chatgpt.com
判断结果:
| 结果 | 含义 |
|---|---|
Connection refused | 远程 7897 没监听,SSH 隧道没起来 |
HTTP/2 200 | 代理链路可用 |
HTTP/2 403 | 代理链路可用,但节点/IP 被目标服务拒绝 |
| 长时间卡住 | 代理软件、路由规则或节点连接异常 |
能返回 403 也说明隧道是通的。它不是端口转发失败,而是请求已经到达目标服务后被拒绝。
403 Forbidden 和 HKG 节点
如果报错里出现:
cf-ray: ...-HKG
通常表示请求从香港节点出去。
部分服务对香港 IP 限制较严格,会返回:
403 Forbidden
或:
stream disconnected before completion
这时服务器配置通常已经没问题,应该在本地代理软件里切换节点,例如:
- 美国。
- 日本。
- 新加坡。
- 台湾。
如果不确定路由规则,可以临时开启代理软件的全局模式测试。
Cursor 终端能用,但侧边栏不能用
这种情况很常见。
原因通常是两种。
1. 侧边栏远程 Extension Host 没有代理环境
解决:
- 代理变量写入远程
~/.profile。 - 执行
Kill VS Code Server on Host。 - 重新连接服务器。
2. 插件代码本身不读取代理
有些第三方插件的网络请求没有正确读取:
- 系统环境变量。
- VS Code
http.proxy。 - Remote settings。
这时可以强制插件在本地 UI 侧运行。
强制插件在本地运行
如果 Codex 是第三方扩展,可以在本地 settings.json 中设置:
{
"remote.extensionKind": {
"插件的真实ID": ["ui"]
}
}
插件 ID 获取方式:
扩展面板
-> 找到插件
-> 右键
-> Copy Extension ID
设置为 ["ui"] 后,该插件会尽量在本地电脑运行,而不是远程服务器。
这样它直接使用本地网络环境,不再依赖远程服务器代理。
Cursor 本地设置和远程设置的区别
本地设置适合本地 Cursor / 插件:
{
"http.proxy": "http://127.0.0.1:10808"
}
远程设置适合远程服务器 Extension Host:
{
"http.proxy": "http://127.0.0.1:7897",
"http.proxySupport": "override",
"http.proxyStrictSSL": false
}
注意端口不同:
- 本地:
10808 - 远程:
7897
同一台服务器:Cursor 能用,外部终端 SSH 不能用
如果 Cursor 连接远程后,Cursor 里的终端能用;但本地 Konsole / 终端手动 SSH 上去不能用,常见原因是:
1. 没有使用 SSH config alias
如果 SSH config 里写了:
Host 154
HostName your.server.ip
RemoteForward 7897 127.0.0.1:10808
外部终端必须用:
ssh 154
如果直接用:
ssh root@your.server.ip
可能不会读取该 Host 下的 RemoteForward。
2. RemoteForward 端口被另一个 SSH 会话占用
Cursor 已经占用了远程 7897 后,另一个 SSH 会话再尝试绑定同一端口,可能失败。
可以换一个新端口,例如:
RemoteForward 18899 127.0.0.1:10808
同时远程环境变量也全部改为:
http://127.0.0.1:18899
3. 使用 SSH 复用
本地 ~/.ssh/config 可加入:
Host *
ControlMaster auto
ControlPath ~/.ssh/sockets/%r@%h-%p
ControlPersist 600
并创建目录:
mkdir -p ~/.ssh/sockets
这样 Cursor 和普通终端可以复用同一条 SSH 连接,减少端口冲突。
多台服务器是否都要改 .profile
需要。
每台 Remote SSH 服务器都有自己的 shell 启动环境。
如果希望在 dl2、154 或其他服务器上使用同样的侧边栏代理方案,都应在对应服务器上写:
~/.profile
然后对对应 host 执行:
Kill VS Code Server on Host
再重新连接。
标准排查流程
建议以后按这个顺序排查:
- 确认本地代理端口是否可用:
curl -I -x http://127.0.0.1:10808 https://chatgpt.com
- 确认 SSH config 方向是否正确:
RemoteForward 7897 127.0.0.1:10808
- 用 SSH alias 连接:
ssh 154
- 在远程测试隧道:
curl -I -v -x http://127.0.0.1:7897 https://chatgpt.com
-
如果
Connection refused,检查 RemoteForward、端口占用、是否用错 SSH 命令。 -
如果
403或stream disconnected,切换本地代理节点。 -
如果终端可用但侧边栏不可用,把代理写入远程:
~/.profile
- 执行:
Kill VS Code Server on Host
- 如果仍然只有侧边栏不可用,尝试:
{
"remote.extensionKind": {
"插件ID": ["ui"]
}
}
一句话总结
远程终端联网依赖 shell 环境,Cursor / Codex 侧边栏联网依赖远程 Extension Host 或本地 UI 插件环境。
所以排障时要分清三层:
SSH 隧道是否通
-> 远程 shell 是否读到代理
-> Cursor / Codex 插件进程是否读到代理
不要只看终端能不能 curl,它只能证明第一层和部分第二层,不一定证明侧边栏也能联网。