← cd ../posts

SSH 不只是登录:tunnel / agent / 跳板机 / config 模板

2026-07-03 ◈ Linux 系列

这是 Linux 系列的第 20 篇——网络章节收尾。前面讲了"网络怎么通",这一篇讲怎么用 SSH 这一个工具把你和服务器之间的一切都打通

0. SSH 的本质:一条加密的字节流通道

你以为 SSH 只是个 telnet 升级版?它实际是一种多路复用的加密通道协议

  • 默认在通道里跑一个 PTY(伪终端)→ 这就是"登录"
  • 也能在通道里跑任意 TCP 端口 → "tunnel"
  • 也能跑 X11 协议 → 远程图形界面
  • 也能跑 socket 转发 → "agent forwarding"

理解这一点,下面所有"花式用法"都是同一通道的不同切片。


1. 基础 + ~/.ssh/config

最基本:

$ ssh user@server
$ ssh -p 22022 user@server
$ ssh -i ~/.ssh/my_key user@server

敲多了非常累~/.ssh/config 给每台机器起别名:

# ~/.ssh/config

Host openclaw
    HostName 101.37.211.203
    User root
    Port 22
    IdentityFile ~/.ssh/id_openclaw

Host web-prod
    HostName web.example.com
    User deploy
    Port 22022
    IdentityFile ~/.ssh/deploy_key

# 通配符
Host *.internal
    User admin
    StrictHostKeyChecking no
    UserKnownHostsFile /dev/null

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

配完:

$ ssh openclaw           # 等价于 ssh -p 22 -i ~/.ssh/id_openclaw [email protected]
$ ssh web-prod
$ ssh db.internal        # 匹配 *.internal

scp / rsync 也认这些别名:

$ scp file openclaw:/tmp/
$ rsync -av ./ web-prod:/var/www/

强烈建议第一台服务器就给配上——一年节省的打字量惊人。

常用 config 字段速记

字段 用途
HostName 真实 IP / 域名
User 远端用户
Port SSH 端口
IdentityFile 用哪个私钥
IdentitiesOnly yes 用 IdentityFile,不试别的
ProxyJump 跳板(见下面)
LocalForward 本地端口转发
RemoteForward 远端端口转发
ServerAliveInterval 心跳间隔(防 NAT idle 断)
ServerAliveCountMax 连续几次心跳失败就断连
ControlMaster auto + ControlPath ~/.ssh/cm-%r@%h:%p 连接复用:下次 ssh 同一台秒进
Compression yes 压缩(慢网络有用,快网络可能反而慢)

2. SSH 密钥 + agent

# 生成新密钥(推荐 ed25519,比 RSA 短且强)
$ ssh-keygen -t ed25519 -C 'my-key 2026'
# 默认存 ~/.ssh/id_ed25519 + .pub

# 把公钥放到服务器
$ ssh-copy-id user@server
# 或手动:cat ~/.ssh/id_ed25519.pub | ssh user@server "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"

ssh-agent:少敲一次密码

私钥设了 passphrase 后,每次 ssh 都问一遍——烦。ssh-agent 帮你缓存:

# 启动 agent
$ eval $(ssh-agent -s)

# 加密钥
$ ssh-add ~/.ssh/id_ed25519
Enter passphrase: ...
Identity added.

# 看现在 agent 里有哪些
$ ssh-add -l

之后 ssh 不再要密码。

Mac 用户:macOS 内置 keychain 集成,配 config:

Host *
    AddKeysToAgent yes
    UseKeychain yes              # macOS-only
    IdentityFile ~/.ssh/id_ed25519

ssh-add 一次 → 重启都还在。

Agent forwarding:从 A→B→git 都不用复制 key

$ ssh -A server
$ ssh server                       # 配 ForwardAgent yes 后等价

意思是"server 上的 ssh 进程能用我本地 agent 里的密钥"。用途

你本地 → ssh → server → 在 server 上 git clone [email protected]:... 
                              ↑ 用你本地的 GitHub key(而不是 server 上的)

安全提示-A 让远端 root 能用你的所有密钥——只对你信任的服务器开。

Host github-builder
    ForwardAgent yes        # 只对这一台开

