Codex Desktop 通过 SSH 连接远程服务器:GPT 辅助排障、代理配置与 app-server 复盘

这篇文章是 GPT 总结我的 Codex Desktop 通过 SSH 连接远程服务器后GPT写的配置记录,也是一份真实排障笔记。你可以选择性的看,大致是没错的。你也可以直接丢给 AI 看,可能有引导之后 AI 做的更快更好。


摘要

这次配置的目标不是简单地“让服务器能连上 Codex”,而是希望把 VSCode Remote-SSHCodex Desktop Remote SSH 分开:

VSCode Remote-SSH:继续走原来的 SSH 入口,尽量保持原样
Codex Desktop:单独走 codex-besci 这个 SSH alias,并通过本机代理访问 OpenAI / Codex 服务

也就是说,同一台远程服务器,希望保留两个 SSH 入口:

原 SSH alias      -> 给 VSCode 用
codex-besci      -> 给 Codex Desktop 用

其中 codex-besci 只是给同一台服务器单独起的一个 SSH 别名,不代表另一台服务器。

说明:
本文中的用户名、IP、项目路径、服务器路径都已经脱敏。真实路径里可能包含个人用户名、学校/实验室目录或项目目录,因此不直接写进公开博客。


文章目录

1. 配置目标

这次配置的核心目标是:让 Codex Desktop 能够通过 SSH 连接远程服务器,同时通过本机代理访问 OpenAI / Codex 后端服务。

但这里并不是简单地修改原有 SSH 配置,而是希望做到:

VSCode Remote-SSH:继续使用原来的 SSH alias
Codex Desktop:使用单独的 SSH alias

也就是让同一台服务器对应两个入口:

<ORIGINAL_SSH_ALIAS>  -> 给 VSCode Remote-SSH 使用
codex-besci           -> 给 Codex Desktop 使用

这样做的原因是:VSCode 原本已经可以正常远程开发,不希望因为 Codex Desktop 的代理配置影响 VSCode 的使用环境。


2. 最终想实现的结构

理想结构如下:

Windows 本机
├── VSCode
│   └── ssh <ORIGINAL_SSH_ALIAS>
│       └── 尽量不走代理,保持原来的远程开发方式
│
└── Codex Desktop
    └── ssh codex-besci
        └── RemoteForward <REMOTE_PROXY_PORT> -> 本机 <LOCAL_PROXY_PORT>
            └── 让远端 Codex app-server 通过本机代理访问 OpenAI / Codex 后端

本文中使用下面这些占位符表示具体配置:

<ORIGINAL_SSH_ALIAS>     原来的 SSH 入口,主要给 VSCode 用
codex-besci              Codex Desktop 专用 SSH 入口
<REMOTE_HOST_IP>         远程服务器 IP
<REMOTE_USER>            远程服务器用户名
<LOCAL_PROXY_PORT>       Windows 本机代理端口,例如 7897
<REMOTE_PROXY_PORT>      远端反向代理端口,例如 17897
<PROJECT_DIR>            远端项目目录
<MODEL_NAME>             Codex 可用模型名称

3. 背景:Codex Desktop 远程连接和 VSCode 插件不是一回事

最开始参考的是一篇讲 VSCode Remote-SSH 使用 Codex 插件 的文章。它的核心思路是:远程服务器不能直接使用本机代理,所以可以通过 SSH 的 RemoteForward,把远端端口转发到本机代理端口。

这个方向是对的。

但是后续排障发现,VSCode 插件方案Codex Desktop Remote SSH 方案 不是完全一样的。

Codex Desktop 连接远程服务器时,会通过 SSH 启动远端 Codex app-server,并且要求远端登录 shell 的 PATH 里能找到 codex 命令。

因此,Codex Desktop 远程连接至少涉及下面几层:

Codex Desktop
  -> SSH alias
  -> 远端 shell
  -> 远端 codex 命令
  -> 远端 codex app-server
  -> 远端 app-server 访问 OpenAI / Codex 后端

也就是说,SSH 能登录只是第一步。真正关键的是:

