SSH 是运维工程师每天都要打交道的工具,但大多数人只停留在 ssh user@host 这一层。本文从密钥管理、公钥部署、服务端加固、fail2ban 防御、跳板机架构、隧道转发到批量运维,系统梳理 SSH 进阶技能。
暴露在公网的服务器,SSH 的默认配置会在 auth 日志里留下持续不断的爆破尝试:22 端口加密码登录相当于把入口敞开。安全加固要叠加多层关卡:修改端口避开自动化扫描、防火墙限制来源 IP、只允许密钥登录、fail2ban 封禁失败尝试。每一层都在抬高攻击成本。
SSH 密钥管理
Ed25519 vs RSA:该选哪个?
| 对比项 | Ed25519 | RSA (4096-bit) |
|---|---|---|
| 密钥长度 | 256-bit(固定) | 2048 / 4096-bit |
| 安全强度 | ≈ RSA-3072 | 取决于位数 |
| 性能 | 签名/验证更快 | 较慢 |
| 兼容性 | OpenSSH 6.5+(2014 年起) | 几乎所有版本 |
| 推荐场景 | 默认首选 | 兼容老旧系统 |
结论:新环境一律用 Ed25519,除非目标机器的 OpenSSH 版本低于 6.5。
密钥生成
密钥对在你自己的电脑上生成,落到 VPS 上的只有公钥:
# 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| 参数 | 含义 |
|---|---|
-t ed25519 |
指定算法,ed25519 比 RSA 更短更快 |
-C "..." |
注释,便于区分多个密钥 |
-f path |
私钥文件名 |
生成结果有两个文件:私钥 ~/.ssh/my-vps-key 绝不泄露、绝不上传;公钥 ~/.ssh/my-vps-key.pub 可以分发到所有需要登录的服务器。查看公钥内容用 cat ~/.ssh/my-vps-key.pub,把整行复制下来使用。
passphrase 取舍:交互使用的生产密钥建议始终设置 passphrase;无人值守的自动化或中转场景可单独生成不带 passphrase 的专用密钥,配合 ssh-agent 或凭据保险库管理,避免明文写入配置文件。
最佳实践:
- 每个环境(dev/staging/prod)使用不同密钥
- 密钥文件命名体现用途:
id_ed25519_prod、id_ed25519_github - 私钥按机器隔离:每台开发机生成自己的密钥,服务器
authorized_keys里按行添加,任何一台机器丢失时只删除对应那一行
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 yes密钥轮换
密钥需要管理有效期、撤销和轮换:
# 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/authorized_keys,一条公钥占一行,多台设备共用同一文件没有问题。
ssh-copy-id 一键部署
最简单的方式,执行一次输入密码即可:
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@<server>公钥会自动追加到服务器的 ~/.ssh/authorized_keys。
手动追加公钥
商家最小系统未装 ssh-copy-id、或 Windows 上没有对应工具时,先密码登录再手动追加:
ssh user@<server>
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI...........xxx user@host" >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
# 验证权限
ls -la ~/.ssh/权限要求
权限过宽时 sshd 会拒绝读取密钥文件,最常见的现象是配置了密钥仍被要求输密码。目录与文件权限必须满足:
drwx------ 2 user user . (.ssh 700)
-rw------- 1 user user authorized_keys (600)服务端 ~/.ssh 应为 700、authorized_keys 应为 600;home 目录不能是 777,authorized_keys 的所有者必须是登录用户。
验证密钥登录
新开一个终端验证,保留已经确认可用的原会话作为恢复通道:
ssh -i ~/.ssh/id_ed25519 user@<server>不输密码直接进入即表示成功。
SSH 配置详解
~/.ssh/config 基础
SSH 客户端配置文件是提升效率的利器,一次配置,终身受益。
# 文件权限必须正确
chmod 700 ~/.ssh
chmod 600 ~/.ssh/configHost 别名与完整配置示例
# === 全局默认配置 ===
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 就能通过跳板机连接数据库服务器,无需手动跳转。
多密钥管理技巧
当一台机器有多个密钥时,IdentitiesOnly yes 很关键——它告诉 SSH 只使用配置文件中指定的密钥,不会尝试 agent 中的其他密钥,避免因尝试过多密钥被服务器拒绝。
# 测试哪个密钥能连接某台服务器
ssh -vT git@github.com 2>&1 | grep "Offering\|Accepted"KeepAlive 防断连
NAT 网关、防火墙会杀掉空闲连接,KeepAlive 是必备配置:
Host *
# 每 60 秒发送一次心跳
ServerAliveInterval 60
# 连续 3 次无响应则断开
ServerAliveCountMax 3服务端也需要配合(/etc/ssh/sshd_config):
ClientAliveInterval 60
ClientAliveCountMax 3SSH 安全加固
修改默认端口
把 22 换成高位端口,能过滤掉大部分按默认端口扫描的自动化脚本。先备份原配置:
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak在 /etc/ssh/sshd_config 设置新端口(示例为 2222,实际建议在 1024-65535 之间自选):
Port 2222先检查语法再重启。sudo sshd -t 无输出表示配置正确;出现 Bad configuration option 之类错误时不要重启,否则 sshd 起不来,外部访问会被锁在门外:
sudo sshd -t端口放行与重启:
sudo ufw allow 2222/tcp
sudo firewall-cmd --permanent --add-port=2222/tcp
sudo firewall-cmd --reload
sudo systemctl restart sshd
sudo systemctl status sshd确认状态是 active (running),再检查监听端口确实变成新端口:
sudo ss -tlnp | grep ssh
# LISTEN 0 128 0.0.0.0:2222 0.0.0.0:* users:(("sshd",pid=xxx,fd=3))验证后删除 22 端口的放行规则。旧终端保留,新开终端用新端口登录,成功进入后再清理:
ssh -p 2222 user@<server>
sudo ufw delete allow 22云厂商用平台控制台代理 SSH 时,入口往往固定监听 22,改端口后要在厂商侧同步调整安全组与入口配置。
限制登录用户
# /etc/ssh/sshd_config
# 只允许指定用户登录
AllowUsers deploy ops admin
# 或者按组限制
AllowGroups ssh-users
# 禁止 root 直接登录
PermitRootLogin no
# 禁止空密码
PermitEmptyPasswords no用密钥登录且需要保留 root 时,可使用 PermitRootLogin prohibit-password,root 只能通过密钥进入,密码登录被禁用。
限制来源 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 2222限制认证尝试
# /etc/ssh/sshd_config
# 最多尝试 3 次密码/密钥认证
MaxAuthTries 3
# 登录宽限时间 30 秒
LoginGraceTime 30
# 关闭密码认证(只允许密钥)
PasswordAuthentication no
# 关闭挑战响应认证
ChallengeResponseAuthentication no服务端配置对照表
修改前后逐个核对语义:
| 配置项 | 改前 | 改后 | 作用 |
|---|---|---|---|
Port |
22 | 2222 | 避开自动化扫描 |
PubkeyAuthentication |
yes/no | yes | 启用密钥 |
PasswordAuthentication |
yes | no | 关闭密码登录 |
PermitEmptyPasswords |
no/yes | no | 禁止空密码 |
PermitRootLogin |
yes | prohibit-password | root 只允许密钥 |
MaxAuthTries |
6 | 3 | 限制单次连接的尝试次数 |
完整的加固配置模板
# /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.orgfail2ban 自动封禁
密钥登录和改端口都挡不住攻击者扫到新端口后继续尝试。fail2ban 监控认证日志,多次失败后按规则封禁来源 IP 一段时间。
安装并写入 /etc/fail2ban/jail.local:
sudo apt install fail2ban[sshd]
enabled = true
port = 2222
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600
findtime = 600
backend = auto
# 家庭/办公 IP 加白名单,避免误封自己
ignoreip = 127.0.0.1/8 ::1 <你的家庭 IP>logpath 随发行版不同:基于 auth 日志的系统写 /var/log/auth.log,基于 journald 的发行版按 journal 配置相应过滤。
启动并验证:
sudo systemctl enable --now fail2ban
sudo systemctl status fail2ban查看封禁状态,手动封禁或解封:
sudo fail2ban-client status
sudo fail2ban-client status sshd
sudo fail2ban-client set sshd banip 1.2.3.4
sudo fail2ban-client set sshd unbanip 1.2.3.4fail2ban 对 SSH 爆破有效,但防不了零日漏洞,保持系统更新才是根本。
跳板机 / Bastion Host
架构设计
┌──────────┐ SSH ┌────────────┐ SSH ┌──────────────┐
│ 运维PC │ ──────────→ │ 跳板机 │ ───────→ │ 内网服务器 │
│ (公网) │ │ (Bastion) │ │ (私有网络) │
└──────────┘ └────────────┘ └──────────────┘跳板机职责:
- 所有对内网的 SSH 访问必须经过跳板机
- 跳板机上记录所有会话日志(审计)
- 内网服务器只允许来自跳板机的 SSH 连接
- 跳板机自身需要严格加固
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 自动通过跳板机连接。
ProxyCommand 配置(兼容旧版本)
# ~/.ssh/config
Host internal-host
HostName 10.0.1.50
User deploy
ProxyCommand ssh -W %h:%p bastion-W %h:%p 表示将标准输入/输出转发到目标主机:端口,效果等同于 ProxyJump。
跳板机安全加固
# 跳板机的 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 最被低估的功能之一,可以安全地穿透防火墙、暴露内网服务。
本地转发(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:本地转发
远程转发(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 # 或 clientspecified动态转发 — 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 服务
实战组合:通过跳板机访问内网 Web 控制台
# 通过跳板机将本地 8443 映射到内网 K8s Dashboard
ssh -J bastion -L 8443:10.0.3.50:6443 user@internal-host
# 浏览器打开 https://localhost:8443SSH 端口转发进阶
多级跳板隧道
场景:需要经过两层跳板机才能访问目标服务器。
# 方法一: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 bastion2隧道保活
长时间运行的隧道容易因网络波动断开,需要保活机制:
# 方法一: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@bastionautossh — 自动重连的隧道
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-mysqlSSH 批量管理
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"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"结合 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" --becomeGUI 客户端接入
Termius、Tabby 等客户端的“添加密钥”流程原理一致:
- 生成密钥:在客户端里
Keys → Generate Key - 导入私钥:把本地私钥整个文件内容粘贴进去(只贴公钥不够)
- 绑定到主机:在主机配置里选这个 key
私钥留在客户端本地,公钥放在服务器的 ~/.ssh/authorized_keys。不同客户端 UI 略有差异,原则相同。
SSH 日志审计
日志位置
| 发行版 | 日志路径 |
|---|---|
| Ubuntu/Debian (systemd) | journalctl -u ssh 或 /var/log/auth.log |
| CentOS/RHEL 7+ | /var/log/secure |
| CentOS/RHEL 8+ (journald) | journalctl -u sshd |
# 查看最近的 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.log增强审计配置
# /etc/ssh/sshd_config
LogLevel VERBOSEVERBOSE 级别会记录:
- 每次认证尝试使用的密钥指纹
- 密钥认证成功/失败的原因
- 会话的详细信息
登录告警脚本
#!/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.sh关键文件与权限一览
| 文件 | 作用 | 权限 |
|---|---|---|
本地 ~/.ssh/id_ed25519 |
私钥 | 600 |
本地 ~/.ssh/id_ed25519.pub |
公钥 | 644 |
本地 ~/.ssh/config |
客户端配置 | 600 |
服务器 /etc/ssh/sshd_config |
服务端配置 | 644 |
服务器 ~/.ssh/authorized_keys |
已授权公钥列表 | 600 |
服务器 /var/log/auth.log |
SSH 登录日志 | - |
服务器 /etc/fail2ban/jail.local |
fail2ban 配置 | - |
常见问题排查
连接超时
# 详细模式查看卡在哪一步
ssh -vvv user@host常见原因及解决:
- 防火墙阻断:
telnet host 22或nc -zv host 22测试端口 - DNS 解析慢:在
/etc/ssh/sshd_config设置UseDNS no - GSSAPI 认证慢:在客户端配置
GSSAPIAuthentication no - 网络 MTU 问题:
ping -M do -s 1400 host测试
权限问题
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 必须属于当前用户,不能是 root密钥不匹配
# 查看服务器端接受的日志
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.pubAgent 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 或 authorized_keys 权限不对 |
chmod 700 ~/.ssh; chmod 600 authorized_keys |
| 改端口后连不上 | 防火墙没放新端口 | 控制台或 VNC 进入后 ufw allow 新端口 |
| sshd restart 后服务起不来 | 配置语法错误 | 重启前用 sshd -t 检查 |
| 改配置没生效 | 没重启 sshd | systemctl restart sshd |
| 改端口没生效(仍 22) | 云厂商平台代理 SSH | 用 ss -lntp 确认监听者 |
| fail2ban 把自己封了 | 没加 ignoreip | 控制台进入后解封自己的 IP |
| 部分轻量服务器没法改端口 | 平台层硬编码 22 | 去厂商控制台调整 |
SSH 最佳实践清单
密钥管理
- 使用 Ed25519 密钥(RSA 至少 4096-bit)
- 每个密钥设置 passphrase
- 不同环境使用不同密钥
- 定期轮换密钥(建议 90 天)
- 使用 ssh-agent 管理密钥,设置自动过期时间
- 离职人员密钥立即清除
安全加固
- 修改默认端口
- 禁止 root 直接登录
- 禁止密码认证,只允许密钥
- 限制可登录的用户/组
- 限制来源 IP(防火墙 + sshd 配置双重限制)
- 设置 MaxAuthTries(≤5)和 LoginGraceTime(≤60)
- 使用强加密算法(Ciphers / MACs / KexAlgorithms)
- 安装 fail2ban 自动封禁暴力破解
- 高危环境可叠加 2FA(google-authenticator-libpam 做一次性验证码)
- 保持 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% 的生产运维场景。关键不在于记住所有命令,而在于理解背后的原理、知道遇到问题时去哪里查、怎么查。
跳转、主机身份与会话验证
ProxyJump 让本机客户端经跳板建立目标连接。目标主机的身份仍由 SSH 主机密钥验证;遇到 host key 变化先通过可信渠道核对变更原因,避免把删除 known_hosts 当作固定修复。
修改配置先用 sshd -t 检查,再保留原会话创建新连接,分别验证普通登录、sudo 和跳板访问。停用旧密钥时同时核查所有账户与自动化任务,撤销流程要能定位密钥所属人和用途。