3. Port forward(tunnel):SSH 通道里跑别的协议

Local forward(-L):本地端口 → 远端可达的目标

最高频。"我本地 localhost:5432 想访问 server 后面的 postgres":

$ ssh -L 5432:db-internal:5432 jump-host

读法:

-L  本地端口 : 远端可达的目标 host : 目标端口
    │       │                    │
    5432    db-internal          5432

发生:

本地 psql localhost:5432
   ↓ TCP
你电脑的 ssh 进程
   ↓ SSH tunnel(加密)
jump-host 的 sshd
   ↓ TCP
db-internal:5432

psql -h localhost -p 5432 就连到了藏在内网的数据库,不暴露 db 到公网

Remote forward(-R):远端端口 → 本地可达的目标

反过来——把本地服务暴露给远端

# 本地跑了个 dev server 在 8080
$ python -m http.server 8080 &

# 让 server 上的人能访问
$ ssh -R 8080:localhost:8080 server

# server 上:curl localhost:8080 → 实际访问你本地的 8080

用途

  • 给同事临时演示本地服务
  • 在没公网 IP 的家用机器上跑服务,通过有公网 IP 的 VPS 暴露
  • ngrok / cloudflare tunnel 的免费替代

Dynamic forward(-D):SOCKS 代理

把整条 SSH 连接变成 SOCKS5 代理:

$ ssh -D 1080 jump-host

之后浏览器配 SOCKS5 = localhost:1080所有流量从 jump-host 出去。等于一个临时 VPN,常用于:

  • 翻看墙内被墙的资源(合规场景)
  • 让外部测试看你内网的服务

4. Jump host:跳板机穿多层

很多公司架构:

你本地 → bastion(堡垒机/跳板机,公网 IP) → 内网服务器
                                              (没公网,只有 bastion 能 ssh)

传统做法:先 ssh bastion,进去后 ssh internal-server。麻烦。

ProxyJump(SSH 7.3+)一键穿透:

$ ssh -J bastion internal-server

或写到 config:

Host bastion
    HostName bastion.example.com
    User me

Host *.internal
    User me
    ProxyJump bastion

之后:

$ ssh db.internal           # 自动经 bastion → db.internal
$ scp file db.internal:/tmp/
$ rsync -av ./ db.internal:/path/

多层跳板

Host hop2
    ProxyJump hop1

Host target
    ProxyJump hop2

走 你 → hop1 → hop2 → target,配一次终生用。


5. 连接复用(control master):第二次秒进

每次 ssh 都要 TCP 握手 + TLS 握手 + 鉴权 = 几百毫秒。配复用:

Host *
    ControlMaster auto
    ControlPath ~/.ssh/cm-%r@%h:%p
    ControlPersist 10m

第一次连后台开个长连接(保留 10 分钟),后续 ssh / scp / rsync 同一台直接复用——0.05 秒就进。

  • 长连接没断开时你改不了 user / port(用同一通道)
  • 想强制重连:ssh -O exit <host> 先关掉控制连接
  • 通道挂了所有依赖它的 session 一起死

6. 文件传输:scp / rsync / sftp 都走 ssh

# scp(简单,但 OpenSSH 9 起用 sftp 底层,部分 flag 变了)
$ scp file.txt server:/tmp/
$ scp -r dir/ server:/tmp/
$ scp server:/etc/hosts ./remote-hosts
$ scp -P 22022 file server:/tmp/                # 注意 P 大写
$ scp -J bastion file internal:/tmp/             # 跳板

# rsync(智能、增量、可断点续传,**强烈推荐**)
$ rsync -avP file server:/tmp/
$ rsync -avP -e 'ssh -p 22022' file server:/tmp/  # 改端口

# sftp(交互式)
$ sftp server
sftp> ls
sftp> put file.txt
sftp> get remote.txt
sftp> bye

99% 场景 rsync 优于 scp(断点续传 + 增量 + 进度条)。


7. 远程执行命令 / 脚本

# 远端跑一条命令立刻退出
$ ssh server 'uptime'

# 多条
$ ssh server 'date; uptime; df -h'

# 跑本地脚本到远端(不用 scp 上去)
$ ssh server 'bash -s' < my-script.sh