远端 codex 命令能否正确启动
远端 app-server 是否继承了代理环境
远端 app-server 是否真的能访问 Codex 后端

这也是后面排障的重点。


4. SSH 配置:拆成两个 alias

4.1 原来的 SSH alias:给 VSCode 用

原来的 SSH 配置大致如下:

Host <ORIGINAL_SSH_ALIAS>
  HostName <REMOTE_HOST_IP>
  User <REMOTE_USER>
  IdentityFile ~/.ssh/id_ed25519
  ServerAliveInterval 60
  ServerAliveCountMax 3

这个入口继续给 VSCode Remote-SSH 使用,尽量保持原样。

4.2 Codex 专用 SSH alias:给 Codex Desktop 用

另外单独增加一个 codex-besci

Host codex-besci
  HostName <REMOTE_HOST_IP>
  User <REMOTE_USER>
  IdentityFile ~/.ssh/id_ed25519
  RemoteForward <REMOTE_PROXY_PORT> 127.0.0.1:<LOCAL_PROXY_PORT>
  ServerAliveInterval 60
  ServerAliveCountMax 3

例如:

Host codex-besci
  HostName <REMOTE_HOST_IP>
  User <REMOTE_USER>
  IdentityFile ~/.ssh/id_ed25519
  RemoteForward 17897 127.0.0.1:7897
  ServerAliveInterval 60
  ServerAliveCountMax 3

其中最关键的是这一行:

RemoteForward 17897 127.0.0.1:7897

它的含义是:

远端服务器上的 127.0.0.1:17897
  -> 通过 SSH 隧道
  -> 转发到 Windows 本机的 127.0.0.1:7897

也就是说,远端服务器可以通过访问自己的:

127.0.0.1:17897

间接使用 Windows 本机的代理:

127.0.0.1:7897

5. 为什么要换远端端口

一开始使用过这样的配置:

RemoteForward 9999 127.0.0.1:7897

但是后来发现远端的 9999 端口已经被旧的 SSH 反向转发占用了。

连接时可能出现类似提示:

Warning: remote port forwarding failed for listen port 9999

可以在远端检查端口:

ss -ltn 'sport = :9999'

如果已经看到 127.0.0.1:9999 在监听,说明这个端口不能再被新的 SSH 会话绑定。

所以后来换了一个新的远端端口,例如:

RemoteForward 17897 127.0.0.1:7897

说明:
17897 不是固定值,只是一个未占用端口。也可以换成其他端口,只要远端没有被占用即可。

为了避免 SSH 连接成功但端口转发失败,可以用:

ssh -o ExitOnForwardFailure=yes codex-besci "echo SSH_FORWARD_OK"

ExitOnForwardFailure 的作用是:如果本地、远程或动态端口转发无法建立,就让 SSH 直接退出,而不是继续假装连接成功。


6. 分层验证网络

这一步非常重要。

不要一上来就看 Codex Desktop 报什么错,而是先分层验证:

1. 本机代理是否可用
2. 远端服务器能否直连目标服务
3. 远端服务器能否通过 SSH RemoteForward 使用本机代理

6.1 验证本机代理可用

在 Windows PowerShell 中执行:

curl.exe -I -k --ssl-no-revoke --max-time 20 `
  -x http://127.0.0.1:7897 `
  https://chatgpt.com/backend-api/codex/responses

如果返回类似:

HTTP/1.1 200 Connection established
HTTP/1.1 405 Method Not Allowed
allow: POST

这通常说明代理链路已经到达后端。

这里的 405 Method Not Allowed 不一定是坏事,因为这里用的是 HEAD 请求,而目标接口可能不接受这个方法。

6.2 验证远端直连是否失败

在远端测试直连:

python3 - <<'PY'
import urllib.request

try:
    req = urllib.request.Request(
        "https://chatgpt.com/backend-api/codex/responses",
        method="HEAD",
    )
    with urllib.request.urlopen(req, timeout=12) as r:
        print("direct", r.status, r.reason)
except Exception as e:
    print("direct", type(e).__name__, str(e)[:220])
PY

如果出现类似:

direct URLError <urlopen error [Errno 101] Network is unreachable>

