SSH 是运维工程师每天都要打交道的工具,但大多数人只停留在 ssh user@host 这一层。本文从密钥管理、安全加固、跳板机架构、隧道转发到批量运维,系统梳理 SSH 进阶技能,帮你从"会用"升级到"用好"。
一、SSH 密钥管理
1.1 Ed25519 vs RSA:该选哪个?
结论:新环境一律用 Ed25519,除非目标机器的 OpenSSH 版本低于 6.5。
1.2 密钥生成
# Ed25519(推荐)
ssh-keygen -t ed25519 -C "your_name@company.com"
# RSA 4096-bit(兼容性兜底)
ssh-keygen -t rsa -b 4096 -C "your_name@company.com"
# 带 passphrase 的密钥(生产环境必须)
ssh-keygen -t ed25519 -C "ops@prod" -f ~/.ssh/id_ed25519_prod最佳实践:
每个环境(dev/staging/prod)使用不同密钥
密钥文件命名体现用途:
id_ed25519_prod、id_ed25519_github始终设置 passphrase
1.3 ssh-agent 管理密钥
ssh-agent 是一个后台进程,缓存解密后的私钥,避免每次连接都输入 passphrase。
# 启动 agent 并添加密钥
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519
# 查看已加载的密钥
ssh-add -l
# 删除所有已加载密钥
ssh-add -D
# 设置密钥自动过期(3600 秒后从 agent 中移除)
ssh-add -t 3600 ~/.ssh/id_ed25519_prodmacOS 用户提示:macOS 的 ssh-agent 支持 Keychain 集成,密钥重启后自动加载:
ssh-add --apple-use-keychain ~/.ssh/id_ed25519在 ~/.ssh/config 中配置自动使用 Keychain:
Host *
AddKeysToAgent yes
UseKeychain yes1.4 密钥轮换
密钥不是一劳永逸的,需要定期轮换:
# 1. 生成新密钥
ssh-keygen -t ed25519 -C "ops@2026-rotate" -f ~/.ssh/id_ed25519_new
# 2. 部署新公钥到目标机器
ssh-copy-id -i ~/.ssh/id_ed25519_new.pub user@target
# 3. 验证新密钥可用
ssh -i ~/.ssh/id_ed25519_new user@target "echo ok"
# 4. 从目标机器移除旧公钥
ssh user@target "sed -i '/old_key_comment/d' ~/.ssh/authorized_keys"
# 5. 本地替换旧密钥
mv ~/.ssh/id_ed25519_new ~/.ssh/id_ed25519
mv ~/.ssh/id_ed25519_new.pub ~/.ssh/id_ed25519.pub轮换策略建议:
生产环境每 90 天轮换一次
离职人员的密钥立即清除
使用配置管理工具(Ansible)批量部署新公钥
二、SSH 配置详解
2.1 ~/.ssh/config 基础
SSH 客户端配置文件是提升效率的利器,一次配置,终身受益。
# 文件权限必须正确
chmod 700 ~/.ssh
chmod 600 ~/.ssh/config2.2 Host 别名与完整配置示例
# === 全局默认配置 ===
Host *
ServerAliveInterval 60
ServerAliveCountMax 3
AddKeysToAgent yes
IdentitiesOnly yes
Compression yes
HashKnownHosts yes
# === 生产环境 Web 服务器 ===
Host prod-web
HostName 10.0.1.50
User deploy
Port 2222
IdentityFile ~/.ssh/id_ed25519_prod
StrictHostKeyChecking ask
# === GitHub ===
Host github.com
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_github
IdentitiesOnly yes
# === GitLab(公司内部)===
Host gitlab-internal
HostName gitlab.company.com
User git
IdentityFile ~/.ssh/id_ed25519_gitlab
Port 2222
# === 生产数据库(通过跳板机)===
Host prod-db
HostName 10.0.2.100
User dbadmin
ProxyJump bastion
IdentityFile ~/.ssh/id_ed25519_prod
# === 跳板机 ===
Host bastion
HostName bastion.company.com
User ops
Port 2222
IdentityFile ~/.ssh/id_ed25519_bastion
ForwardAgent yes配置完成后,直接 ssh prod-db 就能通过跳板机连接数据库服务器,无需手动跳转。
2.3 多密钥管理技巧
当一台机器有多个密钥时,IdentitiesOnly yes 很关键——它告诉 SSH 只使用配置文件中指定的密钥,不会尝试 agent 中的其他密钥,避免因尝试过多密钥被服务器拒绝。
# 测试哪个密钥能连接某台服务器
ssh -vT git@github.com 2>&1 | grep "Offering\|Accepted"2.4 KeepAlive 防断连
NAT 网关、防火墙会杀掉空闲连接,KeepAlive 是必备配置:
Host *
# 每 60 秒发送一次心跳
ServerAliveInterval 60
# 连续 3 次无响应则断开
ServerAliveCountMax 3服务端也需要配合(/etc/ssh/sshd_config):
ClientAliveInterval 60
ClientAliveCountMax 3三、SSH 安全加固
3.1 修改默认端口
最简单的"防君子不防小人"的措施,但能过滤掉 99% 的自动化扫描:
# /etc/ssh/sshd_config
Port 2222# 重启服务
sudo systemctl restart sshd
# 别忘了防火墙放行
sudo ufw allow 2222/tcp
sudo firewall-cmd --permanent --add-port=2222/tcp
sudo firewall-cmd --reload3.2 限制登录用户
# /etc/ssh/sshd_config
# 只允许指定用户登录
AllowUsers deploy ops admin
# 或者按组限制
AllowGroups ssh-users
# 禁止 root 直接登录
PermitRootLogin no
# 禁止空密码
PermitEmptyPasswords no3.3 限制来源 IP
# 只允许办公网段和跳板机 IP
AllowUsers deploy@10.0.0.0/24 deploy@203.0.113.50
# 或者用防火墙层面限制
sudo ufw allow from 10.0.0.0/24 to any port 22223.4 限制认证尝试
# /etc/ssh/sshd_config
# 最多尝试 3 次密码/密钥认证
MaxAuthTries 3
# 登录宽限时间 30 秒
LoginGraceTime 30
# 关闭密码认证(只允许密钥)
PasswordAuthentication no
# 关闭挑战响应认证
ChallengeResponseAuthentication no3.5 完整的加固配置模板
# /etc/ssh/sshd_config — 安全加固版
Port 2222
Protocol 2
AddressFamily inet
PermitRootLogin no
PasswordAuthentication no
PermitEmptyPasswords no
ChallengeResponseAuthentication no
MaxAuthTries 3
LoginGraceTime 30
MaxSessions 5
MaxStartups 10:30:60
AllowGroups ssh-users
AllowUsers deploy ops
X11Forwarding no
AllowTcpForwarding no
AllowAgentForwarding no
UsePAM yes
PrintMotd no
# 日志级别
LogLevel VERBOSE
# 使用强加密算法
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org四、跳板机 / Bastion Host
4.1 架构设计
┌──────────┐ SSH ┌────────────┐ SSH ┌──────────────┐
│ 运维PC │ ──────────→ │ 跳板机 │ ───────→ │ 内网服务器 │
│ (公网) │ │ (Bastion) │ │ (私有网络) │
└──────────┘ └────────────┘ └──────────────┘跳板机职责:
所有对内网的 SSH 访问必须经过跳板机
跳板机上记录所有会话日志(审计)
内网服务器只允许来自跳板机的 SSH 连接
跳板机自身需要严格加固
4.2 ProxyJump 配置(OpenSSH 7.3+,推荐)
ProxyJump 是现代方式,简洁且安全:
# 命令行方式
ssh -J bastion:2222 user@internal-host
# 多级跳板
ssh -J bastion1:2222,bastion2:2222 user@final-host
# ~/.ssh/config 配置方式
Host bastion
HostName bastion.company.com
User ops
Port 2222
IdentityFile ~/.ssh/id_ed25519_bastion
Host internal-*
ProxyJump bastion
User deploy
IdentityFile ~/.ssh/id_ed25519_internal
Host internal-web
HostName 10.0.1.50
Host internal-db
HostName 10.0.2.100配置后,ssh internal-web 自动通过跳板机连接。
4.3 ProxyCommand 配置(兼容旧版本)
# ~/.ssh/config
Host internal-host
HostName 10.0.1.50
User deploy
ProxyCommand ssh -W %h:%p bastion-W %h:%p 表示将标准输入/输出转发到目标主机:端口,效果等同于 ProxyJump。
4.4 跳板机安全加固
# 跳板机的 sshd_config 特殊配置
Port 2222
PermitRootLogin no
PasswordAuthentication no
# 允许 TCP 转发(ProxyJump 需要)
AllowTcpForwarding yes
# 禁止在跳板机上开 shell(可选,极端安全场景)
# ForceCommand /bin/false
# 记录所有通过跳板机的会话
ForceCommand /usr/bin/log-session.sh会话记录脚本示例:
#!/bin/bash
# /usr/bin/log-session.sh
LOG_DIR="/var/log/ssh-sessions"
mkdir -p "$LOG_DIR"
LOG_FILE="$LOG_DIR/$(date +%Y%m%d-%H%M%S)-${USER}-$(echo $SSH_CLIENT | awk '{print $1}').log"
script -qf "$LOG_FILE"五、SSH 隧道
SSH 隧道是 SSH 最被低估的功能之一,可以安全地穿透防火墙、暴露内网服务。
5.1 本地转发(Local Forwarding)
将本地端口映射到远程服务器的某个端口。
场景:访问内网的 MySQL 数据库(10.0.2.100:3306),但你的 PC 无法直接连接。
# 将本地 13306 端口转发到内网 MySQL
ssh -L 13306:10.0.2.100:3306 user@bastion
# 然后用本地客户端连接
mysql -h 127.0.0.1 -P 13306 -u root -p
# 后台运行,不打开 shell
ssh -fNL 13306:10.0.2.100:3306 user@bastion参数说明:
-f:后台运行-N:不执行远程命令-L [bind_addr:]port:host:hostport:本地转发
5.2 远程转发(Remote Forwarding)
将远程服务器的端口映射到本地,让外部能访问你本地的服务。
场景:你在内网开发了一个 Web 服务(localhost:3000),想让同事通过公网服务器访问。
# 将公网服务器的 8080 端口转发到你本地的 3000 端口
ssh -R 8080:localhost:3000 user@public-server
# 同事访问 http://public-server:8080 即可看到你的服务注意:远程转发默认只绑定 loopback 地址,要让外部访问需要在服务端配置:
# /etc/ssh/sshd_config
GatewayPorts yes # 或 clientspecified5.3 动态转发 — SOCKS 代理
将 SSH 变成一个 SOCKS5 代理,所有流量通过远程服务器中转。
# 启动 SOCKS5 代理,监听本地 1080 端口
ssh -D 1080 user@remote-server
# 后台运行
ssh -fND 1080 user@remote-server然后在浏览器或系统中配置 SOCKS5 代理:127.0.0.1:1080。
实战场景:
访问公司内网的 Web 应用
安全地使用公共 WiFi
调试远程环境的 Web 服务
5.4 实战组合:通过跳板机访问内网 Web 控制台
# 通过跳板机将本地 8443 映射到内网 K8s Dashboard
ssh -J bastion -L 8443:10.0.3.50:6443 user@internal-host
# 浏览器打开 https://localhost:8443六、SSH 端口转发进阶
6.1 多级跳板隧道
场景:需要经过两层跳板机才能访问目标服务器。
# 方法一:ProxyJump 链式跳转(推荐)
ssh -J user1@bastion1:2222,user2@bastion2:2222 user3@target
# 方法二:多级本地转发
ssh -L 2222:bastion2:2222 user1@bastion1 &
ssh -L 3306:target-db:3306 -p 2222 user2@localhost &
# 方法三:在 ~/.ssh/config 中配置
Host bastion1
HostName bastion1.company.com
User ops1
Port 2222
Host bastion2
HostName bastion2.company.com
User ops2
Port 2222
ProxyJump bastion1
Host target
HostName 10.0.3.100
User deploy
ProxyJump bastion26.2 隧道保活
长时间运行的隧道容易因网络波动断开,需要保活机制:
# 方法一:ServerAlive 配置(客户端侧)
ssh -o ServerAliveInterval=60 -o ServerAliveCountMax=3 -L 3306:db:3306 user@bastion
# 方法二:TCPKeepAlive(更底层)
ssh -o TCPKeepAlive=yes -o ServerAliveInterval=30 -L 3306:db:3306 user@bastion6.3 autossh — 自动重连的隧道
autossh 在 SSH 连接断开时自动重新建立,是长期运行隧道的必备工具。
# 安装
sudo apt install autossh # Debian/Ubuntu
sudo yum install autossh # CentOS/RHEL
# 基本用法
autossh -M 20000 -fNL 13306:10.0.2.100:3306 user@bastion
# 参数说明
# -M 20000: 使用 20000 端口做连接监控(偶数端口发,奇数端口收)
# -f: 后台运行
# -N: 不执行远程命令
# -L: 本地转发更优雅的用法(使用 ServerAlive 替代 -M 端口监控):
autossh -M 0 -fNL 13306:10.0.2.100:3306 user@bastion \
-o "ServerAliveInterval 30" \
-o "ServerAliveCountMax 3"systemd 管理 autossh 隧道:
# /etc/systemd/system/ssh-tunnel-mysql.service
[Unit]
Description=SSH Tunnel to MySQL
After=network-online.target
Wants=network-online.target
[Service]
User=ops
Environment="AUTOSSH_GATETIME=0"
ExecStart=/usr/bin/autossh -M 0 -NL 13306:10.0.2.100:3306 -o ServerAliveInterval=30 -o ServerAliveCountMax=3 user@bastion
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.targetsudo systemctl daemon-reload
sudo systemctl enable --now ssh-tunnel-mysql
sudo systemctl status ssh-tunnel-mysql七、SSH 批量管理
7.1 PSSH(Parallel SSH)
并行执行 SSH 命令,适合小规模服务器群。
# 安装
sudo apt install pssh # Debian/Ubuntu
sudo yum install pssh # CentOS/RHEL
# 创建主机列表
cat > hosts.txt << 'EOF'
web1 ansible_host=10.0.1.11 ansible_user=deploy
web2 ansible_host=10.0.1.12 ansible_user=deploy
web3 ansible_host=10.0.1.13 ansible_user=deploy
EOF
# 并行执行命令
pssh -h hosts.txt -i "uptime"
# 并行复制文件
pscp -h hosts.txt /tmp/app.conf /opt/app/conf/
# 并行同步目录
prsync -h hosts.txt -r /opt/app/ /opt/app/
# 带日志输出
pssh -h hosts.txt -o /tmp/pssh-out -e /tmp/pssh-err "df -h"7.2 mussh(Multihost SSH)
# 安装
sudo apt install mussh
# 对多台主机执行命令
mussh -H "10.0.1.11 10.0.1.12 10.0.1.13" -c "systemctl restart nginx"
# 使用主机文件
mussh -H hosts.txt -c "free -m"7.3 结合 Ansible 做批量管理
Ansible 底层就是 SSH,适合更复杂的批量运维场景:
# inventory/hosts.yml
all:
children:
webservers:
hosts:
web1: { ansible_host: 10.0.1.11 }
web2: { ansible_host: 10.0.1.12 }
web3: { ansible_host: 10.0.1.13 }
vars:
ansible_user: deploy
ansible_ssh_private_key_file: ~/.ssh/id_ed25519_prod
ansible_ssh_common_args: '-o ProxyJump=bastion'# 批量执行命令
ansible webservers -m shell -a "df -h"
# 批量部署配置
ansible webservers -m template -a "src=nginx.conf.j2 dest=/etc/nginx/nginx.conf"
# 批量更新并重启服务
ansible webservers -m apt -a "name=nginx state=latest" --become
ansible webservers -m service -a "name=nginx state=restarted" --become八、SSH 日志审计
8.1 日志位置
# 查看最近的 SSH 登录
journalctl -u ssh --since "1 hour ago"
# 查看失败的登录尝试
grep "Failed password" /var/log/auth.log
# 统计每个 IP 的失败尝试次数
grep "Failed password" /var/log/auth.log | \
awk '{print $(NF-3)}' | sort | uniq -c | sort -rn | head -20
# 查看成功的登录
grep "Accepted" /var/log/auth.log8.2 增强审计配置
# /etc/ssh/sshd_config
LogLevel VERBOSEVERBOSE 级别会记录:
每次认证尝试使用的密钥指纹
密钥认证成功/失败的原因
会话的详细信息
8.3 登录告警脚本
#!/bin/bash
# /etc/ssh/login-notify.sh
# 在 /etc/ssh/sshd_config 中配置:
# PermitUserEnvironment yes
# 然后在 ~/.ssh/environment 中设置触发
LOG_FILE="/var/log/ssh-login-alerts.log"
DATE=$(date '+%Y-%m-%d %H:%M:%S')
# 获取登录信息
USER=$1
IP=$2
TTY=$3
# 写入日志
echo "[$DATE] SSH Login: User=$USER IP=$IP TTY=$TTY" >> "$LOG_FILE"
# 发送告警(示例:通过 webhook 发送到飞书/钉钉/Slack)
curl -s -X POST "https://open.feishu.cn/open-apis/bot/v2/hook/YOUR_HOOK_ID" \
-H "Content-Type: application/json" \
-d "{
\"msg_type\": \"text\",
\"content\": {
\"text\": \"⚠️ SSH 登录告警\n用户: $USER\nIP: $IP\n时间: $DATE\"
}
}" &
exit 0配合 PAM 模块自动触发:
# /etc/pam.d/sshd 末尾添加
session optional pam_exec.so /etc/ssh/login-notify.sh8.4 使用 fail2ban 自动封禁
# 安装
sudo apt install fail2ban
# 配置
cat > /etc/fail2ban/jail.local << 'EOF'
[sshd]
enabled = true
port = 2222
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600
findtime = 600
EOF
sudo systemctl enable --now fail2ban
# 查看被封禁的 IP
sudo fail2ban-client status sshd
# 手动解封
sudo fail2ban-client set sshd unbanip 192.168.1.100九、常见问题排查
9.1 连接超时
# 详细模式查看卡在哪一步
ssh -vvv user@host常见原因及解决:
防火墙阻断:
telnet host 22或nc -zv host 22测试端口DNS 解析慢:在
/etc/ssh/sshd_config设置UseDNS noGSSAPI 认证慢:在客户端配置
GSSAPIAuthentication no网络 MTU 问题:
ping -M do -s 1400 host测试
9.2 权限问题
SSH 对文件权限非常严格,权限不对直接拒绝:
# 服务端
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
chmod 755 ~ # home 目录不能是 777
# 客户端
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_ed25519
chmod 644 ~/.ssh/id_ed25519.pub
chmod 600 ~/.ssh/config
# 检查所有者
ls -la ~/.ssh/
# authorized_keys 必须属于当前用户,不能是 root9.3 密钥不匹配
# 查看服务器端接受的日志
ssh -vT user@host 2>&1 | grep -i "key\|identity\|offer\|accept"
# 验证公钥指纹是否一致
ssh-keygen -lf ~/.ssh/id_ed25519.pub
ssh-keygen -lf ~/.ssh/id_ed25519
# 在服务器端检查
cat ~/.ssh/authorized_keys | ssh-keygen -lf -
# 确保公钥格式正确(单行,以 ssh-ed25519 或 ssh-rsa 开头)
cat ~/.ssh/id_ed25519.pub9.4 Agent Forwarding 问题
# 检查 agent 是否运行
ssh-add -l
# 检查 agent forwarding 是否启用
ssh -o ForwardAgent=yes user@host "echo \$SSH_AUTH_SOCK"
# 如果 AUTH_SOCK 为空,检查:
# 1. 客户端 ~/.ssh/config 中 ForwardAgent yes
# 2. 服务端 /etc/ssh/sshd_config 中 AllowAgentForwarding yes
# 3. 服务端 ~/.ssh/environment 中 SSH_AUTH_SOCK 设置
# 调试:在中间机器上测试
ssh -A bastion "ssh-add -l && ssh internal-host 'whoami'"安全警告:Agent Forwarding 有风险——root 用户可以劫持你的 agent 来连接其他机器。在不信任的服务器上不要开启。
十、SSH 最佳实践清单
密钥管理
使用 Ed25519 密钥(RSA 至少 4096-bit)
每个密钥设置 passphrase
不同环境使用不同密钥
定期轮换密钥(建议 90 天)
使用 ssh-agent 管理密钥,设置自动过期时间
离职人员密钥立即清除
安全加固
修改默认端口
禁止 root 直接登录
禁止密码认证,只允许密钥
限制可登录的用户/组
限制来源 IP(防火墙 + sshd 配置双重限制)
设置 MaxAuthTries(≤5)和 LoginGraceTime(≤60)
使用强加密算法(Ciphers / MACs / KexAlgorithms)
安装 fail2ban 自动封禁暴力破解
保持 OpenSSH 版本更新
运维效率
善用 ~/.ssh/config 管理连接
配置 ServerAliveInterval 防止断连
跳板机使用 ProxyJump(而非手动跳转)
长期隧道使用 autossh + systemd
批量管理使用 Ansible
启用日志审计,配置登录告警
日常习惯
定期检查 last 和 lastb 查看登录记录
定期审查 authorized_keys 清理过期密钥
不在代码仓库中存储私钥
不在多台机器之间共享同一私钥
使用 ssh-keygen -l -f 验证密钥指纹
附录:速查命令表
# 密钥生成
ssh-keygen -t ed25519 -C "comment"
# 部署公钥
ssh-copy-id -i ~/.ssh/key.pub user@host
# 跳板机连接
ssh -J bastion user@internal
# 本地转发
ssh -L local_port:target:remote_port user@via
# 远程转发
ssh -R remote_port:localhost:local_port user@public
# SOCKS 代理
ssh -D 1080 user@host
# 自动重连隧道
autossh -M 0 -fNL 3306:db:3306 user@bastion
# 并行执行
pssh -h hosts.txt -i "command"
# 查看登录日志
journalctl -u ssh --since "1 hour ago"
last -a
lastb -a本文为《服务器运维笔记》系列篇。SSH 的进阶用法远不止这些,但掌握以上内容足以应对 95% 的生产运维场景。关键不在于记住所有命令,而在于理解背后的原理,遇到问题时知道去哪里查、怎么查。