SSH 端口转发与远程开发
SSH 端口转发完整指南:-L 本地转发、-R 反向转发、-N 隧道模式、Docker 场景、VPC 数据库跳板、localhost 歧义、与 tmux 配合的远程开发工作流。
[!info] related notes
SSH 端口转发与远程开发
核心思路
端口转发可以理解成:你本地电脑开一个”假端口”,这个端口背后通过 SSH 隧道转到远程服务器上的真实服务端口。
Windows 浏览器
http://localhost:5173
↓
Windows 本机 5173 端口
↓
SSH 隧道(加密)
↓
DigitalOcean 服务器 localhost:5173
↓
远程 Vite / Next / API 服务
远程开发场景里,它解决的核心问题是:代码和服务跑在云服务器上,但页面、接口测试、浏览器 DevTools 在本地电脑上看。
为什么远程开发需要端口转发
假设在 DigitalOcean 服务器里启动前端:
pnpm dev
输出 Local: http://localhost:5173,这里的 localhost:5173 是远程服务器自己的 localhost,不是 Windows 电脑的 localhost。
本地浏览器直接打开 http://localhost:5173 访问的是 Windows 本机的 5173 端口,不是远程服务器的 Vite 服务。端口转发就是让两者连起来。
-L 本地端口转发
最常用的方式。命令格式:
ssh -L 本地端口:目标地址:目标端口 用户名@SSH服务器
单端口转发
ssh -L 5173:localhost:5173 do-dev
含义:把本地 5173 端口转发到远程服务器视角下的 localhost:5173。
多端口转发
前后端分离项目通常需要同时转发多个端口:
ssh \
-L 5173:localhost:5173 \
-L 8080:localhost:8080 \
-L 8000:localhost:8000 \
do-dev
| 端口 | 服务 |
|---|---|
| 5173 | 前端 dev server(Vite / Next) |
| 8080 | 后端 API(Go / Node) |
| 8000 | AI Service(Python / uvicorn) |
本地浏览器访问 http://localhost:5173,看到的就是远程服务器上的页面。
-L 语法拆解
ssh -L 5173:localhost:5173 do-dev
三段含义:
5173 本地电脑监听的端口
localhost 从远程服务器视角访问的目标地址
5173 远程目标服务端口
注意:中间的 localhost 是从远程服务器视角来看的。不是转发回 Windows 自己,而是当流量进了 SSH 服务器后,让 SSH 服务器去访问它自己的 localhost:5173。
本地端口和远程端口可以不同
ssh -L 3000:localhost:5173 do-dev
含义:本地 localhost:3000 → 远程 localhost:5173。本地 5173 被占用时这样处理。
-N 只开隧道不进入终端
有时候已经有 tmux 终端在开发了,只想单独开一个端口转发,不想再进入 shell:
ssh -N \
-L 5173:localhost:5173 \
-L 8080:localhost:8080 \
-L 8000:localhost:8000 \
do-dev
-N 的意思:不执行远程命令,只建立 SSH 隧道。这个窗口专门负责隧道,保持开着,本地浏览器就能访问远程服务。
-fN 后台运行
加 -f 让 SSH 进入后台,不占用任何终端:
ssh -fN \
-L 5173:localhost:5173 \
-L 8080:localhost:8080 \
do-dev
为什么比公网端口更安全
SSH 端口转发 vs 直接开放公网端口:
SSH 端口转发:
服务器服务只监听 localhost
公网只能连 SSH 22 端口
Web 页面通过加密 SSH 隧道访问
不需要额外开放 5173 / 8080 / 8000
公网端口:
开发服务暴露在公网
别人可能扫到你的端口
Vite / Next dev server 不是为公网安全设计的
测试接口、调试页面、错误信息可能泄露
localhost 到底是谁
这是端口转发最容易搞混的点。在不同位置,localhost 含义不同:
Windows 终端里的 localhost = Windows 本机
SSH -L 中间的 localhost = SSH 服务器视角下的 localhost
Docker 容器里的 localhost = 容器自己
浏览器里的 localhost = Windows 本机
所以 ssh -L 5173:localhost:5173 do-dev 的含义是:浏览器访问 Windows localhost:5173,转发到远程服务器 localhost:5173。不是转发回 Windows 自己。
本地端口默认只监听 127.0.x.x
默认情况下 ssh -L 5173:localhost:5173 do-dev 只让本机访问,局域网其他设备访问不到。这是安全的。
不要随便用 -g 或绑定 0.0.x.x:
# 不推荐:局域网也能访问转发端口
ssh -g -L 5173:localhost:5173 do-dev
ssh -L 0.0.x.x:5173:localhost:5173 do-dev
日常开发建议始终绑定本地:
ssh -N \
-L 127.0.x.x:5173:localhost:5173 \
-L 127.0.x.x:8080:localhost:8080 \
do-dev
~/.ssh/config 配置
基础配置
Host do-dev
HostName 你的服务器IP
User root
Port 22
ServerAliveInterval 30
ServerAliveCountMax 6
之后 ssh do-dev 即可连接,端口转发手动加 -L。
把端口转发写进 config
Host do-dev
HostName 你的服务器IP
User root
Port 22
ServerAliveInterval 30
ServerAliveCountMax 6
LocalForward 5173 localhost:5173
LocalForward 8080 localhost:8080
LocalForward 8000 localhost:8000
ssh do-dev 就自动转发所有端口。
分离登录和隧道
更推荐分开两个配置:
Host do-dev
HostName 你的服务器IP
User root
Port 22
ServerAliveInterval 30
ServerAliveCountMax 6
Host do-dev-tunnel
HostName 你的服务器IP
User root
Port 22
ServerAliveInterval 30
ServerAliveCountMax 6
LocalForward 5173 localhost:5173
LocalForward 8080 localhost:8080
LocalForward 8000 localhost:8000
ssh do-dev:只登录服务器ssh do-dev-tunnel:登录 + 自动端口转发
Docker Compose 场景
端口映射
services:
web:
ports:
- "5173:5173"
api:
ports:
- "8080:8080"
ai-service:
ports:
- "8000:8000"
远程 docker compose up 后,服务器的 localhost:5173 等端口可以访问。本地再用 SSH 转发。
更安全的绑定
services:
web:
ports:
- "127.0.x.x:5173:5173"
api:
ports:
- "127.0.x.x:8080:8080"
ai-service:
ports:
- "127.0.x.x:8000:8000"
这样端口不会暴露到服务器公网网卡,只能通过服务器本机访问,再配合 SSH 隧道。这是远程开发比较优雅的安全配置。
Vite 在 Docker 里需要注意 --host
裸机运行 Vite:
pnpm dev
SSH -L 5173:localhost:5173 可以访问
Docker 运行 Vite:
pnpm dev --host 0.0.x.x
compose ports 暴露 5173
SSH -L 5173:localhost:5173 可以访问
Docker 容器内部的 localhost 只代表容器自己,不代表宿主机,所以需要 --host 0.0.x.x 让 Vite 监听所有接口。
Vite / Next 的 --host 注意事项
有些 dev server 默认只监听 127.0.x.x,这对 SSH 转发是可以的,因为 SSH 服务器会访问远程的 localhost。
只有以下场景才需要 --host 0.0.x.x:
- 想让局域网或公网直接访问服务器 IP:5173
- 在 Docker 容器里跑 Vite,需要容器外访问
通过跳板机访问 VPC 数据库
DigitalOcean 里 Managed Database 的 VPC 地址只能被同 VPC 的 Droplet 访问,本地电脑访问不到。但可以通过 Droplet 转发。
PostgreSQL
DigitalOcean 给的私网地址类似:
private-db-postgres-sgp1-xxx.db.ondigitalocean.com:25060
本地执行:
ssh -N \
-L 15432:private-db-postgres-sgp1-xxx.db.ondigitalocean.com:25060 \
do-dev
本地连接:
psql "postgresql://user:password@localhost:15432/dbname?sslmode=require"
链路:
本地 psql → localhost:15432 → SSH 隧道 → DigitalOcean Droplet → VPC 内网 → Managed PostgreSQL:25060
Valkey / Redis
ssh -N \
-L 16379:private-db-vk-sgp1-xxx.db.ondigitalocean.com:25061 \
do-dev
本地连接:
redis-cli -u "rediss://default:password@localhost:16379"
[!warning] TLS 主机名校验 有些托管数据库证书会校验主机名。PostgreSQL/Redis 如果因为 TLS 主机名不匹配报错,开发阶段可以用工具提供的 SSL 参数调整;生产环境还是让应用在 DO 服务器内直接连 private host 更好。
-R 反向端口转发
-L 是本地访问远程服务,-R 是远程服务器访问本地服务。
ssh -R 3000:localhost:3000 do-dev
链路:
远程服务器 localhost:3000 → SSH 隧道 → Windows 本机 localhost:3000
使用场景:
- 远程服务器需要回调本地服务
- 远程测试环境要访问本地 mock server
- Webhook 临时调试
日常远程开发 Web 页面主要用 -L,不是 -R。
-D 动态 SOCKS 代理
ssh -D 1080 do-dev
在本地创建一个 SOCKS5 代理 127.0.x.x:1080,浏览器或工具配置后,流量通过远程服务器出去。
使用场景:访问只有服务器网络能访问的内网站点、调试区域网络问题、临时代理流量。和 Web 端口预览不是同一个核心场景。
端口冲突处理
本地 5173 已被占用时:
ssh -L 3000:localhost:5173 do-dev
浏览器打开 http://localhost:3000。前面是本地端口,后面是远程端口,不需要一样。
验证隧道是否工作
本地测试
curl -I http://localhost:5173
curl http://localhost:8080/health
能返回内容说明隧道通了。
远程测试
# 在服务器上
curl -I http://localhost:5173
curl http://localhost:8080/health
ss -lntp | grep 5173
ss -lntp | grep 8080
问题定位
远程 curl 不通 → 服务没启动 / 监听地址不对 / Docker 端口没映射
远程 curl 通,本地 curl 不通 → SSH 隧道没开 / 本地端口冲突 / 命令写错
本地 curl 通,浏览器不正常 → 前端环境变量 / CORS / Cookie / WebSocket / HMR 问题
VS Code Remote SSH 的端口转发
VS Code Remote SSH 会自动检测远程服务端口,也可以在 Ports 面板手动添加。本质上也是 SSH 端口转发,只是图形化了。
Windows Terminal: ssh -L ...
VS Code Ports: 图形化端口转发
主力终端 / tmux 用户掌握 ssh -L;VS Code Remote 用户用 Ports 面板辅助。
与 tmux 配合的完整工作流
Terminal 1:项目主工作区
ssh do-dev -t "tmux new -A -s bodysense"
tmux 里面:
window 1: nvim
window 2: web dev server
window 3: api server
window 4: ai service
window 5: logs
window 6: git / lazygit
Terminal 2:端口转发
ssh -N \
-L 127.0.x.x:5173:localhost:5173 \
-L 127.0.x.x:8080:localhost:8080 \
-L 127.0.x.x:8000:localhost:8000 \
do-dev
Browser:本地预览
http://localhost:5173
本地 API 测试
curl http://localhost:8080/health
或用 Apifox / Postman / Bruno 访问 http://localhost:8080。
本地连接远程数据库
# PostgreSQL(经 Droplet 访问 VPC 私网数据库)
ssh -N -L 127.0.x.x:15432:private-db-postgres-xxx.db.ondigitalocean.com:25060 do-dev
# Valkey
ssh -N -L 127.0.x.x:16379:private-db-vk-xxx.db.ondigitalocean.com:25061 do-dev
最终效果:
代码在云端
服务在云端
浏览器在本地
接口测试在本地
连接通过 SSH 加密隧道
开发端口不暴露公网
tmux 保持开发现场
前端环境变量注意事项
远程开发时前端页面在本地浏览器打开,但代码服务跑在远程服务器。前端请求后端时使用本地转发后的地址:
VITE_API_BASE_URL=http://localhost:8080
不要写服务器公网 IP:
# 不推荐
VITE_API_BASE_URL=http://服务器IP:8080
除非真的把后端端口暴露到公网。
边界
- SSH 端口转发适合单人开发。多人协作或需要固定域名时,考虑 Cloudflare Tunnel 或反向代理。
- SSH 隧道断开后需要重新建立。配合 tmux 使用时,远程服务不会中断,只需重连 SSH 即可。
- 如果你更依赖 GUI 调试器、图形化文件树、IDE 插件生态,VS Code Remote SSH 会更直接。