Tools

SSH 高级配置实战:从密钥管理到跳板机自动化的完整指南

✎ -- 字 🕐 -- 分钟
字号

SSH 高级配置实战封面

说实话,我见过太多人用了五年 SSH,配置还在停留在ssh root@IP然后手动输密码的阶段。不是说这样不能用,而是当你管理的服务器超过三台、当你需要穿过多层内网、当你的密钥散落在各个同事的.ssh目录里——低效和安全风险就会像滚雪球一样越滚越大。

这篇文章不讲基础概念,直接上生产环境验证过的配置方法论。从密钥选型到跳板机自动化,目标是让你登录服务器的过程顺滑到无感知。

一、密钥类型全解析:RSA 真的已经过时了

很多人生成密钥还在用默认的 RSA 2048,但密码学界早在几年前就已经转向更安全的算法。三种主流密钥类型的对比:

算法推荐密钥长度签名速度安全性兼容性
RSA4096 bit中等(量子脆弱)几乎所有系统
ECDSA256 bit (secp256r1)中高等OpenSSH 5.7+
Ed25519256 bit 固定很快高(抗侧信道)OpenSSH 6.5+

我的建议:新环境直接用 Ed25519,老系统 fallback 到 RSA 4096。Ed25519 的密钥长度只有 68 个字符,签名速度快了不止一个量级,而且设计时就考虑了侧信道攻击防护。

生成 Ed25519 密钥对

# 生成 Ed25519 密钥对,带上注释方便识别用途
ssh-keygen -t ed25519 -C "alois@workstation-$(date +%Y%m%d)" -f ~/.ssh/id_ed25519_alois

# 旧系统 fallback 用 RSA 4096
ssh-keygen -t rsa -b 4096 -C "alois@legacy-system" -f ~/.ssh/id_rsa_legacy

这里有个细节:-f指定文件名后,私钥和公钥会成对生成。建议按用途命名,而不是默认的id_rsa,否则密钥一多你就分不清哪把钥匙开哪把锁。

密钥的权限问题

SSH 对私钥权限极其敏感,权限不对直接拒绝连接:

# 私钥必须是 600,公钥 644 即可
chmod 600 ~/.ssh/id_ed25519_alois
chmod 644 ~/.ssh/id_ed25519_alois.pub

# .ssh 目录本身也有要求
chmod 700 ~/.ssh

很多人遇到Permissions are too open报错就是这里出的问题。Windows 用户用 WSL 或者 Git Bash 时也要特别注意,Windows 的 ACL 权限模型和 Unix 不一样,有时候需要手动修复。

二、SSH 配置文件:把所有参数写进一个文件

与其每次敲一长串参数,不如把常用配置写进~/.ssh/config。这个文件的威力被严重低估,它能做的事远超你的想象。

基础配置模板

# ~/.ssh/config
Host web-prod
    HostName 154.23.185.226
    User deploy
    Port 2222
    IdentityFile ~/.ssh/id_ed25519_alois
    ServerAliveInterval 60
    ServerAliveCountMax 3

Host db-staging
    HostName 10.0.4.15
    User ubuntu
    IdentityFile ~/.ssh/id_ed25519_alois
    LocalForward 5433 localhost:5432

配置完之后,直接ssh web-prod就能连上,所有参数自动带入。省下来的时间积少成多。

生产级配置详解

Host *
    # 连接保持:防止 NAT 防火墙掐掉空闲连接
    ServerAliveInterval 60
    ServerAliveCountMax 3
    
    # 压缩:低速网络环境有用,高速网络反而有 CPU 开销
    Compression yes
    
    # 连接复用:对频繁 SSH 的场景(如 Ansible、Git)提升明显
    ControlMaster auto
    ControlPath ~/.ssh/sockets/%r@%h-%p
    ControlPersist 10m
    
    # 安全加固:禁用已知有问题的算法
    Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com
    MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
    KexAlgorithms curve25519-sha256,ecdh-sha2-nistp521
    
    # 可视化:遇到主机密钥变更时显示指纹差异
    VisualHostKey yes
    
    # 哈希 known_hosts,防止泄露服务器清单
    HashKnownHosts yes

