#DevOps

SSH 隧道与网络基础

SSH 不只是远程登录,它的隧道(端口转发)是开发者的瑞士军刀:访问内网服务、安全代理、跳板机。本文讲清本地/远程/动态三种端口转发、跳板机(ProxyJump)、SSH key 管理、以及 ssh_config 配置,让你用 SSH 解决一堆网络访问难题。

本文要点

  • 主题聚焦:DevOps
  • 适合人群:独立开发者、全栈工程师、AI 应用开发者
  • 核心价值:快速理解背景、实践路径、常见误区和工程建议
作者 @admin·发布于 2026年6月6日·更新于 2026年7月23日

大多数开发者对 SSH 的认知停留在 ssh user@host。这就像买了把瑞士军刀,只拿它开瓶盖。SSH 真正强大的地方在于它的隧道(Port Forwarding):一条命令就能穿透防火墙、加密任意 TCP 流量、把内网服务搬到本地、反向暴露本地服务给外网。

这些能力几乎是云原生时代开发者的刚需——你天天和 VPC、堡垒机、内网数据库、容器网络打交道,不懂 SSH 隧道,你就只能靠一堆临时工具和vpn硬扛。本文把端口转发、跳板机、key 管理、ssh_config 一次讲透,让你之后遇到网络访问难题,第一反应是「SSH 能搞定」。

一、SSH 不只是登录,端口转发是开发者必备技能

先建立一个核心心智模型:SSH 隧道的本质是「在一条加密的 SSH 连接里,偷偷塞进去其它 TCP 流量」。你在本地和远程主机之间建好一条 SSH 通道,然后告诉它:把某个端口的流量转发到另一端去。所有数据都走 SSH 的加密通道,对中间网络完全透明。

端口转发分三种,记住这个区分就够了:

  • 本地转发 -L:把远程端口搬到本地(本地→远程)。访问远程内网服务用这个。
  • 远程转发 -R:把本地端口搬到远程(本地→远程暴露,反向)。内网穿透用这个。
  • 动态转发 -D:SOCKS 代理,一次性转发任意目标。全局代理用这个。

注意一个高频踩坑:转发是否生效,取决于目标服务在远程机器上的监听地址。PG 默认只监听 127.0.0.1,你得在远程连本地的 5432 才能拿到;监听 0.0.0.0 才是全网卡。先 ss -tlnp 看清楚,别一上来就怀疑 SSH。

还有一点:从 OpenSSH 7.6 开始(2017 年),-L/-R 默认只绑定到 localhost,不是 0.0.0.0。如果你想跨机器访问转发出来的端口,必须显式写绑定地址,例如 -L 0.0.0.0:5432:... 或在 ssh_config 里设 GatewayPorts yes。这是 90% 「为什么别人连不上我的转发端口」的根因。

二、本地端口转发(-L):把远程服务的端口映射到本地

最常用的形态。典型场景:数据库在内网 VPC,你的笔记本连不到,但能 SSH 到一台跳板机。

bash
ssh -N -L 15432:db.internal:5432 jumpbox

拆开看参数:

  • -N:不执行远程命令,纯转发(否则会开个 shell,关了终端就断)。
  • -L [绑定地址:]本地端口:远程目标:远程端口
  • 15432:db.internal:5432:本地 15432 → 经 jumpbox → 转发到 db.internal:5432。

执行后,你本地 psql -h 127.0.0.1 -p 15432 就等于在 jumpbox 上访问 db.internal:5432。注意「远程目标」是从 jumpbox 的视角 解析的,不是你本地——所以 db.internal 必须是 jumpbox 能 DNS 解析到的名字。

绑定地址那条坑再强调一次:-L 15432:... 只绑定 127.0.0.1,只有你本机能连;写成 -L 0.0.0.0:15432:... 才允许局域网其他机器通过你这台机器访问(配合 GatewayPorts yes 更省心)。

实战建议:别用默认端口做转发,用 15432、13306、16379 这种加一位的口诀,一眼就知道是转发出来的,避免和你本地装的 PG/MySQL/Redis 撞端口。后台跑加 -f(fork 到后台),组合 -fN 是开发机常驻转发的标准姿势。

bash
ssh -fNT -L 15432:db.internal:5432 jumpbox
# 后续用 ss -tlnp | grep 15432 确认监听
# 干活完毕:ps aux | grep 'ssh -fNT' 找 PID,kill 掉

三、远程端口转发(-R):反向,内网穿透的鼻祖

-L 是把远程搬过来,-R 反过来:把本地的服务,通过远程机器暴露出去。

最经典场景:你本地起了个 Web 服务(localhost:3000),想给客户/同事/微信测试号预览,但你在家/在公司内网,外面访问不到。只要有一台公网 VPS:

bash
ssh -N -R 8080:localhost:3000 vps.example.com

含义:在 vps 上开 8080 端口,流量转发到你本地的 3000。外人访问 http://vps.example.com:8080 就访问到你本地服务了——这就是内网穿透,frp/ngrok 干的事 SSH 一直能干。