# 带变量 / 参数
$ ssh server "echo Hello $USER"           # 双引号让 $USER 在本地展开
$ ssh server 'echo Hello $USER'           # 单引号让 $USER 在远端展开

# heredoc 跑一坨远端命令
$ ssh server 'bash -s' <<'EOF'
set -e
cd /var/www/app
git pull
systemctl restart myapp
EOF

# 在多台机器上并行(用 GNU parallel)
$ parallel -j 5 ssh {} uptime ::: web1 web2 web3 web4 web5

8. 实战:一些超有用的高阶用法

A. SSH 当 git 跳板(写代码不用复制 key 上 server)

Host github.com
    User git
    IdentityFile ~/.ssh/github_key
    
Host devbox
    HostName 1.2.3.4
    ForwardAgent yes

server 上 git clone / git pull 用的是你本地的 GitHub key——server 上根本没 key 文件,最安全。

B. SSH config 加 alias 触发某条命令

Host k8s-shell
    HostName k8s-bastion
    User me
    RemoteCommand kubectl exec -it api-pod -- /bin/bash
    RequestTTY yes

ssh k8s-shell → SSH 进 bastion → 立刻自动 kubectl exec 进 pod。一条命令穿 3 层。

C. 端口转发后自动后台 + 不开 shell

$ ssh -fNT -L 5432:db:5432 jump-host
  • -f background
  • -N 不执行命令(只建 tunnel)
  • -T 不分配 TTY

适合后台开几条 tunnel 不打扰你的终端。

D. 用 SSH 当 SOCKS 代理 + autossh 自动重连

$ sudo apt install autossh
$ autossh -M 0 -fNT -D 1080 jump-host

autossh 在网络抖动时自动重连——比 ssh 直接跑稳得多。

E. tmux + ssh 防断网

跑长任务时,先 ssh,再开 tmux

$ ssh server
$ tmux new -s work
(跑长任务)
Ctrl+B d        # 脱离 tmux,回到原 shell

# 几小时后
$ ssh server
$ tmux attach -t work       # 回到之前的会话

ssh 断了 tmux 不死。


9. SSH 安全清单(公网服务器必看)

# /etc/ssh/sshd_config (server 端,不是 client 的 ~/.ssh/config)

# 强制 key 登录,禁密码(关键!防爆破)
PasswordAuthentication no
PubkeyAuthentication yes

# 禁 root 直接 ssh(要先 ssh 普通用户再 sudo)
PermitRootLogin no
# 或者只允许 key 登录 root(保留紧急救援能力)
PermitRootLogin prohibit-password

# 限制能 ssh 的用户
AllowUsers alice bob deploy

# 改默认端口(防扫描器,但不是真安全;安全靠 key)
Port 22022

# 缩短超时,干净退出空闲连接
ClientAliveInterval 60
ClientAliveCountMax 3

# 禁用 X11 / agent forward 等用不到的
X11Forwarding no

改完:

$ sudo sshd -t           # 语法测试(不重启)
$ sudo systemctl restart sshd

测试前别关现有 ssh 会话——万一改错了,新 ssh 进不来你还有老连接救场。


10. 现在做一件事

# 1. 生成一对密钥(如果还没)
$ ssh-keygen -t ed25519

# 2. 配 ~/.ssh/config 给你常连的服务器起别名
$ vim ~/.ssh/config

# 3. 把 ssh-add 调成 auto(加到 ~/.zshrc 或 ~/.bashrc)
$ eval $(ssh-agent -s) ; ssh-add ~/.ssh/id_ed25519
# Mac:直接配 UseKeychain yes

# 4. 给你最频繁的服务器开 ControlMaster
# 测一下:连过一次后再 ssh 同一台
$ time ssh server exit
$ time ssh server exit       # 第二次应该 < 0.1s

# 5. 试一次 port forward 把远端服务"借"到本地
$ ssh -L 6379:localhost:6379 server
# 另一个 terminal:redis-cli -h localhost

SSH 是日常运维的 80% 入口——把它调到顺手,你之后做任何远程操作都飞快。


下一篇observability——top / iotop / iostat / vmstat / strace,机器在干什么——CPU / 内存 / 磁盘 / 网络的实时观测。

$ ls related/