VS Code + SSH 反向隧道:让远程服务器借用本地"魔法"运行 Codex CLI

1. 为什么 VS Code 已经连上服务器,服务器却不一定能使用本地网络?
先看一个典型开发环境:
text
本地电脑
├── VS Code
├── 本地"魔法"工具
└── 本地代理端口:127.0.0.1:31888 ← 虚构示例
│
│ VS Code Remote SSH
▼
远程 Linux 服务器
└── Codex CLI / curl / Git / pip
这里最容易产生的误解是:
"VS Code 已经通过 SSH 连上服务器了,远程终端是不是自然也能使用本地电脑的网络环境?"
答案是:不会自动共享。
原因在于:
text
127.0.0.1
永远表示"当前这台机器自己"。
因此:
text
本地电脑的 127.0.0.1
和:
text
远程服务器的 127.0.0.1
不是同一个地址。
如果本地"魔法"监听在:
text
127.0.0.1:31888
服务器并不能直接访问这个端口。
我们真正需要做的是:
text
让服务器上的一个本地端口
↓
通过 SSH 隧道
↓
连接回本地电脑的"魔法"端口
这就是 SSH 反向端口转发(Remote Forwarding)。
2. 整体结构到底是什么?
最终链路可以画成:
text
Codex / curl / Git
│
▼
远程服务器
127.0.0.1:18080
│
│ SSH RemoteForward
▼
本地电脑
127.0.0.1:31888
│
▼
本地"魔法"
│
▼
你被授权访问的外部开发服务
这里使用两个故意不同的虚构端口:
text
服务器端口:18080
本地端口:31888
这样也能说明一个重要事实:
反向转发两端的端口号不需要相同。
3. 一句话理解 RemoteForward
SSH 配置中的:
ssh
RemoteForward 18080 127.0.0.1:31888
可以理解为:
text
把远程服务器的 18080
映射到
本地电脑的 127.0.0.1:31888
也就是:
text
远程服务器 127.0.0.1:18080
│
│ SSH 隧道
▼
本地电脑 127.0.0.1:31888
这里的 18080 和 31888 都只是本文的虚构示例值。
4. 为什么写进 SSH config,而不是每次手动敲命令?
如果每次都手动建立隧道,思路类似:
bash
ssh -R 18080:127.0.0.1:31888 demo_user@host.example.invalid
其中:
text
demo_user
host.example.invalid
18080
31888
全部是演示信息。
对于本来就通过 VS Code Remote SSH 开发的人,更方便的做法是把配置放进本地 SSH config。
这样:
text
打开 VS Code
↓
连接远程服务器
↓
SSH 自动读取 RemoteForward
↓
反向隧道同时建立
也就是说,日常开发时不需要再单独维护一个 SSH 隧道窗口。
5. 本地 SSH config 怎么写?
Windows 通常可以在下面的位置找到 SSH 配置:
text
C:\Users\你的用户名\.ssh\config
macOS / Linux 通常是:
text
~/.ssh/config
下面是一份完全虚构的示例:
ssh
Host demo-codex-server
HostName host.example.invalid
User demo_user
RemoteForward 18080 127.0.0.1:31888
ServerAliveInterval 60
ServerAliveCountMax 3
逐行理解:
text
Host
是你给这台服务器起的本地别名。
text
HostName
是真实环境中的服务器地址。本文使用 .invalid 保留域名作为演示,不对应真实服务器。
text
User
是登录服务器时使用的账号。这里使用虚构用户名:
text
demo_user
最关键的是:
ssh
RemoteForward 18080 127.0.0.1:31888
它告诉 SSH:
text
服务器 18080
↓
本地 31888
保存以后,重新通过 VS Code Remote SSH 连接该 Host。
6. 为什么一定要重新连接 VS Code?
因为 RemoteForward 属于 SSH 会话建立时加载的配置。
如果你:
text
已经连接服务器
↓
修改 ~/.ssh/config
当前已经存在的连接一般不会自动增加新的转发规则。
因此最稳妥的流程是:
text
修改 SSH config
↓
保存
↓
断开当前 Remote SSH
↓
重新连接
新的 SSH 会话建立后,反向隧道才会同时创建。
7. 先测试隧道,再改服务器配置
不要一上来就把代理变量永久写进 .bashrc。
建议先在服务器中临时测试:
bash
curl -x http://127.0.0.1:18080 -I https://chatgpt.com
或者测试你实际被授权使用的目标开发服务。
如果能迅速收到 HTTP 响应,说明至少:
text
服务器
↓
18080
↓
SSH 隧道
↓
本地电脑
↓
本地"魔法"
这条链路已经工作。
需要注意:
text
HTTP 200
HTTP 301
HTTP 302
HTTP 401
HTTP 403
虽然业务含义不同,但从网络层面看,它们通常都意味着已经成功和目标服务器建立了 HTTP 通信。
如果看到:
text
Connection refused
Connection timed out
Could not connect
则优先检查 SSH 隧道、本地工具以及端口配置。
8. 在服务器永久写入代理环境变量
确认测试正常后,可以把服务器端的代理入口写进:
text
~/.bashrc
例如:
bash
cat >> ~/.bashrc <<'EOF'
export http_proxy=http://127.0.0.1:18080
export https_proxy=http://127.0.0.1:18080
export HTTP_PROXY=http://127.0.0.1:18080
export HTTPS_PROXY=http://127.0.0.1:18080
EOF
让配置立即生效:
bash
source ~/.bashrc
检查:
bash
echo $http_proxy
echo $https_proxy
预期看到:
text
http://127.0.0.1:18080
这里的:
text
127.0.0.1:18080
并不是服务器安装了一个新的"魔法"工具。
它只是:
text
SSH 反向隧道在服务器这一端留下的入口
9. 为什么大小写变量都配置?
这里同时写了:
bash
http_proxy
https_proxy
HTTP_PROXY
HTTPS_PROXY
主要是为了减少不同命令行程序之间的兼容性差异。
不同软件读取环境变量的习惯并不完全一致,因此个人开发服务器通常会一起配置。
10. 最重要的理解:.bashrc 永久,但隧道不是永久的
这是整个方案中非常容易忽略的一点。
你把:
bash
export HTTPS_PROXY=http://127.0.0.1:18080
写进 .bashrc 后,它确实会长期存在。
但是:
text
SSH 隧道
依赖当前 SSH 连接。
也就是说:
text
VS Code Remote SSH 在线
↓
隧道存在
↓
服务器 18080 可用
一旦 VS Code 对服务器的 SSH 连接完全断开:
text
SSH 会话消失
↓
反向隧道消失
这时 .bashrc 虽然仍然指向:
text
127.0.0.1:18080
但对应的 SSH 转发入口已经不存在。
所以可以记住一句:
永久保存的是代理配置,不是 SSH 隧道本身。
11. 网络测试通过后,再安装 Codex CLI
Linux / macOS 当前可以使用 Codex CLI 官方 standalone installer:
bash
curl -fsSL https://chatgpt.com/codex/install.sh | sh
安装结束后检查:
bash
codex --version
以及:
bash
which codex
如果能够正常显示版本和程序路径,就说明 CLI 已经安装。
Codex 的安装方式可能随版本更新,因此长期阅读本文时,应以 OpenAI 官方 Codex CLI 文档的最新说明为准。
12. 远程服务器怎么登录 Codex?
远程 Linux 服务器经常没有图形浏览器。
这种情况下可以使用 Device Code 登录:
bash
codex login --device-auth
流程大致是:
text
远程服务器执行登录命令
↓
终端给出网址和一次性验证码
↓
在自己的浏览器中打开授权页面
↓
完成登录与授权
↓
服务器上的 Codex 获得登录状态
检查当前登录状态:
bash
codex login status
OpenAI 官方文档也把 Device Code 登录作为远程 / headless 环境下的推荐方式之一。
13. 开始使用 Codex
进入自己的项目:
bash
cd /path/to/your/project
启动:
bash
codex
第一次使用,可以先让 Codex 只读分析项目:
text
先阅读项目结构,不要修改文件。
告诉我这个项目的入口、核心模块、主要依赖和运行方式。
这种使用方式很适合:
text
代码阅读
项目接手
科研工程分析
排查问题
重构前理解代码
14. 最终日常流程其实非常短
首次配置完成后,日常基本只剩下:
text
① 打开本地"魔法"
↓
② 打开 VS Code
↓
③ Remote SSH 连接服务器
↓
④ 进入项目目录
↓
⑤ codex
背后自动发生的是:
text
VS Code 建立 SSH
↓
SSH 读取 RemoteForward
↓
服务器生成本地转发入口
↓
Linux 工具读取代理环境变量
↓
流量经过 SSH 回到本地电脑
因此这套方式最大的优点就是:
一次配置,日常使用几乎没有额外操作。
15. 常见问题:服务器提示 Connection refused
如果出现类似:
text
Failed to connect to 127.0.0.1 port 18080
可以在服务器检查:
bash
ss -lnt | grep 18080
如果没有看到对应监听,重点检查:
text
VS Code 是否重新连接
SSH config 是否保存
RemoteForward 是否写在正确的 Host 下
本地"魔法"是否正在运行
本地端口是否填写正确
16. 两边端口号必须一样吗?
不需要。
例如:
ssh
RemoteForward 18080 127.0.0.1:31888
完全合法。
它表示:
text
服务器 18080
↓
本地 31888
因此没有必要为了"看起来整齐"强行让两个端口相同。
17. 为什么文章不用真实服务器名称和端口?
这是写技术博客时非常值得养成的习惯。
截图、终端记录、SSH config 中经常会不小心暴露:
text
真实服务器域名
真实公网 IP
SSH 用户名
特殊端口
目录名称
项目名称
Token / Key
机器编号
云厂商实例信息
公开发布之前建议全部替换。
例如本文统一采用:
text
Host: demo-codex-server
HostName: host.example.invalid
User: demo_user
远程示例端口: 18080
本地示例端口: 31888
这些值只用于讲解,不来自任何真实用户环境。
其中:
text
127.0.0.1
是标准的本机回环地址,不是某个用户的个人公网地址。
18. 推荐的"脱敏后博客模板"
以后写服务器教程,可以统一采用类似格式:
ssh
Host demo-server
HostName host.example.invalid
User demo_user
Port 22
RemoteForward <REMOTE_PORT> 127.0.0.1:<LOCAL_PORT>
服务器:
bash
export http_proxy=http://127.0.0.1:<REMOTE_PORT>
export https_proxy=http://127.0.0.1:<REMOTE_PORT>
这样博客读者一眼就知道:
text
这里需要替换成自己的参数
同时不会泄露作者本人的服务器环境。
写在最后
SSH 反向隧道这个名字看起来很专业,但在这个场景中,其实只做了一件事:
让远程服务器通过现有 SSH 连接,借用本地电脑已经合法配置好的"魔法"网络能力。
整个方案可以拆成三层:
text
第一层:VS Code Remote SSH
负责建立和维护远程开发连接
第二层:RemoteForward
负责把服务器上的端口接回本地电脑
第三层:http_proxy / https_proxy
负责告诉服务器上的程序该使用哪个入口
理解这三层之后,就不用死记命令了。
你会知道:
text
连接问题 → 看 SSH
端口问题 → 看 RemoteForward
工具不走网络 → 看 proxy 环境变量
Codex 登录问题 → 看 Codex 登录流程
对于长期使用远程 Linux / GPU 服务器做开发、学习和科研的人来说,这是一套很自然的工作流:
本地负责网络环境,SSH 负责连接,服务器专心跑代码。
发布前隐私检查清单
公开发布此类教程前,建议至少检查一次:
- 是否出现真实服务器域名或公网 IP;
- 是否出现真实 SSH 用户名;
- 是否出现个人专用端口;
- 是否出现 Token、API Key、Cookie 或验证码;
- 是否出现家庭目录中可识别个人的信息;
- 是否出现云平台实例 ID、机器名称或项目私有名称;
- 截图中是否包含侧边栏账号、历史命令、浏览器标签等无关信息;
- 代码块是否全部换成演示数据。
本文提供的版本已经按照上述原则使用虚构示例数据。