说明远端服务器本身无法直连外网。

6.3 验证远端能否通过反向代理访问

先确保 SSH 转发已经建立,然后在远端执行:

python3 - <<'PY'
import urllib.request

proxy = "http://127.0.0.1:17897"

opener = urllib.request.build_opener(
    urllib.request.ProxyHandler({
        "http": proxy,
        "https": proxy,
    })
)

try:
    req = urllib.request.Request(
        "https://chatgpt.com/backend-api/codex/responses",
        method="HEAD",
    )
    with opener.open(req, timeout=12) as r:
        print("proxy", r.status, r.reason)
except Exception as e:
    print("proxy", type(e).__name__, str(e)[:220])
PY

如果返回类似:

proxy HTTPError HTTP Error 405: Method Not Allowed

说明远端已经可以通过 SSH 反向代理访问到 Codex 后端。

原始排障记录中的结论也是这样:

远端直连失败
远端通过 127.0.0.1:<REMOTE_PROXY_PORT> 可以访问 Codex backend
旧端口存在占用
wrapper 存在但旧 app-server 没继承代理环境

7. 配置远端 Codex wrapper

为了让远端 Codex 一定走代理,GPT 给出的方案是在远端创建一个 wrapper:

~/.local/bin/codex

内容如下:

#!/usr/bin/env bash

export http_proxy=http://127.0.0.1:17897
export https_proxy=http://127.0.0.1:17897
export HTTP_PROXY=http://127.0.0.1:17897
export HTTPS_PROXY=http://127.0.0.1:17897

exec "$HOME/.codex/packages/standalone/releases/<CODEX_VERSION>/bin/codex" "$@"

然后添加执行权限:

chmod +x ~/.local/bin/codex

确认当前 shell 优先找到 wrapper:

which codex

理想输出:

/home/<REMOTE_USER>/.local/bin/codex

这个 wrapper 的作用是:

调用 codex 前,先注入 http_proxy / https_proxy 等代理环境变量
然后再 exec 真正的 standalone codex 二进制文件

8. 重要坑点:wrapper 改好了,不代表旧 app-server 会自动生效

这是整个排障过程中最容易误判的地方。

wrapper 已经配置好,但 Codex Desktop 仍然连接失败。后续排查发现,远端已经存在旧的:

codex app-server --listen unix://

这个旧 app-server 是在 wrapper 修好之前启动的,所以它没有继承新的代理环境变量:

http_proxy
https_proxy
HTTP_PROXY
HTTPS_PROXY

也就是说:

wrapper 文件已经正确
但是旧 app-server 还在用旧环境运行

所以必须重启远端 Codex app-server。

查看进程:

ps -ef | grep 'codex app-server'

重点识别 standalone Codex 的 app-server,例如:

$HOME/.codex/packages/standalone/releases/.../bin/codex app-server --listen unix://
$HOME/.codex/packages/standalone/releases/.../bin/codex app-server proxy

停止这些旧进程后,清理 stale socket / lock:

rm -f ~/.codex/app-server-control/app-server-control.sock
rm -f ~/.codex/app-server-control/app-server-startup.lock

注意:
不要误杀 VSCode Server 或 VSCode 插件自己的进程。
比如下面这种路径就可能是 VSCode 扩展相关进程:

~/.vscode-server/extensions/.../codex app-server

原始排障记录显示:旧 standalone app-server 没有代理环境,所以必须停掉旧进程,并清理 app-server-control 里的 socket 和 lock 文件。


9. 在远端验证 Codex CLI 是否能工作

进入一个远端项目目录:

cd <PROJECT_DIR>

为了确保使用 wrapper,可以临时指定 PATH:

PATH="$HOME/.local/bin:$PATH"

然后测试:

printf "Print exactly OK.\n" | timeout 45 codex exec -m <MODEL_NAME> --sandbox read-only -

如果能返回模型响应,说明下面几层都已经打通:

远端 codex 命令可执行
wrapper 生效
代理变量生效
SSH RemoteForward 生效
远端 Codex 可以访问后端

说明:
<MODEL_NAME> 请换成自己当前 Codex 可用的模型名称。


