服务器没有显示器。你和大侠的机器之间只有一条 SSH 连接 —— 这条连接必须安全、可靠、能穿透。
- 怎么安全登录远端(认证与加密)
- 怎么在两端搬文件(scp / rsync / sftp)
- 怎么穿透网络限制(端口转发、跳板机)
- 怎么让会话不因断网丢失(tmux / screen)
SSH = 加密隧道 + 认证 + 多路复用。
sequenceDiagram
participant C as 客户端
participant S as 服务端 (sshd:22)
C->>S: TCP 连接
S->>C: 发送主机公钥
C->>C: 校验主机指纹(known_hosts)
C->>S: 密钥交换(DH/ECDH)→ 生成会话密钥
Note over C,S: 此后全部加密
C->>S: 认证(公钥 / 密码 / OTP)
S->>C: 认证通过,建立会话
两个"密钥"要分清(新手最常混淆):
| 主机密钥 | 用户密钥 | |
|---|---|---|
| 谁的 | 服务器的身份 | 你的身份 |
| 存哪 | 服务端 /etc/ssh/ssh_host_*,客户端记在 ~/.ssh/known_hosts |
私钥在客户端 ~/.ssh/id_*,公钥在服务端 ~/.ssh/authorized_keys |
| 作用 | 防止中间人(确认连的是真服务器) | 让你免密登录 |
| 变了怎么办 | 报 REMOTE HOST IDENTIFICATION HAS CHANGED |
重新生成并部署公钥 |
$ ssh user@host
$ ssh -p 2222 user@host # 指定端口
$ ssh user@host uptime # 直接执行命令(不进交互 shell)
$ ssh -i ~/.ssh/special_key user@host # 指定私钥
$ ssh -v user@host # 调试(-vvv 更详细)← 连不上第一命令
$ ssh -o ConnectTimeout=5 user@host
$ ssh -t user@host sudo systemctl restart nginx # 强制分配终端(sudo 需要)# ① 生成密钥(ed25519 是现代推荐,比 RSA 更短更安全)
$ ssh-keygen -t ed25519 -C "axu@laptop"
# 也可生成 RSA(兼容老系统)
$ ssh-keygen -t rsa -b 4096 -C "axu@laptop"
# ② 部署公钥到服务端
$ ssh-copy-id user@host
# 手工方式:
$ cat ~/.ssh/id_ed25519.pub | ssh user@host 'mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys'
# ③ 验证
$ ssh -o PreferredAuthentications=publickey user@host服务端权限要求(权限不对,SSH 会静默拒绝公钥认证 —— 这是最常见的坑):
$ chmod 700 ~/.ssh
$ chmod 600 ~/.ssh/authorized_keys
$ chmod 600 ~/.ssh/id_ed25519 # 私钥,客户端侧
$ chmod 644 ~/.ssh/id_ed25519.pub
$ chmod 700 ~ # 家目录权限也别太开放$ eval "$(ssh-agent -s)"
$ ssh-add ~/.ssh/id_ed25519
$ ssh-add -l # 已加载的密钥
$ ssh-add -D # 全部清除
$ ssh-add -t 3600 ~/.ssh/id_ed25519 # 1 小时后自动失效
$ ssh-keygen -p -f ~/.ssh/id_ed25519 # 改私钥密码keyring/agent 转发:
ssh -A host可以把 agent 转发到远端,让你在跳板机上用本地密钥。但这是安全风险 —— 跳板机的 root 可以用你的 agent 登录任何你有权限的机器。谨慎使用。
# ~/.ssh/config ← 管理多台机器的核心文件
Host github.com
HostName ssh.github.com # 有些网络封 22 端口,走 443
Port 443
User git
IdentityFile ~/.ssh/id_ed25519
Host pihole
HostName 192.168.1.2
User pi
IdentityFile ~/.ssh/id_ed25519
ForwardAgent no
Host vps
HostName vps.example.com
User ubuntu
IdentityFile ~/.ssh/cloud.pem
ServerAliveInterval 60 # 每 60 秒发心跳,防止 NAT 超时断开
Host internal
HostName 10.0.0.5
User deploy
ProxyJump bastion # ← 通过跳板机连接
# 老写法等价于:ProxyCommand ssh -W %h:%p bastion
Host *
ServerAliveInterval 30
ServerAliveCountMax 3
AddKeysToAgent yes
HashKnownHosts yes配好之后:ssh pihole、ssh internal 直接可用。
sshd -t 检查语法。
Port 2222 # 换端口(减少扫描噪音,不是真安全措施)
PermitRootLogin no # 禁止 root 直接登录 ← 基本要求
PasswordAuthentication no # 只允许密钥 ← 防暴力破解最有效
PubkeyAuthentication yes
PermitEmptyPasswords no
MaxAuthTries 3
X11Forwarding no # 不需要就别开
AllowUsers deploy axu # 白名单
ClientAliveInterval 300
ClientAliveCountMax 2$ sudo sshd -t # 语法检查 ← 必须做
$ sudo systemctl reload sshd # reload 而非 restart(不断开现有连接)改完 sshd 但你没验证:
reload不会断开已建立的连接,所以你的当前会话还活着。用第二个终端测试能不能新登录,确认后再关掉旧会话。更进一步:用
fail2ban或sshguard自动封禁爆破 IP(见 09 章)。
注意:OpenSSH 9.0 起 scp 默认改用 SFTP 协议(老的 -O 可回退)。
$ scp file.txt user@host:/tmp/
$ scp user@host:/tmp/file.txt ./
$ scp -r dir/ user@host:/tmp/ # 递归
$ scp -P 2222 file user@host:/tmp/ # 注意是大写 P(ssh 是小写 p)
$ scp -C bigfile user@host:/tmp/ # 压缩传输
$ scp -l 1000 file user@host:/tmp/ # 限速 Kbit/s
$ scp user1@h1:/f user2@h2:/f # 两端都是远端(需 agent)rsync 的核心优势:增量传输 + 断点续传 + 保留属性 + 灵活过滤。
$ rsync -avz src/ user@host:/dest/ # 最常用组合
$ rsync -avz --delete src/ dest/ # 让目标与源完全一致(⚠️ 会删目标多余文件)
$ rsync -avz --dry-run src/ dest/ # 先演练,看会做什么 ← 好习惯
$ rsync -avzP big.iso user@host:/data/ # -P = 进度 + 断点续传
$ rsync -avz -e "ssh -p 2222" src/ host:/dest/
$ rsync -avz --exclude='*.log' --exclude='.git/' src/ dest/
$ rsync -avz --exclude-from=.rsyncignore src/ dest/
$ rsync -avz --bwlimit=5000 src/ dest/ # 限速 KB/s
$ rsync -avz --partial --progress src/ dest/
$ rsync -n -avzi src/ dest/ # -i 显示变更项明细参数速记:
| 参数 | 含义 |
|---|---|
-a |
归档模式(= -rlptgoD,保留链接/权限/时间/属主) |
-v |
详细输出 |
-z |
传输时压缩(局域网可不加,还费 CPU) |
-P |
--partial --progress |
-n / --dry-run |
演练 |
--delete |
删除目标端多余文件(镜像同步必用,手滑必伤) |
--exclude |
排除 |
-e |
指定远程 shell |
--bwlimit |
限速 |
⚠️ 尾部斜杠的语义差异是 rsync 最经典的坑:
rsync -av src/ dest/→ 把src的内容放进destrsync -av src dest/→ 在dest里创建src目录另外
--delete配合错的源路径等于删库。永远先--dry-run。
$ sftp user@host
sftp> put local.txt /remote/path/
sftp> get /remote/file.txt
sftp> ls -l ; cd ; lcd ; pwd ; bye
$ sshfs user@host:/remote/path /mnt/point -o reconnect # 把远端目录挂载到本地
$ fusermount -u /mnt/point| 场景 | 推荐 |
|---|---|
| 单文件、一次 | scp |
| 目录、重复同步 | rsync |
| 增量备份 | rsync --link-dest(硬链接快照式备份) |
| 极大量小文件 | 先 tar 打包再传(省握手开销) |
| 两端都是远端 | rsync + agent,或先拉到本地再推 |
# tar 流式传输(不落中间文件)← 很实用
$ tar czf - /data/dir | ssh user@host 'tar xzf - -C /backup/'
# rsync 增量备份(每天一份硬链接快照)
$ rsync -a --link-dest=/backup/prev /data/ /backup/today/这是 SSH 被严重低估的能力:它可以把任意 TCP 流量塞进加密隧道。
| 类型 | 参数 | 方向 | 用途 |
|---|---|---|---|
| 本地转发 | -L |
本地端口 → 远端目标 | 访问远端内网服务 |
| 远程转发 | -R |
远端端口 → 本地目标 | 把本地服务暴露给远端 |
| 动态转发 | -D |
本地 SOCKS 代理 | 全局代理上网 |
| 跳板 | -J / -W |
— | 穿过堡垒机 |
# 本地转发:把远端 5432 映射到本地 15432
$ ssh -L 15432:localhost:5432 user@dbhost
$ psql -h 127.0.0.1 -p 15432 # 现在本地就能连远端数据库
# 穿过跳板访问内网第三台机器
$ ssh -L 8080:internal-web:80 user@bastion
# 远程转发:把本地 3000 暴露到远端的 8080(内网穿透常用)
$ ssh -R 8080:localhost:3000 user@public-host
$ # 需要在远端 sshd 里允许 GatewayPorts(如需绑 0.0.0.0)
# 动态转发:本地 SOCKS5 代理
$ ssh -D 1080 user@host
$ curl --socks5 127.0.0.1:1080 https://example.com
$ # 浏览器/系统代理设为 socks5://127.0.0.1:1080 即可全局走隧道
# 跳板机(推荐写法)
$ ssh -J bastion internal-host
$ ssh -J user1@bastion1,user2@bastion2 target # 多级跳
# 保持连接的完整写法
$ ssh -N -f -L 15432:localhost:5432 user@dbhost # -N 不执行命令,-f 后台
$ autossh -M 0 -N -L 15432:localhost:5432 user@dbhost # 断线自动重连在 ~/.ssh/config 里固化:
Host db-tunnel
HostName dbhost
User axu
LocalForward 15432 localhost:5432
ExitOnForwardFailure yes
ServerAliveInterval 30
Host socks
HostName gateway
User axu
DynamicForward 1080SSH 断线后进程会被 SIGHUP 杀死。长期任务(编译、备份、部署)必须放进终端复用器。
$ tmux # 新建会话
$ tmux new -s deploy # 命名会话
$ tmux ls # 列出会话
$ tmux attach -t deploy # 重新接入
$ tmux kill-session -t deploytmux 快捷键(默认前缀 Ctrl+b):
| 快捷键 | 作用 |
|---|---|
Ctrl+b d |
detach(脱离,会话继续跑) |
Ctrl+b c |
新窗口 |
Ctrl+b n/p |
下/上一个窗口 |
Ctrl+b % / " |
垂直/水平分屏 |
Ctrl+b 方向键 |
切换窗格 |
Ctrl+b [ |
进入复制模式(可滚动查看历史) |
Ctrl+b , |
重命名窗口 |
# 脚本里用 tmux 跑长任务
$ tmux new -d -s backup 'rsync -avzP /data/ /backup/'
$ tmux ls
$ # 更规范的做法:交给 systemd-run
$ systemd-run --unit=backup --property=Type=oneshot rsync -avz /data/ /backup/
nohup cmd &也有用,但不方便回看输出。 长任务优先 tmux 或 systemd。
| 症状 | 原因 |
|---|---|
Connection refused |
sshd 没跑 / 端口不对 / 防火墙拦 |
Connection timed out |
网络不通 / 防火墙 DROP / IP 错 |
Permission denied (publickey) |
公钥没部署 / 权限不对(~/.ssh 或家目录太开放)/ 用户错 |
REMOTE HOST IDENTIFICATION HAS CHANGED |
服务器重装或换了密钥。确认无误后 ssh-keygen -R host |
Host key verification failed |
首次连接未确认(非交互场景) |
| 连上就断 / 卡住 | ServerAliveInterval 没配;MTU 问题;-v 看卡在哪步 |
Too many authentication failures |
agent 里密钥太多,SSH 逐个试超限:IdentitiesOnly yes |
sudo 报 no tty present |
ssh -t 分配终端 |
| 传文件中断 | 用 rsync -P 断点续传 |
-L 转发失败 |
AllowTcpForwarding no;端口被占 |
$ ssh -vvv user@host 2>&1 | tail -40 # 定位认证卡在哪步
$ sudo sshd -T | grep -i -E "password|permitroot|pubkey" # 看实际生效的配置
$ sudo journalctl -u ssh -n 50
$ sudo journalctl -u ssh -f | grep -i "invalid user" # 看爆破尝试
$ ss -tlnp | grep :22- 换 SSH 端口不是安全措施(只是减少日志噪音),密钥认证 + fail2ban 才是。
~/.ssh权限必须严格,否则密钥认证静默失效。-A(agent 转发)有风险,别在不可信机器上用。- rsync 尾斜杠语义不同,且
--delete方向搞反会删数据。 scp -P是大写,ssh -p是小写。- 主机密钥变更可能是中间人攻击,确认原因再
known_hosts清理。 - 长任务不放 tmux = 断网就白跑。
- SSH 证书认证(CA 签发,适合大规模)
-
ProxyCommand高级用法(nc、openssl s_client 穿透) - Mosh:移动网络下的稳定远程
- WireGuard / Tailscale 作为 SSH 的替代路径
- 堡垒机方案(JumpServer、Teleport)
- rsync 快照式备份完整方案
- SSH 多路复用(
ControlMaster、ControlPersist)加速 - 二进制文件差分传输(
zsync、bita) - 2FA / U2F 硬件密钥登录 SSH