关键坑:-R 8080:... 默认在 vps 上只绑定到 127.0.0.1,外人还是连不到。要在 vps 的 /etc/ssh/sshd_config 设:

text
GatewayPorts yes

然后重启 sshd(systemctl restart sshd),再用 -R 0.0.0.0:8080:localhost:3000 vps 才会真正监听公网。安全提醒:暴露公网一定要加认证,或只对特定 IP 开放,别把没鉴权的管理后台甩到 0.0.0.0。

-R 还有个进阶用法:反向 SSH 进内网。目标机器在 NAT 后面,主动外连你的公网机器:

bash
# 在内网机器上跑(开机自启)
ssh -fNR 2222:localhost:22 vps.example.com
# 之后在 vps 上 ssh -p 2222 localhost 就进入了内网机器

这是 IoT、家庭服务器、远程办公访问公司内网的常用招式。稳定性差一点(SSH 长连接会被 NAT 踢),生产环境建议配 autossh 自动重连:

bash
autossh -M 0 -fN -o "ServerAliveInterval 30" -o "ServerAliveCountMax 3" \
  -R 2222:localhost:22 vps.example.com

-M 0 关掉老的监控端口,改用 ServerAliveInterval 做心跳,这是 autossh 的现代用法。

四、动态端口转发(-D):SOCKS 代理,全局代理

前面两个都是「一个端口对一个端口」。-D 直接给你一个 SOCKS5 代理,流量想转发到哪就到哪。

bash
ssh -D 1080 -N vps.example.com

执行后本地 1080 就是个 SOCKS5 代理,所有走它的流量都从 vps 出去。浏览器配 SwitchyOmega 指向 127.0.0.1:1080,或者命令行:

bash
curl --socks5 127.0.0.1:1080 https://ifconfig.me
# 你会看到 vps 的公网 IP

应用场景:访问公司内部一堆域名(没有公网 DNS)、临时让某个工具走特定出口 IP、调试 geo-restricted 服务。比开 VPN 轻得多,随用随关。

进阶技巧——把 SOCKS 代理变成全局透明代理:

bash
# proxychains 让任意命令强制走 SOCKS
echo "socks5 127.0.0.1 1080" >> ~/.proxychains/proxychains.conf
proxychains curl https://internal.company.local/api
proxychains npm install   # 公司内网 npm 源

注意 -D 默认也是只绑 127.0.0.1,想共享给同事加 0.0.0.0 绑定:-D 0.0.0.0:1080。但 SOCKS 默认无认证,共享一定要谨慎,或改用 -D + SSH 自带的认证(SOCKS 走 SSH 连接本身,本质上认证就是你的 SSH key)。

五、跳板机:ProxyJump(-J)一条命令通过堡垒机

老办法是嵌套 SSH:ssh -t jump 'ssh internal'。难用、SCP 不支持、rsync 折腾。OpenSSH 7.3+(2016 年)开始,官方给了 -J / ProxyJump,一行搞定:

bash
ssh -J jump.internal target.internal

含义:先连 jump.internal,再通过它连 target.internal。SCP、rsync、端口转发全部正常工作:

bash
# 通过跳板机传文件
scp -J jump.internal file.tar target:/tmp/
# 通过跳板机做本地转发,访问 target 上的 PG
ssh -J jump.internal -L 15432:localhost:5432 target.internal

多级跳板机也支持,逗号分隔:

bash
ssh -J jump1,jump2,internal.target target

每一跳会按 user@host:port 格式解析,如 -J admin@jump:2222,root@jump2。配合下面的 ssh_config,真正用起来一条 ssh target 就够了。别再用 -t 嵌套了,那玩意儿是上个时代的产物。

六、SSH key:生成、上传、ssh-agent、passphrase

key 是 SSH 的灵魂。三条铁律:

  1. 用 ed25519:快、短、安全。RSA 除非老系统兼容,否则别用了。
  2. 必须设 passphrase:key 文件落盘加密,丢了/泄露了不至于立刻完蛋。
  3. key 分离:不同环境不同 key(公司一台、个人一台、生产一台),出事能精准吊销。

生成:

bash
ssh-keygen -t ed25519 -C "work@codenodex-2026" -f ~/.ssh/id_ed25519_work
# 一路回车前,passphrase 一定要填

上传公钥到目标机器(把 ~/.ssh/id_ed25519_work.pub 内容追加到目标机器的 ~/.ssh/authorized_keys):

bash
ssh-copy-id -i ~/.ssh/id_ed25519_work.pub user@host
# 老系统不支持就用猫:
cat ~/.ssh/id_ed25519_work.pub | ssh user@host 'mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys'

authorized_keys 权限是 600,.ssh 目录是 700,权限不对 SSH 会静默拒绝,日志里写 "Authentication refused: bad ownership"。这是排查 SSH key 不生效的第一步。

ssh-agent 用来缓存 passphrase,避免每次输入:

bash
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519_work
# 之后这个 key 在当前会话免密