10. Codex Desktop 中连接远程项目

完成前面的配置后,再打开 Codex Desktop。

大致流程是:

Codex Desktop
  -> 设置
  -> 连接
  -> 找到 SSH
  -> 添加
  -> 选择 codex-besci
  -> 选择远端项目目录

Codex Desktop 应该会通过 codex-besci 这个 SSH alias 连接远端,并启动远端 Codex app-server。

10.1 不确定点:Codex Desktop 是否一定只使用所选 SSH alias?

从官方文档看,Codex App 会通过 SSH 配置连接远程主机,并启动远端 app-server。

但这次排查中,旧 app-server、socket、lock、VSCode 插件进程之间可能存在残留影响,所以不能百分百确认每一次启动都完全符合预期。

因此,如果要严格隔离,最好每次修改配置后都清理旧 app-server,并重新连接。


11. 原本想隔离 VSCode 和 Codex,但最后可能没有完全隔离

最初目标是:

VSCode:走 <ORIGINAL_SSH_ALIAS>,不走代理
Codex:走 codex-besci,走代理

但是最后实际现象像是:

VSCode 可能也能走代理
Codex Desktop 也能走代理

这部分没有完全搞清楚,所以这里必须标注为不确定。

11.1 不确定点:为什么最后 VSCode 似乎也走了代理?

GPT 根据排障过程推测,可能有下面几种原因。

11.1.1 可能原因 A:代理变量被写进了远端全局 shell 配置

如果在排查过程中,把下面这些内容写进了:

~/.bashrc
~/.profile
~/.zshrc
/etc/environment

例如:

export HTTP_PROXY=http://127.0.0.1:17897
export HTTPS_PROXY=http://127.0.0.1:17897
export http_proxy=http://127.0.0.1:17897
export https_proxy=http://127.0.0.1:17897

那么所有使用这个远端用户的 shell 都可能继承代理,包括 VSCode Remote-SSH。

检查方法:

grep -nEi 'http_proxy|https_proxy|HTTP_PROXY|HTTPS_PROXY|17897|9999' \
  ~/.bashrc ~/.profile ~/.zshrc 2>/dev/null

如果想让 VSCode 不走代理,就不要把代理变量写进全局 shell 配置。

11.1.2 可能原因 B:VSCode Remote Settings 被配置了代理

如果远端 VSCode 的 settings 里有:

{
  "http.proxy": "http://127.0.0.1:17897",
  "http.proxySupport": "on"
}

那么 VSCode 插件也可能走代理。

检查:

cat ~/.vscode-server/data/Machine/settings.json

如果目标是让 VSCode 不走代理,就需要删除这类配置。

11.1.3 可能原因 C:旧 app-server 或旧 socket 影响判断

这次遇到过旧 app-server 没有继承 wrapper 代理环境的问题。也就是说,运行中的进程环境和当前文件配置并不一定一致。

所以如果只是看配置文件,可能会误判。

建议检查:

ps -ef | grep 'codex app-server'

必要时清理:

rm -f ~/.codex/app-server-control/app-server-control.sock
rm -f ~/.codex/app-server-control/app-server-startup.lock
11.1.4 可能原因 D:GPT / Codex 辅助调试过程中修改过多处配置

这次不是完全按照一个固定教程一步步手动配置的,而是由 GPT 和 Codex 边排查边修改。过程中可能改过:

SSH config
RemoteForward 端口
远端 wrapper
远端 shell 配置
VSCode Remote Settings
Codex app-server 进程
stale socket / lock

最后 Codex Desktop 确实能连上,但“为什么 VSCode 最后也像是走了代理”,没有完全确认。

更准确的结论是:

这次尝试让 Codex Desktop 单独走 codex-besci 代理入口;
最终 Codex Desktop 成功连接;
但 VSCode 是否仍然完全不受代理影响,没有彻底确认。

12. 如果重新配置一次,建议这样做

12.1 SSH config 明确分两个入口