ControlMaster这个选项特别值得展开。它的原理是在第一次 SSH 连接时建立一个主通道,后续连接复用这条 TCP 链路。对于批量执行命令的场景(比如 Ansible 跑 playbook),能省去大量握手的开销。注意ControlPath对应的目录要提前创建:

mkdir -p ~/.ssh/sockets

三、跳板机与多级转发:穿透多层内网

公司生产环境通常不会直接把数据库或者核心服务暴露在公网,而是通过一台跳板机(Bastion)作为入口。手动先跳板再连接的操作太繁琐,SSH 的 ProxyJump 可以一键搞定。

场景一:单级跳板

Host bastion
    HostName jump.company.com
    User jumpuser
    IdentityFile ~/.ssh/id_ed25519_alois

Host db-internal
    HostName 192.168.10.50
    User dbadmin
    IdentityFile ~/.ssh/id_ed25519_alois
    ProxyJump bastion

配置完成后,ssh db-internal会自动先连跳板机,再通过跳板机跳转目标。你感知到的就是一次无缝连接。

场景二:多级跳板(堡垒机 → 业务网关 → 数据库)

Host db-deep
    HostName 10.0.0.15
    User postgres
    IdentityFile ~/.ssh/id_ed25519_alois
    ProxyJump bastion,gateway

ProxyJump支持逗号分隔的多级跳板,按顺序逐级穿透。比老版的ProxyCommand nc ...写法简洁太多。

场景三:本地端口转发(暴露远程服务到本机)

Host db-tunnel
    HostName 192.168.10.50
    User dbadmin
    IdentityFile ~/.ssh/id_ed25519_alois
    ProxyJump bastion
    LocalForward 15432 localhost:5432
    LocalForward 16379 localhost:6379

连上db-tunnel后,你本地的 15432 端口就映射到了远程数据库的 5432 端口。直接用psql -h localhost -p 15432就能访问内网数据库,不需要暴露任何公网端口。

场景四:远程端口转发(反向隧道,内网穿透)

# 在内网服务器上执行,把本地 8080 映射到公网服务器的 18080
ssh -R 18080:localhost:8080 user@public-server.com

这个技巧在调试 webhook、接收第三方回调时非常有用。配合autossh还能实现断线自动重连。

四、SSH Agent 与密钥转发:免密的艺术

如果你的密钥设置了 passphrase(强烈建议),每次使用都要输密码会很烦。ssh-agent就是来解决这个问题的——一次解锁,全程免密。

Agent 基础用法

# 启动 agent(大多数 Linux 桌面环境已经自动启动)
eval "$(ssh-agent -s)"

# 添加密钥到 agent(只需输入一次 passphrase)
ssh-add ~/.ssh/id_ed25519_alois

# 查看已加载的密钥
ssh-add -l

# 清空 agent(离开工位时建议执行)
ssh-add -D

Agent Forwarding:跳板场景下的免密传递

从 A 连到 B 再连到 C 时,B 上不应该存放你的私钥。这时需要 Agent Forwarding,让 A 的 agent 代理 B 的认证请求:

Host bastion
    HostName jump.company.com
    ForwardAgent yes
    IdentityFile ~/.ssh/id_ed25519_alois

警告:Agent Forwarding 有安全风险。如果跳板机被攻破,攻击者可以通过转发的 agent 访问你所有已加载密钥能登录的服务器。建议只在可信的跳板机上开启,或者使用更安全的ProxyJump替代。

更安全的替代方案:ProxyJump + Agent

Host internal
    HostName 10.0.0.50
    ProxyJump bastion
    # 不需要 ForwardAgent,ProxyJump 会自动处理认证

ProxyJump 的认证发生在本地,私钥永远不会离开你的机器,这是推荐的做法。

五、自动化运维脚本实战

批量密钥分发

新服务器上线时,最烦的就是手动复制公钥。写个脚本一键搞定:

#!/bin/bash
# deploy-key.sh - 批量分发 SSH 公钥

PUB_KEY="$HOME/.ssh/id_ed25519_alois.pub"
SERVERS=(
    "deploy@web1:2222"
    "deploy@web2:2222"
    "ubuntu@db1:22"
)

for server in "${SERVERS[@]}"; do
    IFS=':' read -r user_host port <<< "$server"
    echo "→ Deploying key to $user_host (port: $port)..."
    
    ssh-copy-id -i "$PUB_KEY" -p "$port" "$user_host"
    
    if [ $? -eq 0 ]; then
        echo "  ✓ Success"
    else
        echo "  ✗ Failed"
    fi
