跳到主要内容

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 没有代理环境

解决:

  1. 代理变量写入远程 ~/.profile
  2. 执行 Kill VS Code Server on Host
  3. 重新连接服务器。

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 启动环境。

如果希望在 dl2154 或其他服务器上使用同样的侧边栏代理方案,都应在对应服务器上写:

~/.profile

然后对对应 host 执行:

Kill VS Code Server on Host

再重新连接。

标准排查流程

建议以后按这个顺序排查:

  1. 确认本地代理端口是否可用:
curl -I -x http://127.0.0.1:10808 https://chatgpt.com
  1. 确认 SSH config 方向是否正确:
RemoteForward 7897 127.0.0.1:10808
  1. 用 SSH alias 连接:
ssh 154
  1. 在远程测试隧道:
curl -I -v -x http://127.0.0.1:7897 https://chatgpt.com
  1. 如果 Connection refused,检查 RemoteForward、端口占用、是否用错 SSH 命令。

  2. 如果 403stream disconnected,切换本地代理节点。

  3. 如果终端可用但侧边栏不可用,把代理写入远程:

~/.profile
  1. 执行:
Kill VS Code Server on Host
  1. 如果仍然只有侧边栏不可用,尝试:
{
"remote.extensionKind": {
"插件ID": ["ui"]
}
}

一句话总结

远程终端联网依赖 shell 环境,Cursor / Codex 侧边栏联网依赖远程 Extension Host 或本地 UI 插件环境。

所以排障时要分清三层:

SSH 隧道是否通
-> 远程 shell 是否读到代理
-> Cursor / Codex 插件进程是否读到代理

不要只看终端能不能 curl,它只能证明第一层和部分第二层,不一定证明侧边栏也能联网。