1. 为什么放弃密码登录,转向密钥认证
如果你手里管着几台甚至几十台 Linux 服务器,还在用ssh user@host然后手动敲密码的方式登录,那大概率经历过下面这些场景:密码太长记不住,只好所有机器用同一个;团队里有人离职,第一反应是挨个改密码;半夜收到告警说某台机器有大量失败登录记录,心里一紧。这些问题归根结底都指向同一个原因——密码这种认证方式,天生就不适合用在自动化、规模化的远程管理场景里。
SSH 密钥登录解决的正是这个核心痛点。它的原理不复杂:你生成一对数学上关联的密钥,一把叫私钥,自己保管,绝不外传;另一把叫公钥,放到目标服务器的指定位置。登录时,服务器用公钥出一道“题”,只有持有对应私钥的客户端才能解出来,解对了就放行。整个过程私钥从不在网络上传输,也不存在被暴力猜解的可能。相比密码,它的优势是压倒性的:抗暴力破解(2048 位以上的密钥,穷举在算力上不可行)、可自动化(脚本、CI/CD 流程无需人工干预)、易撤销(删掉公钥即可收回权限,不影响其他人)。
这篇文章面向的是刚接触 Linux 服务器运维的开发者、需要管理多台主机的后端工程师,以及任何想让自己远程登录更安全、更省事的人。我会从密钥生成一路讲到服务端配置、权限设置、常见报错排查,把每一步背后的“为什么”都讲清楚。你跟着走一遍,基本就能在自己的 Ubuntu 机器上把密钥登录跑通,并且知道出了问题该往哪个方向查。
2. 动手前的整体思路与方案选型
2.1 密钥登录的完整链路拆解
很多人照着教程敲命令,成功了也不知道为什么成功,失败了更不知道怎么查。所以动手之前,先把整条链路在脑子里过一遍。一次密钥登录,实际上经历了这么几个阶段:
- 客户端生成密钥对:在本地机器上执行
ssh-keygen,得到私钥(如id_ed25519)和公钥(如id_ed25519.pub)。 - 公钥上传到服务器:把公钥内容追加到服务器上目标用户的
~/.ssh/authorized_keys文件里。 - 客户端发起连接:
ssh命令带上私钥(或使用默认路径),向服务器请求认证。 - 服务器校验:服务器读取
authorized_keys,用其中的公钥验证客户端是否持有匹配的私钥。 - 权限检查:服务器会检查
.ssh目录和authorized_keys文件的权限,权限不对直接拒绝,这是新手最容易踩的坑。 - 服务端配置放行:
sshd_config里必须允许公钥认证(PubkeyAuthentication yes),否则前面全白搭。
理解这条链路的意义在于:任何一步出问题,登录都会失败,而报错信息往往只给一个笼统的“Permission denied”。知道链路,你就能逐段排查,而不是盲目重试。
2.2 密钥算法怎么选:RSA、ECDSA 还是 Ed25519
ssh-keygen默认可能生成 RSA 密钥,但现在我更推荐Ed25519。原因有三点。第一,安全性:Ed25519 基于椭圆曲线,同等安全强度下密钥短得多,且没有 RSA 那些历史遗留的实现陷阱。第二,性能:签名和验证速度更快,连接建立更迅速。第三,密钥长度:Ed25519 公钥只有短短一行,复制粘贴到authorized_keys里不容易出错,而 RSA 4096 的公钥是一大坨。
如果你有兼容性顾虑——比如要连一些非常老的设备——那 RSA 仍然是保底选择,但请至少用 4096 位。ECDSA 介于两者之间,兼容性比 Ed25519 好一些,但不如 RSA 通用。我的建议很直接:新部署一律用 Ed25519,遇到连不上的老古董再退回 RSA 4096。
| 算法 | 推荐密钥长度 | 兼容性 | 相对优势 |
|---|---|---|---|
| Ed25519 | 固定 256 位 | 较新系统良好 | 短、快、安全 |
| RSA | 4096 位 | 极好 | 通用性最强 |
| ECDSA | 521 位 | 中等 | 折中方案 |
2.3 是覆盖默认密钥还是新建专用密钥
ssh-keygen不加参数会生成~/.ssh/id_ed25519,这是默认密钥,ssh连接时会自动尝试。方便,但有个隐患:如果你用同一把默认密钥登录所有服务器,一旦某台机器被攻破、私钥泄露,所有机器都危险。更稳妥的做法是按用途或按环境生成专用密钥,比如~/.ssh/id_ed25519_work、~/.ssh/id_ed25519_personal,然后在~/.ssh/config里为不同主机指定不同密钥。这样即使一把泄露,影响范围也可控。对于个人练手,用默认密钥没问题;但只要涉及生产环境,我强烈建议走专用密钥这条路。
3. 客户端密钥生成与上传实操
3.1 生成密钥对的具体命令与参数解读
在本地机器(你用来发起连接的那台)上打开终端,执行:
ssh-keygen -t ed25519 -C "your_email@example.com"逐个参数说清楚。-t ed25519指定算法类型,这是核心。-C后面跟的是注释,通常写邮箱或用途标识,它会被附加在公钥末尾,方便你在authorized_keys里一眼认出这把钥匙是谁的、干什么用的——机器多了以后这个注释非常有用。
执行后会有三段交互:
- 保存路径:默认
~/.ssh/id_ed25519,直接回车即可;要专用密钥就输入完整路径,比如/home/you/.ssh/id_ed25519_work。 - 设置密码短语(passphrase):这里很多人图省事直接回车留空。留空意味着私钥文件一旦被人拿到,就能直接用,没有任何二次保护。我的建议是设一个,哪怕简单点。设了之后,可以用
ssh-agent缓存,日常使用并不会每次都让你输。 - 确认密码短语:再输一遍。
生成完成后,~/.ssh/下会多出两个文件:id_ed25519(私钥,权限应为 600)和id_ed25519.pub(公钥,权限 644)。私钥文件绝对不能改权限、绝对不能外传、绝对不能提交到代码仓库,这是铁律。
3.2 用 ssh-copy-id 一键上传公钥
公钥生成后,要放到服务器上。最省事的方式是ssh-copy-id:
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@server_ip这条命令会自动帮你做几件事:通过密码登录一次服务器,创建~/.ssh目录(如果不存在),把公钥内容追加到authorized_keys,并设置好正确的权限。-i指定要上传的公钥文件,不指定的话默认找~/.ssh/id_ed25519.pub。
注意:
ssh-copy-id需要你先能用密码登录一次服务器。如果你已经禁用了密码登录,那就得用别的方式传公钥,比如通过已有的密钥登录后手动追加,或者用带外的方式(如控制台)操作。
3.3 手动上传公钥的完整步骤
有些环境没有ssh-copy-id(比如某些精简系统),或者你想完全掌控过程,那就手动来。先在本地查看公钥内容:
cat ~/.ssh/id_ed25519.pub复制输出的那一整行。然后登录服务器,执行:
mkdir -p ~/.ssh chmod 700 ~/.ssh echo "粘贴刚才复制的公钥内容" >> ~/.ssh/authorized_keys chmod 600 ~/.ssh/authorized_keys这四步的顺序和权限都不能错。mkdir -p保证目录存在且不报错;chmod 700让只有属主能进入该目录;>>是追加而不是覆盖,避免把已有的公钥冲掉;chmod 600让只有属主能读写。权限设置是密钥登录里最容易被忽略、也最容易导致失败的一环,后面排查章节会专门讲。
3.4 验证密钥登录是否生效
上传完先别急着禁用密码登录,先验证密钥能不能用。新开一个终端窗口(保留当前会话,万一失败还有退路),执行:
ssh -i ~/.ssh/id_ed25519 user@server_ip如果没让你输密码就直接进去了,说明密钥登录成功。如果还让你输密码,说明密钥没生效,服务器退回到了密码认证。这时候别慌,先看下一节的排查思路。验证通过后,再考虑禁用密码登录。
4. 服务端 sshd 配置与安全加固
4.1 关键配置项逐条说明
服务端的配置文件是/etc/ssh/sshd_config。修改前务必备份:
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak然后编辑,找到并确认以下几项。注意,很多配置项在文件里是被注释掉的,注释掉意味着使用默认值,你需要取消注释并改成想要的值:
PubkeyAuthentication yes AuthorizedKeysFile .ssh/authorized_keys PasswordAuthentication no PermitRootLogin prohibit-password ChallengeResponseAuthentication no UsePAM yes逐条解释。PubkeyAuthentication yes是开启公钥认证的总开关,必须为 yes。AuthorizedKeysFile指定公钥文件的位置,默认就是.ssh/authorized_keys,一般不用改。PasswordAuthentication no是禁用密码登录,这是安全加固的关键一步,但一定要在确认密钥登录可用之后再改。PermitRootLogin prohibit-password表示 root 用户只能用密钥登录、不能用密码,比直接yes安全得多。ChallengeResponseAuthentication no关掉键盘交互认证,堵住另一条可能被利用的密码通道。
4.2 修改配置后的重载与验证
改完配置不要直接重启服务,先用sshd -t检查语法:
sudo sshd -t没有输出就说明语法没问题。然后重载配置:
sudo systemctl reload sshd用reload而不是restart,因为 reload 不会断开现有连接,万一新配置有问题,你当前这个会话还在,可以改回去。这是运维的基本素养:永远给自己留一条退路。重载后,再新开一个终端测试密钥登录,确认无误后,才算真正完成。
4.3 禁用密码登录的正确时机
我见过太多人一上来就把PasswordAuthentication改成 no,结果密钥没配好,直接把自己锁在门外,只能去云控制台走 VNC 救援。正确的顺序是:
- 生成密钥、上传公钥。
- 验证密钥登录成功(新开终端测试)。
- 确认
PubkeyAuthentication yes已生效。 - 再改
PasswordAuthentication no。 - reload 后再次用新终端验证。
提示:在云服务器上操作时,改配置期间始终保持一个已登录的 SSH 会话不要关闭。这个会话就是你的“安全绳”,新配置出问题也能通过它改回来。
4.4 进一步收紧:限制用户与来源
密钥登录跑通后,如果还想更严,可以加几道限制。AllowUsers可以白名单指定哪些用户能登录:
AllowUsers deploy admin这样即使系统里有其他账户,也无法通过 SSH 登录。还可以用Match块针对特定用户或组做更细的策略,比如只允许某个用户从特定网段登录。这些属于进阶加固,按需使用,但思路是一致的:默认拒绝,按需放行。
5. 权限、属主与常见报错排查
5.1 权限问题是密钥登录失败的头号原因
SSH 对权限的检查近乎偏执,这是有道理的:如果authorized_keys或.ssh目录对其他人可写,那任何人都能往里塞公钥,密钥认证就形同虚设。所以服务器会严格校验,权限不对直接拒绝,而且报错信息往往很含糊。正确的权限组合是:
| 路径 | 权限 | 属主 |
|---|---|---|
~(家目录) | 755 或 700 | 用户本人 |
~/.ssh | 700 | 用户本人 |
~/.ssh/authorized_keys | 600 | 用户本人 |
| 私钥(客户端) | 600 | 用户本人 |
家目录权限也常被忽略。如果家目录是 777 或属主不对,SSH 同样会拒绝。用ls -ld ~ ~/.ssh ~/.ssh/authorized_keys一眼就能看出问题。
5.2 用 -v 参数定位失败环节
密钥登录失败时,最有效的排查手段是加-v(verbose)参数:
ssh -v -i ~/.ssh/id_ed25519 user@server_ip它会打印整个握手过程。重点看这几行:Offering public key表示客户端把公钥发过去了;Server accepts key表示服务器认可这把钥匙;Authentication succeeded才是真正成功。如果卡在Offering public key之后没有Server accepts key,多半是服务器端authorized_keys里没有这把公钥,或者权限不对。如果连Offering都没有,可能是客户端没找到私钥,检查-i路径。想看更详细的服务端日志,可以在服务器上查/var/log/auth.log(Ubuntu 默认位置),里面会明确写出拒绝原因。
5.3 常见问题速查表
| 现象 | 可能原因 | 解决方向 |
|---|---|---|
| 仍提示输入密码 | 公钥未生效,回退到密码认证 | 检查 authorized_keys 内容与权限 |
| Permission denied (publickey) | 权限不对或公钥不匹配 | 核对权限、比对公钥指纹 |
| 连接被拒绝 | sshd 未运行或端口不对 | systemctl status sshd检查服务 |
| 改了配置后连不上 | 配置语法错误或禁用了唯一可用方式 | 用保留会话改回,sshd -t查语法 |
| 提示私钥权限过于开放 | 私钥权限不是 600 | chmod 600私钥文件 |
5.4 几个容易忽略的细节
第一,公钥要整行复制,包括开头的ssh-ed25519和结尾的注释,少一个字符都不行。第二,换行问题:手动追加时确保公钥独占一行,不要和已有内容挤在一起。第三,多把公钥:authorized_keys里可以放多行,每行一把公钥,方便多台客户端登录同一账户。第四,SELinux:Ubuntu 默认不开 SELinux,但如果你在别的发行版上操作,SELinux 的上下文也可能导致失败,需要用restorecon修复。这些细节平时不显眼,出问题时却能折腾你半天。
6. 多主机管理与密钥生命周期维护
6.1 用 ssh config 简化日常连接
密钥多了、主机多了以后,每次敲ssh -i ~/.ssh/id_ed25519_work user@192.168.1.100太累。在本地~/.ssh/config里配置别名:
Host work-server HostName 192.168.1.100 User deploy IdentityFile ~/.ssh/id_ed25519_work Port 22配好之后,直接ssh work-server就能连,密钥、用户、端口全自动匹配。这个文件支持通配符和Host *全局配置,机器一多,效率提升非常明显。我自己的习惯是每台常用主机都配一个别名,几个月不用的机器也能靠别名快速回忆起来是干什么的。
6.2 密钥轮换与撤销的正确做法
密钥不是配一次就一劳永逸。人员变动、设备更换、怀疑泄露,都需要处理。撤销很简单:登录服务器,编辑authorized_keys,删掉对应那一行即可,不影响其他密钥。轮换则是定期更换密钥对,尤其是长期使用的密钥。做法是生成新密钥、上传、验证、再删除旧公钥,全程不影响服务。建议给密钥注释里写上生成日期或用途,方便日后判断哪把该退休了。
6.3 ssh-agent 与密码短语的配合
前面建议给私钥设密码短语,但每次连接都输一遍确实烦。ssh-agent就是解决这个矛盾的:它把解密后的私钥缓存在内存里,一次输入,会话内多次使用。启动并添加:
eval "$(ssh-agent -s)" ssh-add ~/.ssh/id_ed25519之后同一终端会话里再连接就不需要重复输入了。这样既保留了密码短语的安全价值,又兼顾了使用便利。安全和便利从来不是非此即彼,关键是找到合适的平衡点,ssh-agent 就是密钥登录场景下的那个平衡点。
6.4 备份策略:私钥丢了怎么办
私钥一旦丢失且没有备份,就只能重新生成、重新上传,服务器本身不受影响,但如果你禁用了密码登录又没有其他可用密钥,那就麻烦了。所以私钥需要备份,但备份方式要讲究:加密后存放在离线介质或密码管理器里,绝不能明文丢在网盘或聊天记录里。我个人的做法是把私钥用强密码加密打包,放在一个只有自己知道的离线位置,同时确保至少有一台服务器保留着可用的备用登录方式,作为最后的保险。
7. 我在实际配置中踩过的坑
说几个真实踩过的坑,都是文档里不会写、但实际会遇到的。有一次帮朋友配服务器,密钥怎么都不生效,-v看到Server accepts key都出来了,最后还是失败,查了半天发现是家目录权限被之前某次操作改成了 777,SSH 直接拒绝。改回 755 立刻就好。还有一次是复制公钥时终端自动换行,把一行公钥拆成了两行,authorized_keys里就成了两条无效记录,肉眼根本看不出来,用ssh-keygen -l -f比对指纹才发现对不上。
另一个高频坑是改完sshd_config直接restart,结果配置里有个拼写错误,服务起不来,当前会话也断了,只能去云控制台救援。从那以后我养成了两个习惯:改配置前备份,改完先sshd -t再reload,并且永远保留一个活动会话。这些习惯看起来啰嗦,但关键时刻能省下大量折腾时间。
最后一个体会是关于密码短语的。早期我图省事全部留空,后来意识到私钥文件一旦因为误操作泄露(比如不小心同步到了公共位置),留空的私钥等于把钥匙直接插在门上。现在我的做法是:所有私钥都设密码短语,配合 ssh-agent 使用,安全性和便利性都不牺牲。这个转变花不了多少时间,但安全等级完全不一样。