做运维和开发的,谁没被 SSH 口令折磨过。每天要登录十几台服务器,输密码输到怀疑人生,碰上弱口令检测被锁定、密码过期、批量巡检要一台台输入账号密码,效率直接打骨折。其实解决这个问题很简单,就是SSH 免密登录,也就是基于密钥的 Passwordless SSH Login。配置一次,后续所有会话、脚本、自动化任务都能直接通,再也不用在命令行里摸密码了。
这篇内容适合所有被密码登录困扰的人——刚入门的运维、经常连远程服务器的开发、还有做CI/CD需要自动化部署的同学。我会从原理讲到实操,再把踩过的坑、权限坑、排查步骤全部写清楚,保证看完能自己动手配好,并且不会留下安全隐患。
1. SSH免密登录的价值与原理
1.1 为什么你需要一套免密登录方案
先不说自动化,就谈日常操作。你可能有几十台服务器,有的一周才登一次,密码早就忘了;有的服务器按照安全合规要求强制90天改一次密码,改了之后你要更新所有文档、脚本、本地 known_hosts 以外的记忆;更麻烦的是有些内网环境不允许密码登录,只开放密钥认证。这些场景下,免密登录不是锦上添花,而是刚需。
把密码登录换成密钥认证之后,每次 SSH 登录就是一次“签名验证”,本质上不再传输密码,也就不存在密码被嗅探的风险。对于脚本和工具链来说,免密登录意味着scp传输文件不再中断、ansible批量执行不会卡在密码输入上、Git 拉取私有仓库也能直接通过 SSH 完成。可以说,配置好免密登录,是通往高效运维的第一级台阶。
除了效率,安全性也提升了。密码认证最大的问题是容易被暴力破解,只要你的 IP 暴露在公网,就会有bot脚本不断尝试弱口令;而密钥认证使用2048位以上的 RSA 或 Ed25519 非对称密钥,暴力破解的代价指数级上升。很多安全基线里直接就把PasswordAuthentication no作为强制要求,所以这一步迟早要完成。
1.2 免密登录背后的原理:公钥认证机制
要配置好免密登录,得先搞清楚它是怎么工作的。SSH 的密钥认证是基于非对称加密:每个连接参与方有一对密钥,一个公钥、一个私钥。公钥可以公开放在服务器上,私钥必须严格保密,只存在于你本地。
认证过程大致是这样的:
- 客户端发起 SSH 连接,向服务器表明自己持有某个私钥。
- 服务器配置了对应的公钥,于是发送一个用公钥加密的质询(challenge)。
- 客户端收到质询后,用本地私钥解密并返回响应。
- 服务器验证响应正确,确认客户端确实持有私钥,允许登录。
整个过程不需要在网络上传输私钥或密码,所以中间人即使截获了流量,也无法反推出私钥内容。这就好比你家门口放了一把挂锁(公钥),只有你的钥匙(私钥)能打开;别人即便拿了挂锁的照片也没用,因为钥匙不匹配。
理解这个原理后,很多配置细节就顺理成章了:服务器只需要保存公钥,私钥永远不要分发;密钥文件权限必须严格,因为如果私钥被其他用户读取,相当于钥匙被复制了一份,任何拥有私钥的人都能登录你的服务器。
2. 环境准备与密钥生成
2.1 需要准备的工具和条件
在开始配置之前,先确认你的环境满足基本条件。你需要:
- 一台作为客户端的本地机器(Windows / Linux / macOS 都可以,现代系统基本自带
ssh命令) - 一台需要配置免密登录的远程服务器,支持 SSH 协议
- 远程服务器的登录凭据(账号密码或已信任的密钥),用于第一次把公钥放上去
- 确保本地和远程之间有网络连通性,端口通常为 22,内网环境可能有自定义端口
如果你用的是 Windows,强烈建议不要用老旧的命令行里自带的ssh.exe以外的工具,就用 OpenSSH 即可。Windows 10 1809 以上系统基本都自带了ssh、ssh-keygen、ssh-copy-id(这个不一定有,可以用手动方式替代)。Linux 和 macOS 自带全套工具,基本不需要额外安装。
如果远程服务器是全新环境,你还需要确认服务端 SSH 服务已经启动。Linux 上可以用systemctl status sshd或service ssh status查看。对于云服务器,一般默认开启了密码登录,这样第一次部署公钥不会有障碍。
2.2 生成密钥对:ssh-keygen 实操
生成密钥对的命令非常简单,在本地终端执行:
ssh-keygen -t ed25519 -C "your_email_or_comment"如果你需要兼容非常老旧的系统和工具链,可以改用 RSA 类型,比如:
ssh-keygen -t rsa -b 4096 -C "your_email_or_comment"执行之后,系统会问你保存位置,默认是~/.ssh/id_ed25519(或~/.ssh/id_rsa),直接回车使用默认路径即可。如果你有多套密钥要区分,也可以手动指定路径,比如~/.ssh/id_rsa_work、~/.ssh/id_rsa_home。
接下来会提示设置 passphrase,也就是私钥密码。这里要展开说:免密登录不等于私钥不设密码。你完全可以给私钥设置一个 passphrase,这样即使私钥文件泄露,别人也无法直接使用它。每次使用前输入一次 passphrase,可以用ssh-agent帮你记住,实现“笔记本解锁后全自动登录”。我自己的习惯是:
- 个人电脑:设置 passphrase,配合系统钥匙串或 ssh-agent,安全性更高
- 无人工介入的自动化服务器:可以设空 passphrase,但必须严格控制私钥文件的访问权限并只放在受信任的机器上
生成完成后,你会看到两个文件:私钥(如id_ed25519)和公钥(如id_ed25519.pub)。私钥权限默认是 600,公钥是 644,先不要动它,后面排查权限问题时会讲到。
生成结束后,建议立即查看公钥内容,确保它完整无误:
cat ~/.ssh/id_ed25519.pub输出应该是类似ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... your_email的格式。这个内容就是你接下来要放到服务器上的字符串。
2.3 密钥类型选型:RSA、Ed25519 怎么选
我强烈推荐优先使用Ed25519,如果你不强制兼容 10 年前的老旧 OpenSSH 版本的话。原因是:
- 密钥更短(公钥约 68 字节),签名速度快,认证过程更快
- 安全性更高,现代密码学上比同类长度的 RSA 更抗量子计算(当然抗量子还早,但趋势如此)
- OpenSSH 6.5+ 已支持,目前主流发行版默认版本的 OpenSSH 都支持,除非你在维护一个非常老的 CentOS 5 之类
RSA 的优势是兼容性极好,几乎所有 SSH 实现都支持,包括各种老设备、嵌入式系统、网络设备(比如 Cisco 交换机的 SSH)。如果你需要连接这些设备,用 2048 或 4096 位 RSA 更保险。如果你在两个现代 Linux 系统之间配置,就无脑选 Ed25519。
另外还有 ECDSA 和 DSA。ECDSA 依赖曲线参数,历史上出过一些实现问题;DSA 密钥长度有限制(只能到 1024 位),已经不被安全基线接受。所以这两个我都不建议,除非你明确知道目标系统只支持它们。
3. 启用无密码登录:密钥部署与配置
3.1 将公钥复制到远程主机
生成好密钥后,关键一步是把公钥放到服务器的~/.ssh/authorized_keys文件中。最方便的命令是ssh-copy-id,很多系统自带:
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@remote_host这条命令会自动登录远程主机(需要你输入一次密码),然后把公钥追加到远程用户的~/.ssh/authorized_keys,并创建必要的目录和权限。执行过程中提示的密码就是远程账号当前的密码。
如果你的环境没有ssh-copy-id(比如默认 Windows),也可以用一条管道命令手动完成:
cat ~/.ssh/id_ed25519.pub | ssh user@remote_host "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"注意这里的顺序:先创建目录,再追加内容,最后设置文件权限。目录权限必须是 700,文件权限必须是 600,否则 SSH 服务端会因为“权限过于开放”而拒绝使用该密钥。这是最常踩的坑之一。
追加完成后,可以先验证一下公钥是否正确写入:
ssh user@remote_host "cat ~/.ssh/authorized_keys"比对一下输出和本地公钥文件内容,确认一致。
3.2 修改服务端 sshd 配置以增强安全性
密钥放上去之后,理论上已经可以免密登录了。但为了稳妥和安全,我们还需要调整服务端的 SSH 配置。配置文件一般是/etc/ssh/sshd_config,如果你在云服务器上有自定义路径,可以用sshd -T查看当前生效配置。
推荐的安全配置项包括:
PubkeyAuthentication yes PasswordAuthentication no PermitRootLogin prohibit-password ChallengeResponseAuthentication no UsePAM no关键解释:
PubkeyAuthentication yes:明确允许公钥认证,默认就是 yes,但显式写出来更清晰。PasswordAuthentication no:关闭密码认证。这一步必须在确认公钥登录成功后再做,否则你可能把自己锁在门外。PermitRootLogin prohibit-password:允许 root 通过公钥登录,但禁止 root 使用密码登录。如果你的运维体系需要直接 root 操作,这是安全性和便利性的折中方案。如果不需要 root 直接登录,建议直接设为no。ChallengeResponseAuthentication no和UsePAM no:关闭额外的交互式认证,防止某些系统配置下仍走密码流程。
修改完配置后,务必先测试配置语法:
sudo sshd -t然后再重启 SSH 服务:
sudo systemctl restart sshd # 或者 sudo service ssh restart在断开当前连接之前,强烈建议再开一个新的终端会话测试免密登录,确认没有问题后再关闭旧的连接。否则配置一错,你的会话一断,就真的连不上了。
3.3 多主机、批量部署与自动化场景
如果你只有一两台服务器,手动复制公钥还行。但如果你有几十台机器,还一台台输密码,那效率太低了。有几个思路可以提速:
- 使用
ssh-copy-id加循环脚本。假设你有一个hosts.txt,每行一个 IP 或主机名,可以这样:
while read host; do ssh-copy-id -i ~/.ssh/id_ed25519.pub user@"$host" done < hosts.txt这样每台机器仍然要输入一次密码,但省去了手工输入用户和路径的麻烦。配合sshpass可以在脚本中传密码,但我不建议把明文密码写进脚本,除非是临时一次性环境。
- 结合 Ansible 分发公钥。Ansible 有现成的
authorized_key模块,可以在大批量服务器上统一添加公钥:
- name: Add SSH public key authorized_key: user: your_user state: present key: "{{ lookup('file', '~/.ssh/id_ed25519.pub') }}"前提是你第一次还需要用密码或已有密钥连上服务器,但后续配置可以一拉到底,非常适合中大型集群。
- 定时同步公钥到堡垒机或云平台。现在很多云服务器提供商提供了“密钥对”功能,你可以在控制台上传公钥,创建实例时直接注入。这种方式能避免手工操作,强烈推荐。但自建机房或内网环境,还是老老实实执行前面的步骤。
自动化场景里还要注意,免密登录不是在客户端配置完就完事了,服务端的账号、目录、权限、SELinux 上下文这些细节都会影响最终效果。我遇到过 SELinux 开启的情况下,authorized_keys 文件的上下文不对,导致密钥认证失败,后面排查部分会展开。
4. 常见问题排查与实战经验
4.1 常见连接失败问题速查表
配置免密登录很容易,一次成功也很常见,但一旦失败,排查起来有点费劲。我把最常见的问题整理成了一张速查表,你可以按图索骥:
| 现象 | 可能原因 | 解决方向 |
|---|---|---|
| 连接时仍提示要求密码 | 公钥未写入 authorized_keys | 检查文件内容,重新追加 |
提示Permission denied (publickey) | 服务端拒绝公钥认证 | 打开-v调试,查看具体拒绝原因 |
提示Bad owner or permissions | 本地或远程密钥文件权限过大 | 调整目录为700,文件为600 |
提示No supported authentication methods | 服务端可能关闭了公钥认证 | 检查sshd_config的PubkeyAuthentication |
提示Connection closed by remote host | 可能是PasswordAuthentication no后没有可用认证方式 | 检查其他认证是否开启,或恢复密码认证临时修复 |
SSH 登录成功,但sudo或免密到其他主机失败 | 代理转发未开启 | 检查ForwardAgent yes,或使用ssh-add |
| 自动化脚本执行时找不到私钥 | 私钥路径或用户名错误 | 使用-i指定密钥,或使用~/.ssh/config配置别名 |
这个表只是“救援地图”,下面几个具体问题我们再展开细讲。
4.2 权限问题:bad owner or permissions
这是最经典的坑,尤其是 Windows 用户配置好之后看到Bad owner or permissions on C:\\Users\\xxx/.ssh/config。这个报错不是针对 authorized_keys,而是本地 SSH 客户端检查到了不安全的权限设置。
在 Linux/macOS 上,~/.ssh目录权限不能超过 700,私钥文件不能超过 600,公钥文件不能超过 644。如果你用chmod 777之类修改过,SSH 会拒绝使用。修复命令:
chmod 700 ~/.ssh chmod 600 ~/.ssh/id_ed25519 chmod 644 ~/.ssh/id_ed25519.pub在 Windows 上更麻烦一点,OpenSSH for Windows 会检查 ACL(访问控制列表),如果继承自父目录的安全设置有问题,同样报错。简单粗暴的办法是右键属性→安全→编辑,确认只有当前用户拥有完全控制权,或者用 PowerShell 重置权限:
icacls "C:\Users\你的用户名\.ssh\id_ed25519" /inheritance:r /grant:r "你的用户名:F"对.ssh目录和config文件也要做类似操作。有一种更省事的办法:干脆不用C:\Users\xxx\.ssh下的配置文件,直接用命令行指定,或者用 WSL 配置,权限模型更接近 Linux。
服务端 authorized_keys 权限问题也一样,如果远程目录或文件权限过大,服务端日志会提示Authentication refused: bad ownership or modes for directory。修复:
chmod 700 ~/.ssh chmod 600 ~/.ssh/authorized_keys另外注意,~目录本身也不能被其他用户写,如果 home 目录权限是 777,同样会导致认证拒绝。正常应该是 755 或 700。
4.3 免密登录失效的排查思路
免密登录配置好之后,可能某一天突然就失效了。我自己遇到过几次,总结下来主要场景有:
- 用户公钥被误删。有人清理目录的时候把
authorized_keys删了,或者用 git 仓库管理 dotfiles 时覆盖了文件。 - 服务器重启后 SELinux 上下文变了。在 CentOS/RHEL 上,如果你把 authorized_keys 从别处拷贝过来,它的 SELinux 标签可能不是
sshd_key_t,导致 sshd 没权限读取。检查一下:
ls -Z ~/.ssh/authorized_keys如果显示的不是unconfined_u:object_r:sshd_key_t:s0类似的标签,可以恢复:
restorecon -R -v ~/.ssh- 私钥换了但公钥没更新。本地重新生成密钥后,忘记更新服务器上的 authorized_keys,导致客户端拿着新的私钥,服务器还拿着旧公钥。
- 全局配置和用户配置冲突。
/etc/ssh/ssh_config或~/.ssh/config中指定了错误的IdentityFile,或者IdentitiesOnly yes限制了候选密钥。这时候用ssh -v看具体尝试了哪些私钥。
排查时先开详细日志:
ssh -v -i ~/.ssh/id_ed25519 user@remote_host输出里会显示尝试的认证方法、哪个密钥被拒绝,以及服务端的响应信息。如果还不够,去看服务端日志,Linux 上一般是:
sudo tail -f /var/log/auth.log # Debian/Ubuntu sudo tail -f /var/log/secure # CentOS/RHEL日志里会明确写类似Failed publickey for user ... from ...或Offering public key ...的信息,直接告诉你卡在哪一步。
还有一种情况:你在~/.ssh/config里配置了多个 Host,结果 Host 别名匹配到了错误的主机配置。这个时候要注意配置的顺序,SSH 会按“最长的通配符匹配”优先,而不是按文件顺序。
4.4 一些安全加固建议
免密登录配好之后,别急着把所有密码认证都关掉,先做一轮安全检查:
私钥必须加密保存。如果私钥没有 passphrase,一旦本地机器被攻破,私钥文件就等于免密钥匙。优先用
ssh-agent管理,每次开机输入一次 passphrase,后面自动使用。使用
ssh-agent和代理转发要谨慎。ForwardAgent会把本地 ssh-agent 里的私钥通过远程服务器转发,实现从服务器 A 免密登录服务器 B。这个功能方便,但有风险:如果 A 被入侵,攻击者可以借用你的 agent,在没有私钥文件的情况下登录 B。所以只在可信的跳板机上开启ForwardAgent,并且可以配合ssh-add -x锁定。区分不同用途的密钥。管理个人 Git 仓库、公司服务器、云主机,最好用不同的密钥对,并在
~/.ssh/config里分别指定。这样即使一个私钥泄露,影响范围也不会蔓延到所有机器。定期轮换密钥。建议每半年到一年更新一次,或者当员工离职、审计要求时立即轮换。轮换时删除旧公钥,部署新公钥,然后禁掉旧私钥登录。
把
PasswordAuthentication设置在修改前先小范围验证。在生产环境中,先选一台测试机完成全流程,确认没问题后再批量修改配置。避免一次性全机房改造,结果某台机器配置错误导致无法登录。
我个人的习惯是:把公钥部署和 sshd 配置的变更写成一个可回滚的脚本,比如把旧的sshd_config备份为.bak,一旦测试不通过立刻恢复。另外,永远开一个不受影响的 root 会话窗口,直到确认新配置在另一条连接上正常。
最后再分享一个小技巧:如果你有几台机器经常来回登录,可以在本地~/.ssh/config里给每台主机起一个别名,配置好用户和密钥,以后直接ssh web01就行,再也不用记 IP 和用户名了。这个文件的写法很简单:
Host web01 HostName 192.168.1.10 User deploy IdentityFile ~/.ssh/id_ed25519 ForwardAgent no配置好之后,自己日常使用的体验会上升一个档次,这也是我从被密码折磨到彻底解脱的关键一步。希望这份免密登录配置经验能帮你少走弯路,一次配好,长期享福。