done

批量执行命令

#!/bin/bash
# run-remote.sh - 在多台服务器执行命令

HOSTS=(web1 web2 web3)
CMD="df -h | grep -E '(Filesystem|/data$)'"

for host in "${HOSTS[@]}"; do
    echo "=== $host ==="
    ssh "$host" "$CMD" 2>/dev/null || echo "Connection failed"
    echo
done

配合 tmux 的并行批量操作

#!/bin/bash
# 在 tmux 多个 pane 中同时登录所有服务器

HOSTS=(web1 web2 web3 db1 cache1)
SESSION="prod-$(date +%H%M)"

tmux new-session -d -s "$SESSION"

for i in "${!HOSTS[@]}"; do
    host="${HOSTS[$i]}"
    if [ $i -eq 0 ]; then
        tmux send-keys -t "$SESSION:0.0" "ssh $host" C-m
    else
        tmux split-window -t "$SESSION:0"
        tmux send-keys "ssh $host" C-m
    fi
done

tmux select-layout -t "$SESSION:0" tiled
tmux attach -t "$SESSION"

这个脚本配合 tmux 的synchronize-panes功能(按Ctrl+B然后:setw synchronize-panes on),你可以在多个服务器的 shell 里同步输入命令。批量重启服务、批量查看日志时效率极高。

六、服务器端安全加固

客户端配置再花哨,服务器端不加固也是白搭。生产环境建议至少做到以下几点:

/etc/ssh/sshd_config 关键配置

# 禁用 root 直接登录
PermitRootLogin no

# 禁用密码认证,只用密钥
PasswordAuthentication no
PubkeyAuthentication yes

# 限制允许登录的用户
AllowUsers deploy ubuntu git

# 修改默认端口(不是安全措施,但可以减少扫描噪音)
Port 2222

# 禁用空密码
PermitEmptyPasswords no

# 限制认证尝试次数
MaxAuthTries 3

# 空闲超时断开
ClientAliveInterval 300
ClientAliveCountMax 2

# 禁用 X11 和 TCP 转发(按需开启)
X11Forwarding no
AllowTcpForwarding no

修改完配置后,不要直接重启 SSHD。先用sshd -t测试配置文件语法,确认无误后再systemctl restart sshd。另外,改端口和禁用密码之前,确保你的密钥已经能正常登录,否则可能把自己锁在门外。

fail2ban 防暴力破解

# 安装
sudo apt install fail2ban

# 配置 /etc/fail2ban/jail.local
[sshd]
enabled = true
port = 2222
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600
findtime = 600

七、常见陷阱与排错指南

现象根因解决方法
Permission denied (publickey)服务器上没有对应公钥,或权限不对检查 authorized_keys 和目录权限(700/600)
Connection refused端口错误或服务未启动确认端口,检查systemctl status sshd
WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED服务器重装或中间人攻击ssh-keygen -R hostname清除旧指纹后重连
Broken pipeNAT 超时或网络不稳定配置 ServerAliveInterval
Too many authentication failuresssh-agent 加载了太多密钥IdentitiesOnly yes限制,或清理 agent
locale 警告本地和远程 locale 不匹配ssh 加-o SendEnv=LANG或服务器端配置默认 locale

调试连接问题的利器:-vvv

# 三级详细输出,看完整握手过程
ssh -vvv user@host

# 只看认证阶段的调试信息
ssh -o LogLevel=DEBUG user@host

遇到连不上的问题时,不要猜,直接上-vvv。输出里会明确告诉你卡在哪一步:是 DNS 解析失败?TCP 连不上?还是密钥认证被拒?

总结

SSH 的配置深度远超大多数人的日常使用。花一小时把配置文件整理好、密钥管理规范起来、跳板机规则梳理清楚,后续省下的时间远不止一小时。

核心要点再提一遍:Ed25519 替代 RSA,config 文件管理所有连接参数,ProxyJump 处理跳板场景,Agent 管理 passphrase,ServerAlive 保持长连接,服务端配合 fail2ban 做最后一道防线。把这些串起来,你的远程服务器管理体验会顺畅很多。