很多运维朋友第一次接触免密登录,都以为把公钥往服务器上一扔就完事了,结果配完一登录,迎面就是一行Permission denied (publickey,gssapi-keyexch,password)。我自己早年也在这个坑里蹲过不少时间,后来把原理和权限细节彻底捋清楚之后,才发现这事其实不复杂,只是网上教程大多只给了命令,没讲背后的认证逻辑和文件权限规矩。这篇就把Linux服务器SSH免密登录从原理到实战、从单机到批量、从配置到排错完整走一遍,适合刚接触Linux的运维新人,也适合配置了但总出问题的老手对照排查。
1. 为什么非做免密不可:密码登录在真实环境里的三个痛点
1.1 批量运维场景下的效率黑洞
先说说最直接的痛点:效率。我最开始维护三五台服务器的时候,用密码登录还不觉得有什么,ssh root@ip 输一次密码也就几秒钟。但后来服务器数量涨到几十台,每次发布代码、批量检查服务状态、同步配置,一台一台连过去输密码,那个酸爽谁试谁知道。尤其是遇到线上故障,需要快速登陆几十台机器看日志、重启服务的时候,卡在密码输入这一步上,每台浪费三五秒,加起来就是几分钟的黄金排障时间。
当时我算过一笔账:每天要登录的服务器在30台左右,每次登录平均输入密码加等待大约4秒,一天下来光是输密码的时间就接近两分钟。再加上密码输错重试的几率,一个月下来因为这一个小小的交互动作浪费的时间相当可观。免密登录配好之后,ssh user@host 直接进,连密码输入提示都不会出现,整个操作节奏完全不一样。
1.2 密码认证本身的安全隐患
很多人觉得密码登录挺安全的,其实在生产环境里,密码认证恰恰是最容易被攻击的入口之一。
服务器只要暴露在公网,/var/log/secure 或者 journalctl -u ssh 里几乎天天能看到一堆来自陌生IP的暴力破解尝试,内容基本都是"root@某IP 密码错误"这种刷屏。虽然强密码能扛住大部分破解,但总有密码复用、弱口令、内鬼泄露这类不可控因素。相比之下,密钥认证使用的是公钥加密、私钥解密的方式,私钥不出客户端机器,攻击者光有公钥不可能算出私钥,安全性天然高一个量级。
还有一点是审计维度。用密码登录时,运维操作者对"谁登录的"这个信息的认定是比较弱的,因为密码可能被多人知道;而密钥认证每一把私钥都是独立的,配合服务端日志,可以相对清晰地定位到具体是哪台机器、哪个用户在用哪把密钥登录。这对于后来做权限回收、离职交接、异常排查都太重要了。
1.3 哪些场景其实不应该用免密
有句老话叫"免密一时爽,配置火葬场"——所以先说清楚边界:免密登录适合服务器与服务器之间的自动化交互(脚本、定时任务、配置同步)、运维人员从跳板机或本机频繁登录生产环境。但如果这台机器是高危环境、多人共用的测试机,或者根本不具备私钥保管条件,那还是老老实实输密码吧。
我自己就踩过这么一次:帮朋友配置一台多人共用的开发服务器,配好免密之后,其中一个人的私钥文件从电脑里泄露了,结果其他人拿着私钥就能登录。后来整治这个问题花了不少功夫。所以免密登录一定有一个前提:私钥的安全等级决定了整条链路的安全等级,后面专门讲安全管理部分。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 免密登录的底层逻辑:SSH密钥认证到底是怎么认人的
2.1 一对密钥和三次握手
要配置好免密登录,先得搞清楚SSH密钥认证的基本原理。整个过程可以简化为三件事:你有一把私钥,服务器有一把公钥,两者配对成功,认证通过。
具体交互过程我没必要从RFC文档逐条抄,但可以这么理解:
- 客户端发起SSH连接,告诉服务端"我支持公钥认证";
- 服务端返回一个用公钥加密的随机挑战数(challenge);
- 客户端用私钥解开这个挑战数,回传给服务端;
- 服务端确认结果正确,放行登录。
这里有个网上的常见误解必须纠正:免密登录不是服务器存了你的私钥,服务器存的是公钥。公钥相当于一把"锁",谁手里有对应的"钥匙"(私钥)谁就能开门。所以服务器上authorized_keys文件里存的是id_rsa.pub或者id_ed25519.pub内容,千万不要把私钥id_rsa的文件内容放到服务器上。
2.2 为什么authorized_keys权限要求那么苛刻
配置免密登录时,大家都会照着一堆教程执行chmod 700 ~/.ssh、chmod 600 ~/.ssh/authorized_keys,但很少有人问为什么。这里面的原因是SSH服务端的严格检查机制:
- 家目录
~不能对"其他用户"可写:如果你家目录权限是777或者775,别人可以在你目录下放文件、改配置,SSH服务端出于安全考虑会直接拒绝信任这个环境; ~/.ssh目录不能"其他用户"可写:同理,防止别人往你目录里塞恶意公钥;authorized_keys文件不能"其他用户"可写:否则任何人都可以往这个文件里追加自己的公钥,等于给所有持有私钥的人发了通行证。
这个权限设计给600和700,不是为了吓唬人,是SSH防止信任链被破坏的底线。只要有一项权限不对,服务端日志里就会出现Authentication refused: bad ownership or modes这类报错。
2.3 客户端和服务端配置项的职责划分
理解了原理,再看配置项就清晰多了。免密登录涉及两个层面的配置文件:
- 服务端:
/etc/ssh/sshd_config,关键参数是PubkeyAuthentication yes和AuthorizedKeysFile .ssh/authorized_keys; - 客户端:
/etc/ssh/ssh_config或~/.ssh/config,关键参数是IdentityFile指定私钥路径、User指定登录用户。
一个很常见的坑就是:服务端sshd_config里被人为改成了PubkeyAuthentication no,或者注释掉了,结果客户端怎么配置都免密不了。后面排查章节会专门讲这个。
3. 完整配置步骤:从生成密钥到首次免密登录
下面进入正题。我从零开始走一遍标准配置流程,并且把每一步的意图和容易踩的细节标出来。
3.1 生成密钥时的参数选择和注意事项
在客户端机器(也就是你想从哪台机器发起免密登录,就在哪台机器上生成密钥对)上执行:
bash复制ssh-keygen -t ed25519 -C "yourname@client-machine" -f ~/.ssh/id_ed25519
这里我推荐使用ed25519算法而不是传统的rsa。ed25519密钥更短、生成速度快、安全强度高,而且现代Linux发行版的OpenSSH都默认支持。如果你需要兼容特别老的系统(比如CentOS 6上的OpenSSH 5.x),那再考虑rsa,命令类似:
bash复制ssh-keygen -t rsa -b 4096 -C "yourname@client-machine" -f ~/.ssh/id_rsa
-C参数是备注信息,建议写成"用户名@主机名"的形式,方便将来管理多台客户端的密钥。执行过程中会提示设置passphrase,这里先留空直接回车,后面讲安全管理时再谈如何给私钥加口令。
生成完检查一下:
bash复制ls -la ~/.ssh/
正常情况下应该看到一对文件:id_ed25519(私钥)和id_ed25519.pub(公钥)。私钥权限默认是600,这就对了,千万别改成644甚至777。
3.2 把公钥送到服务器上的几种方法
最推荐的方式是ssh-copy-id:
bash复制ssh-copy-id -i ~/.ssh/id_ed25519.pub user@server-ip
执行后输入一次服务器密码,工具会自动把公钥内容追加到服务器上对应用户的~/.ssh/authorized_keys文件里,并且设置好权限。这是目前最不会出错的方法。
有些系统没有安装ssh-copy-id,比如精简版CentOS或者一些容器镜像。这时候可以手动操作:
bash复制cat ~/.ssh/id_ed25519.pub | ssh user@server-ip "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
这条命令拆开看就是:在服务器上创建.ssh目录、给目录设好权限、把公钥追加到authorized_keys、给文件设好权限。一气呵成,不会因为中间缺了某一步权限设置而引发后面的Permission denied。
还有一种极少用但要知道的方法:把公钥内容直接复制粘贴到服务器的authorized_keys文件里。注意authorized_keys文件里每行一把公钥,公钥内容必须是一整行,不要因为复制操作把内容折行破坏了,否则这一把公钥就是废的,服务端解析不了。
3.3 权限修正和第一次登录验证
无论用哪种方式传公钥,传完之后我都建议到服务器上亲自确认一遍权限:
bash复制ls -ld ~
ls -ld ~/.ssh
ls -l ~/.ssh/authorized_keys
期望的输出是:家目录drwx------或者至少不能是other可写;.ssh目录是drwx------;authorized_keys是-rw-------。如果不是,立即修正:
bash复制chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
chmod go-w ~
然后回到客户端,测试免密登录:
bash复制ssh user@server-ip "echo hello"
如果配置成功,这条命令会直接输出hello,不会提示输密码。第一次连接可能会提示确认host key,输入yes回车即可,之后就不会再有这个提示。
走到这一步,单机免密就算配好了。但实际工程里很少只配一台,更多时候面对的是一整个服务器集群。
4. Permission denied实战排查:最容易翻车的五个环节
坦白说,免密登录配置过程中,顺利一次通过是少数,大多数人都会经历Permission denied。这个报错出现后,别慌,按下面的链路逐层排查,基本都能定位。
4.1 登录报错后的第一步:看服务端日志
很多人一看到Permission denied (publickey,gssapi-keyexch,password)就去改文件权限,其实最有效的第一步是去服务器端看SSH日志。日志文件的位置根据发行版不同而有差异:
- CentOS / RHEL / Rocky:
/var/log/secure - Ubuntu / Debian:
/var/log/auth.log - 使用systemd的机器可以直接:
journalctl -u sshd -f
在另一个终端窗口执行tail -f盯着日志,然后再次发起免密登录尝试,观察日志里新增的那行错误提示。不同错误提示直接指向不同原因,我整理成一个对照表:
| 日志关键词 | 问题指向 |
|---|---|
bad ownership or modes |
家目录、.ssh目录或authorized_keys权限不对 |
No such file or directory |
authorized_keys路径不存在或文件名拼错 |
Authentication refused |
可能是权限问题,也可能是服务端配置拒绝公钥认证 |
Connection closed by authenticating user |
客户端提供的私钥与服务端公钥不匹配 |
user user not allowed because account is locked |
系统账号被锁定,不是密钥问题 |
这一步能过滤掉绝大多数问题,比在客户端瞎试半天高效得多。
4.2 权限问题的层层拆解
如果日志显示bad ownership or modes,按前面讲的权限标准逐层检查。这里补充一个我实际踩过的隐蔽情况:家目录本身权限正常,.ssh权限正常,authorized_keys权限也正常,但SSH依然报权限错误。
最后排查了半天,发现问题是家目录的父目录权限不对。比如用户的主目录是/home/user,而/home目录本身被设置成了775并且属主不是root,某些严格配置的SSH也会拒认。这个属于少数情况,但排错时要知道有这个方向。
还要注意:~/.ssh里的其他文件也会被检查。比如你曾经往.ssh里放过id_rsa私钥的备份、config文件,只要权限过宽,同样可能触发拒绝认证。
4.3 SELinux和sshd_config的隐性坑
权限全对,公钥也在authorized_keys里,但就是连不上,这种情况在CentOS/RHEL系上要先查SELinux。
bash复制getenforce
如果输出是Enforcing,检查一下SSH相关的布尔值:
bash复制getsebool -a | grep ssh
重点看use_sshd_authorized_keys这类项。有时候因为迁移家目录、改过文件上下文,导致authorized_keys文件的SELinux标签不对,用restorecon -R -v ~/.ssh恢复一下上下文就解决了。
再一个容易忽略的是/etc/ssh/sshd_config里的配置。注意检查这几项:
bash复制PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
PasswordAuthentication yes # 排查期间先保持yes,确认免密成功后再关
改完配置要重启服务:
bash复制systemctl restart sshd
4.4 客户端私钥指定错误
最后一个高频坑不在服务端在客户端:你有多对密钥,ssh默认只找~/.ssh/id_rsa或~/.ssh/id_ed25519,如果你用的是别的文件名(比如~/.ssh/aliyun_key),那就得显式指定:
bash复制ssh -i ~/.ssh/aliyun_key user@server-ip
或者写进~/.ssh/config一劳永逸:
code复制Host aliyun-server
HostName 192.168.1.100
User root
IdentityFile ~/.ssh/aliyun_key
之后直接ssh aliyun-server就能免密登录。
5. 多台服务器批量免密:一条命令和它的变体
单台配好之后,如果手头有十几台、几十台服务器,逐台ssh-copy-id也是个体力活,而且每台都要输一次密码,体验并没有好太多。这里分享我实际用过的批量方案。
5.1 基于ssh-copy-id的循环分发
如果你有所有服务器的密码统一管理方式(比如临时密码表),可以写个简单的循环脚本:
bash复制for ip in $(cat servers.txt); do
sshpass -p 'your_password' ssh-copy-id -i ~/.ssh/id_ed25519.pub -o StrictHostKeyChecking=no user@$ip
done
servers.txt里每行一个IP。sshpass是一个非交互式输密码的小工具,CentOS上用yum install sshpass安装,Ubuntu上用apt install sshpass。-o StrictHostKeyChecking=no的作用是跳过首次连接的host key确认,这个参数只适合在可控内网环境使用,公网环境不要这样干,容易遭受中间人攻击。
5.2 免交互手工分发方案
如果不想装sshpass,可以用前面提到的手工追加公钥方法,配合循环:
bash复制for ip in $(cat servers.txt); do
cat ~/.ssh/id_ed25519.pub | ssh -o StrictHostKeyChecking=no user@$ip "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
done
这个方案的问题在于每台机器还是要输密码,不过比一条条执行命令稍微省事一点。真正彻底免交互的做法是用Ansible这类自动化工具,把目标机器的密码配置到ansible的hosts文件里,然后一条playbook批量推送公钥,这里就不展开说了。
5.3 批量配置后的统一验证
批量分发完之后,逐一验证有没有漏网之鱼也很关键。我是这么做的:
bash复制for ip in $(cat servers.txt); do
result=$(ssh -o BatchMode=yes -o ConnectTimeout=3 user@$ip "echo ok" 2>&1)
echo "$ip: $result"
done
这里-o BatchMode=yes非常关键,它禁止SSH进行任何交互式输入,如果免密没配上,不会卡在密码提示符那里,而是直接失败,输出一目了然。跑一遍就能看出哪些机器配置成功、哪些需要重新处理。
6. 免密之后的安全收尾:从禁用密码到密钥全生命周期管理
免密登录只是手段,安全可控才是目的。配置完免密之后,还有几件收尾的事情得做,不然前面等于白干。
6.1 确认正常后关闭密码登录
在确认所有必须免密登录的客户端都测试通过之后再执行这步操作,否则你把自己锁在外面是迟早的事。修改/etc/ssh/sshd_config:
code复制PasswordAuthentication no
ChallengeResponseAuthentication no
UsePAM no
要特别小心UsePAM,有些发行版关闭PAM可能会导致SSH整个不可用。保守一点的做法是只改PasswordAuthentication no,重启前一定要另开一个终端窗口保持已登录状态,避免配置错误导致连不上。然后:
bash复制sshd -t
systemctl restart sshd
sshd -t是检查配置语法,返回没有任何输出就是正常。
6.2 私钥保管、passphrase和ssh-agent的组合拳
前面生成密钥时我让大家把passphrase留空,是为了配置流程顺畅不卡壳。但真正生产环境里,裸奔的私钥文件还是有风险。推荐的做法是:给私钥设置passphrase,然后配合ssh-agent使用,这样既不用每次输passphrase,又能多一层保护。
设置passphrase:
bash复制ssh-keygen -p -f ~/.ssh/id_ed25519
启用agent并添加密钥:
bash复制eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519
输入一次passphrase之后,只要agent进程还活着,所有ssh连接都直接走免密,不用重复输入。macOS和Windows上的OpenSSH客户端也都支持类似机制。
6.3 密钥轮换、找回和异常监控
最后说一点长期维护的经验。密钥这东西一旦分发出去,回收是非常困难的。所以要养成两个习惯:
- 定期轮换:制定一个周期(比如每半年)重新生成一批密钥对,更新服务器上的
authorized_keys。操作前先确认哪些机器、哪些账号在用旧公钥,避免轮换期间有人掉线。 - 公钥指纹登记:在服务器上执行
ssh-keygen -lf ~/.ssh/authorized_keys可以查看每个公钥的指纹,做好登记表,哪天发现多了奇怪的公钥,能第一时间定位是哪台客户端加进来的。
我自己还习惯在crontab里加一条定时任务,每天把/var/log/secure里SSH相关的日志汇总发到邮箱或者监控系统,重点关注异常登录尝试和认证失败记录。免密登录配好之后,并不代表可以高枕无忧,密钥安全是终身责任。
最后分享个小技巧:如果你配好免密之后发现某些命令在远程机器上执行时还是会提示要密码,比如sudo,那是sudo本身的密码要求,不是SSH的问题。这时候需要在服务器上配置sudo免密,或者用ssh -t分配一个伪终端再操作,别把这两件事混为一谈。配置SSH免密只是第一步,后续还有权限管理、堡垒机、审计等一大堆事情要做,但把这第一步走得扎实,后面就顺溜多了。
