一、结论先给
SSH 免密登录(公钥登录)本质上就三步:本地生成密钥对 → 把公钥放进服务器的 ~/.ssh/authorized_keys → 服务端开启 PubkeyAuthentication。90% 的失败不是命令敲错,而是文件权限不对或服务端配置没开。
| 环节 | 命令 | 常见坑 |
|---|---|---|
| 生成密钥 | ssh-keygen -t ed25519 -C "备注" |
老设备不支持 ed25519,改用 rsa -b 4096 |
| 传公钥 | ssh-copy-id -i ~/.ssh/xx.pub user@host |
没有 ssh-copy-id 时手动追加,别用 > 覆盖 |
| 修权限 | chmod 700 ~/.ssh; chmod 600 authorized_keys |
家目录权限过宽(777)也会被拒 |
| 开服务端 | PubkeyAuthentication yes |
改完必须 sshd -t 验语法再重载 |
| 验证 | ssh -v user@host |
-v 是唯一的真相来源 |
👉 配置前后想确认目标机 22 端口通不通,用 SSH 端口连通检测(在线),不用登机器就能测。
二、本地:生成一对密钥
2.1 算法怎么选
# 首选:ed25519,密钥短、快、安全性高(OpenSSH 6.5+ 支持,2014 年以后基本都有)
ssh-keygen -t ed25519 -C "afei@macbook-2026"
# 兼容老设备(CentOS 6 / 老交换机 / 部分堡垒机)用 RSA 4096
ssh-keygen -t rsa -b 4096 -C "afei@macbook-rsa"
# 指定文件名,避免覆盖默认 id_rsa
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_ops -C "ops"
交互会问两件事:
- Enter passphrase:给私钥再加一层口令。生产环境建议设,配合
ssh-agent用;自动化脚本(CI、定时任务)留空。 - 生成后得到两个文件:
id_ed25519(私钥,绝不外传)和id_ed25519.pub(公钥,放服务器上)。
判断该用哪个:
| 场景 | 推荐 |
|---|---|
| 服务器是 CentOS 7+/Ubuntu 16+ | ed25519 |
| 要连网络设备、老交换机、旧堡垒机 | RSA 4096 |
| 合规要求 FIPS | ecdsa 或 RSA |
2.2 私钥权限自检
OpenSSH 对私钥权限很敏感,太开放会直接拒绝使用:
chmod 600 ~/.ssh/id_ed25519
chmod 644 ~/.ssh/id_ed25519.pub
ls -l ~/.ssh/
看到 WARNING: UNPROTECTED PRIVATE KEY FILE! 就是这个问题,chmod 600 即可。
三、把公钥送到服务器
3.1 方式一:ssh-copy-id(推荐)
# 默认用 ~/.ssh/id_ed25519.pub
ssh-copy-id root@10.0.0.11
# 指定密钥和端口
ssh-copy-id -i ~/.ssh/id_ed25519_ops.pub -p 2222 ops@10.0.0.11
它做的事:登一次密码 → 在远端创建 .ssh 目录并设 700 → 把公钥追加到 authorized_keys 并设 600。追加语义很重要,多个人/多台机器的公钥能共存。
3.2 方式二:手动追加(没有 ssh-copy-id 时)
# macOS / Linux 本地执行,注意用的是 >> 追加
cat ~/.ssh/id_ed25519.pub | ssh root@10.0.0.11 "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
千万别用 >,那会清空别人已经配好的公钥,导致整台机器的免密集体失效。
3.3 方式三:云厂商控制台注入
阿里云/腾讯云/AWS 都支持创建实例时绑定密钥对,或者停机后"绑定密钥"。这种方式会覆盖 ~/.ssh/authorized_keys,且通常要求先把实例关机,适合开新机器,不适合给正在跑的机器补配。
四、服务端:sshd 配置与权限
4.1 四个关键参数
sudo vim /etc/ssh/sshd_config
PubkeyAuthentication yes # 开启公钥登录,默认一般是 yes
AuthorizedKeysFile .ssh/authorized_keys # 公钥存放位置
PermitRootLogin prohibit-password # 禁 root 密码登录,但允许 root 密钥登录
PasswordAuthentication no # 彻底关掉密码登录(确认密钥能登录之后再改)
改完先验证语法,再重载,这条顺序不能反:
sudo sshd -t # 有错会直接报行号;没错不出任何输出
sudo systemctl reload sshd # CentOS / Ubuntu 通用;Ubuntu 服务名也可能是 ssh
# 老系统:
sudo service sshd reload
用 reload 而不是 restart:reload 不会踢掉当前已建立的连接,配错时至少还能靠现有会话救回来。配 SSH 时永远保留一个已登录的会话窗口别关,这是运维的保命习惯。
4.2 权限矩阵(错一个就登录失败)
| 路径 | 正确权限 | 说明 |
|---|---|---|
/home/user |
755(不能 777) |
家目录被同组/其他用户可写,sshd 直接不信任 |
/home/user/.ssh |
700 |
目录必须仅本人可读写执行 |
authorized_keys |
600 |
也可以是 644,但 600 最稳 |
| 本地私钥 | 600 |
否则 UNPROTECTED PRIVATE KEY FILE |
chmod go-w ~
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
4.3 SELinux 环境(CentOS/RHEL)额外一步
手动拷贝文件到 .ssh 后,SELinux 上下文可能是错的,sshd 读不到:
restorecon -R -v ~/.ssh
# 临时排查可以用 setenforce 0 确认是不是 SELinux 的锅,验完记得 setenforce 1
五、多密钥与 SSH Config
管理多台机器、多个身份(公司/个人/客户)时,靠 ~/.ssh/config 而不是每次 -i:
Host ops-prod
HostName 10.0.0.11
User ops
Port 2222
IdentityFile ~/.ssh/id_ed25519_ops
IdentitiesOnly yes # 只试这一把钥匙
Host *
ServerAliveInterval 30 # 每 30 秒发心跳,防 NAT/防火墙掐断
ServerAliveCountMax 3
AddKeysToAgent yes
配完直接 ssh ops-prod。
IdentitiesOnly yes 是解决 "Too many authentication failures" 的关键——不加它,本地 5 把钥匙会被依次尝试,很多服务端默认 MaxAuthTries 6,轮到正确的那把之前就被踢了。
六、ssh-agent:私钥带口令又不想到处输
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519_ops # 输入一次 passphrase
ssh-add -l # 列出已加载的钥匙
# 跳板机场景:A → B → C,不想把私钥放 B 上
ssh -A user@B # 或配置里 ForwardAgent yes
-A(Agent Forwarding)的安全提醒:B 机器上的 root 能通过你的 agent socket 冒充你登录 C。只在可信机器上使用,不可信环境改用 ProxyJump:
ssh -J user@B user@C
# 或 config 里
Host C
ProxyJump user@B
ProxyJump 的流量从本地加密直通 C,B 只做转发,看不到明文也拿不到你的私钥。
七、8 类失败与定位方法
| 报错 / 现象 | 根因 | 处理 |
|---|---|---|
Permissions 0644 for 'id_rsa' are too open |
私钥权限 | chmod 600 |
Server refused our key |
服务端没开 Pubkey / 公钥没写进去 / 权限 | 查 sshd_config 与 authorized_keys 权限 |
Permission denied (publickey) |
密钥不匹配或 StrictModes 拒绝 | ssh -vvv 看服务端接受哪些方法 |
Too many authentication failures |
本地试了太多钥匙 | config 加 IdentitiesOnly yes |
REMOTE HOST IDENTIFICATION HAS CHANGED |
服务器重装/换 IP,known_hosts 旧指纹冲突 | ssh-keygen -R 10.0.0.11 删旧记录 |
| 能密码登录,密钥登录仍要密码 | PasswordAuthentication 关了但 PubkeyAuthentication 也没生效 |
sshd -T | grep -i pubkey 看实际生效值 |
| root 密钥登录被拒 | PermitRootLogin no |
改成 prohibit-password |
| 登录卡住十几秒才出提示 | UseDNS yes 反查 DNS / GSSAPI 认证 |
UseDNS no、GSSAPIAuthentication no |
排障万能命令:
ssh -vvv -i ~/.ssh/id_ed25519_ops ops@10.0.0.11 2>&1 | grep -iE "offering|authentications|denied|accepted|trying"
# 服务端实时看日志
sudo tail -f /var/log/secure # CentOS / RHEL
sudo tail -f /var/log/auth.log # Ubuntu / Debian
-vvv 输出里的三行是关键:Offering public key(本地有没有送)、Server accepts key(服务端认不认)、Authentication succeeded。卡在哪一行,问题就在哪一层。
👉 服务器日志里的报错太多看不过来,可以把日志段粘进 日志分析工具(浏览器内分析),直接标出错误点和异常 Top。日常命令记不全就查 Linux 命令速查。
八、加固:配通之后还要做的几件事
免密登录本身已经比密码安全得多,但还可以更严:
# /etc/ssh/sshd_config
Port 2222 # 改端口,噪声扫描能少九成
PermitEmptyPasswords no
MaxAuthTries 3
LoginGraceTime 30
AllowUsers ops deploy # 白名单,只有这几个用户能登
X11Forwarding no
ClientAliveInterval 300
ClientAliveCountMax 2
再配 fail2ban 拦暴力破解:
sudo yum install -y fail2ban # Ubuntu: apt install -y fail2ban
sudo systemctl enable --now fail2ban
sudo fail2ban-client status sshd
改端口的正确顺序:新端口开防火墙 → sshd -t → reload → 新开一个窗口用新端口连 → 连上了再关旧端口。顺序反了就再也进不去了。
九、几条纪律
- 改 sshd 配置前先留一个已登录会话,改完用
sshd -t验语法,用reload不用restart。 - 确认密钥能登录之后再关
PasswordAuthentication,不然就把自己锁在门外。 - 追加公钥一律用
>>,永远不要用>。 - 私钥 = 身份证,不进 Git、不进镜像、不传网盘;要传给别人让他把公钥给你。
- 自动化用的密钥单独一把、不带 passphrase,并在服务端用
command=前缀限制它只能执行固定命令。 - 每台机器、每个人用不同密钥,出事时能精确定位是哪一把泄露。
十、延伸阅读
👉 相关在线工具:SSH 连通检测 · Linux 命令速查 · 网络延迟测试,全部免登录、数据不上传服务器。