macOS 上 ssh-add --apple-use-keychain ~/.ssh/... 把 passphrase 存进钥匙串,重启不丢;Linux 看发行版,通常配 AddKeysToAgent yes 在 ssh_config 里自动加。生产环境用 systemd user service 管 ssh-agent 是干净做法。

查看当前 agent 里有哪些 key:

bash
ssh-add -l

七、ssh_config:把常用主机配置写进文件

记 IP、记端口、记 key、记跳板机——人脑不该干这个。~/.ssh/config 是答案:

sshconfig
Host bastion
    HostName bastion.prod.codenodex.cn
    User deploy
    Port 2222
    IdentityFile ~/.ssh/id_ed25519_work
    ForwardAgent yes

Host db-prod
    HostName 10.0.1.20
    User postgres
    ProxyJump bastion
    LocalForward 15432 127.0.0.1:5432
    ServerAliveInterval 30

Host *.internal
    User ubuntu
    ProxyJump bastion
    IdentityFile ~/.ssh/id_ed25519_work
    StrictHostKeyChecking accept-new

# 全局默认
Host *
    ServerAliveInterval 60
    ServerAliveCountMax 3
    AddKeysToAgent yes
    IdentitiesOnly yes

效果:

bash
ssh bastion           # = ssh -p 2222 -i ~/.ssh/... deploy@bastion...
ssh db-prod           # 自动走 bastion 跳板,自动转发 5432 到本地 15432
ssh web1.internal     # 通配符匹配,自动跳板,自动用对的 key

LocalForward 写进 config 后,每次连自动转发,日常用就是这么丝滑。ServerAliveInterval 30 解决 NAT 踢连接的痛点。IdentitiesOnly yes 强制只使用指定的 key,避免 agent 里堆了一堆 key 触发服务器 MaxAuthTries 限制(默认 6 次失败就断)。

配置文件层级:~/.ssh/config 是用户级,/etc/ssh/ssh_config 是系统级。两者都加载,前者优先。团队可以维护一份 /etc/ssh/ssh_config 模板放进 Ansible/SaltStack 统一分发。

八、实战场景

场景 1:本地连远程 PostgreSQL

最经典。PG 在内网,你只能 SSH 到跳板机:

bash
# 临时
ssh -fNT -L 15432:db.internal:5432 bastion
psql -h 127.0.0.1 -p 15432 -U postgres

# 长期用就写进 config(见上面 db-prod 那段),然后:
ssh -fN db-prod
psql -h 127.0.0.1 -p 15432

DataGrip/DBeaver 里 host 填 127.0.0.1、port 填 15432,直接连。GUI 工具也支持内置 SSH 隧道,但用 OS 层的隧道更可控、好调试(ss -tlnp 一看便知)。

场景 2:通过堡垒机访问内网一堆机器

bash
ssh -J bastion web1.internal
ssh -J bastion web2.internal
# 或者直接靠 ssh_config 的通配符:
ssh web1.internal   # 自动 ProxyJump bastion

调试端口也走跳板:

bash
# web1 内网 8080 端口的服务,本地访问
ssh -L 18080:localhost:8080 web1.internal   # 走 config 里 ProxyJump
curl http://127.0.0.1:18080/health

场景 3:临时暴露本地服务

给客户演示本地起的 Next.js 应用(localhost:3000):

bash
ssh -R 0.0.0.0:8080:localhost:3000 vps.example.com
# vps 上确保 GatewayPorts yes,然后发链接:
# http://vps.example.com:8080

要 HTTPS?在 vps 上用 Caddy 一行反代到 8080,自动 Let's Encrypt。比 ngrok 免费、可控、不限流。

场景 4:全局走公司出口访问内网域名

公司内部有一堆 *.corp.local,本地 DNS 解析不到:

bash
ssh -D 1080 -fN bastion
proxychains curl https://wiki.corp.local
proxychains git clone [email protected]:team/repo.git

小结

  • 三种转发的本质:-L 本地→远程(访问内网服务),-R 反向暴露(内网穿透),-D SOCKS 代理(全局代理)。一张图记住方向。
  • GatewayPorts 和绑定地址是 90% 的转发故障来源。默认只绑 127.0.0.1,跨机访问必须显式 0.0.0.0 或开 GatewayPorts yes。
  • ProxyJump 替代一切嵌套 SSH,scp/rsync/转发全部支持,别再用 ssh -t 'ssh ...'。
  • key 用 ed25519 + passphrase + ssh-agent,分环境隔离,authorized_keys 权限 600/.ssh 700 是排查第一步。
  • ssh_config 把复杂参数固化成别名,LocalForward、ProxyJump、ServerAliveInterval 一次配置,终身受益。

SSH 隧道这套东西学会了,你会发现云原生时代一半的「网络访问难题」其实都不需要新工具。一行 ssh -fNT -L,比折腾 VPN、改路由表、装代理软件快十倍。工具不在多,在熟——SSH 就是开发者网络工具箱里那把最趁手的瑞士军刀,别让它继续吃灰了。

Related

相关推荐