Host <ORIGINAL_SSH_ALIAS>
  HostName <REMOTE_HOST_IP>
  User <REMOTE_USER>
  IdentityFile ~/.ssh/id_ed25519
  ServerAliveInterval 60
  ServerAliveCountMax 3

Host codex-besci
  HostName <REMOTE_HOST_IP>
  User <REMOTE_USER>
  IdentityFile ~/.ssh/id_ed25519
  RemoteForward 17897 127.0.0.1:7897
  ServerAliveInterval 60
  ServerAliveCountMax 3

使用规则:

VSCode 只连接 <ORIGINAL_SSH_ALIAS>
Codex Desktop 只连接 codex-besci

12.2 不要污染远端全局环境变量

不要把代理变量写进:

~/.bashrc
~/.profile
~/.zshrc
/etc/environment

除非目标就是让整个远端用户环境都走代理。

12.3 只在 Codex wrapper 中注入代理

推荐 wrapper:

#!/usr/bin/env bash

export http_proxy=http://127.0.0.1:17897
export https_proxy=http://127.0.0.1:17897
export HTTP_PROXY=http://127.0.0.1:17897
export HTTPS_PROXY=http://127.0.0.1:17897

exec "$HOME/.codex/packages/standalone/releases/<CODEX_VERSION>/bin/codex" "$@"

12.4 每次改 wrapper 后重启 app-server

ps -ef | grep 'codex app-server'

停掉旧 standalone Codex app-server 后清理:

rm -f ~/.codex/app-server-control/app-server-control.sock
rm -f ~/.codex/app-server-control/app-server-startup.lock

12.5 用命令验证,不要只看界面

验证 SSH 转发:

ssh -o ExitOnForwardFailure=yes codex-besci "echo SSH_FORWARD_OK"

验证远端代理:

curl -I -m 20 -x http://127.0.0.1:17897 https://chatgpt.com/

验证 Codex CLI:

PATH="$HOME/.local/bin:$PATH"
printf "Print exactly OK.\n" | timeout 45 codex exec -m <MODEL_NAME> --sandbox read-only -

13. 最终总结

这次配置的核心不是“SSH 能不能登录”,而是要把下面几层全部理清楚:

1. Windows 本机代理是否可用
2. SSH alias 是否配置正确
3. RemoteForward 是否真的建立成功
4. 远端能否通过反向代理访问 OpenAI / Codex 后端
5. 远端 codex 命令是否在 PATH 中
6. Codex wrapper 是否注入了代理
7. 旧 codex app-server 是否仍在使用旧环境
8. VSCode 是否被全局代理变量或 Remote Settings 影响

这次排障得到的核心经验是:

Codex Desktop 连接远程服务器时,SSH 只是入口;
真正关键的是远端 codex app-server 的运行环境。

如果只是想让 Codex Desktop 走代理,而不想影响 VSCode,比较稳妥的方式是:

给 Codex Desktop 单独建 SSH alias;
只在这个 alias 上配置 RemoteForward;
只在 codex wrapper 中注入代理;
不要把代理写入远端全局 shell;
不要改 VSCode Remote Settings;
每次修改后重启旧 app-server。

这次最终能跑通,但仍然保留一个不确定结论:

最初想让 VSCode 和 Codex 完全隔离;
但最后 VSCode 似乎也受到了代理配置影响;
具体是全局环境变量、VSCode settings,还是旧 app-server / socket 残留导致的,没有完全确认。

因此,这篇文章既是教程,也是一次真实的 GPT 辅助排障记录。确定的部分写成步骤;不确定的部分单独标注,避免把猜测写成结论。


14. 参考资料

  1. OpenAI Codex Remote Connections 官方文档:说明 Codex App 通过 SSH 启动远端 Codex app-server,并要求远端 codex 命令在登录 shell 的 PATH 中可用。
  2. OpenAI Codex Quickstart / CLI 文档:说明 Codex CLI 的安装和登录方式。
  3. OpenSSH ssh_config 手册:说明 RemoteForwardExitOnForwardFailure 的含义。
  4. 原始 GPT / Codex 排障记录:远端直连失败、反向代理可用、旧端口占用、wrapper 与旧 app-server 环境不一致等问题。
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