SSH 端口转发与远程开发

SSH 端口转发完整指南:-L 本地转发、-R 反向转发、-N 隧道模式、Docker 场景、VPC 数据库跳板、localhost 歧义、与 tmux 配合的远程开发工作流。

#type / synthesis #status / evergreen #tech / ops #resource / ssh #tech / dev

[!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)
8000AI 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 会更直接。

参考

创建于 2026/7/6 更新于 2026/7/15