刚在终端里粘贴好私钥回车屏幕上弹出一句Permission denied (publickey)。这个场景对跟 Linux 打过交道的人来说应该都不陌生。我在刚接触服务器运维那会儿被这个报错卡过不下十几次而且它从来不是单一原因可能是密钥没放对位置服务端把公钥认证关了也可能只是某个目录的权限被意外搞坏。这篇文章就把我从实践里总结出来的排查链路完整写一遍配合ssh -vvv的调试输出和 sshd 日志帮你一步步定位问题而不是看到报错就瞎试。1. 区分三种“Permission denied”先确认卡在登录哪一步很多人一看到 Permission denied 就去查密钥结果折腾半天发现其实是防火墙。所以第一件事不是改配置而是看完整报错。SSH 登录过程其实分三个阶段TCP 连接建立、协议/算法协商、认证。Permission denied是认证阶段失败的结果而Connection timed out、No route to host、Connection closed by remote host这些是传输层或会话层的问题原因完全不同。常见的失败提示大概分三类我先列出来后面逐个展开。报错形态问题阶段首选排查方向Permission denied (publickey)公钥认证被拒密钥位置、权限、sshd 配置Permission denied (password)密码认证被拒用户密码、PAM 锁定、登录白名单Connection closed by ...会话被中断sshd 启动状态、TCP 包装、fail2ban 封 IP1.1 看小括号里的内容那是服务端给你的认证菜单OpenSSH 报错时通常会写Permission denied (publickey,password)小括号里的列表就是服务端允许的认证方式。比如只写了publickey说明这台服务器把密码登录关了你输密码再对也没用只写了password说明 PubKeyAuthentication 被关了你配的公钥再好也不会被验证。类似的还有keyboard-interactive这是 PAM 类的交互式认证常见于启用了双因子或者 Windows AD 认证的环境。它和普通密码不一样报错形态也会不同比如此类认证失败时显示Permission denied (keyboard-interactive)。1.2 连接都没建立成功不要怪认证如果你看到的是Connection refused或者Connection timed out那还没走到认证环节。Connection refused一般是 sshd 没启动或者端口不对Connection timed out一般是安全组、防火墙没放行或目标主机根本不可达。顺手用一行命令确认下端口状态nc -vz 192.168.1.10 22如果端口都没通就别继续调试密钥了。之前帮人排查过一个问题他手里的服务器 IP 填错了一个数SSH 登录报的是 permission denied —— 因为那个 IP 上恰好有另一台机器正连着别的会话登录时走到了认证阶段才被拒。这种阴差阳错最容易让人跑偏。2. 公钥认证的“权限洁癖”authorized_keys 与前后目录链绝大多数公钥认证失败最后都能归到权限问题上。OpenSSH 在后台有一个很严格的安全检查机制StrictModes它会检查登录用户家目录、~/.ssh目录和authorized_keys文件的所有者和写权限。只要你把其中一个改成了“别的用户可写”sshd 就会拒绝使用这个公钥文件。这是为了防止一个场景同一个机器上有多个用户如果一个用户对另一个用户的家目录有写权限他就能往人家authorized_keys里塞自己的公钥从而实现无密码登录。所以 sshd 宁可把你拒之门外也不冒这个险。2.1 为什么目录权限管得这么严你可以把~/.ssh/authorized_keys想象成公寓门口的信箱信箱锁芯只有在“只有你本人能碰”的情况下才可信。如果信箱所在的走廊是公共的、任何路过的人都能写那警察sshd自然会认为信箱里的信件公钥可能被动过手脚。这个安全模型导致了一个让新手很困惑的现象authorized_keys文件里面明明有正确的公钥文件内容也确认过没问题但登录还是被拒。这时候去翻系统日志常见的是这些Authentication refused: bad ownership or modes for directory /home/user Authentication refused: bad ownership or modes for file /home/user/.ssh/authorized_keys2.2 一套可以直接照抄的修复命令遇到权限类问题我通常会直接执行这么几条chmod 700 ~/.ssh chmod 600 ~/.ssh/authorized_keys chown -R user:user ~/.ssh chmod go-w ~注意最后一条很多资料不会强调用户家目录本身也不能让“组”或“其他用户”可写。默认情况下大多数发行版家目录权限是 755 或 700这都没问题只要组和其他没有写权限就行。如果你是在排查 root 用户的登录那路径换成/root/.ssh其他逻辑一模一样。2.3 容易被忽略的几个坑第一个坑从备份里恢复用户目录。备份归档里的属主和权限可能来自另一台机器的 UID恢复之后authorized_keys的属主变成了数字 ID 对不上的用户sshd 直接拒绝。用ls -ld检查一下属主是不是user:user再用chown修回来。第二个坑Windows 下编辑过的公钥文件。部分文本编辑器会存成带 BOM字节序标记的 UTF-8或者把换行符统一成 CRLF。公钥内容本身是单行文本BOM 字符在开头有时候粘贴时不会被看见但服务端比对时就会出问题。遇到过一种情况公钥文件里开头有个不可见字符服务端读出来的字符串跟预期不匹配登录一直失败。排插了半天用head -1 | od -c才看到的。如果你在 Windows 上操作建议保存为纯文本、UTF-8 无 BOM换行用 LF。第三个坑多用户环境下的组权限。有的团队会把用户加进同一个组然后图方便给整个组加写权限比如chmod -R gw /home/。这么做会让 sshd 的严格模式直接拒绝所有人的公钥登录。3. 密码没错却说密码错误服务端配置才是隐形凶手很多人没配公钥就靠密码登录。结果输入密码回车看到Permission denied (password)。这时候第一反应是“密码难道错了”但又确认没记错。实际上密码正确但登录被拒的情况非常常见。3.1 先把“密钥失败”和“密码失败”区分开有时候你用的是公钥认证但客户端配置里同时开了其他认证方式报错显示的可能是password。这时候要看ssh -v输出里的认证顺序看客户端到底Offering public key了没有。别看到(password)就以为只是密码的问题可能客户端连私钥都没提供。如果走的是纯密码认证先做一次自检ssh userserver -o PreferredAuthenticationspassword -o PubkeyAuthenticationno这样强制走密码认证能避开本机默认载入的密钥干扰。3.2 sshd 配置里的几扇隐形闸门密码认证失败后的排查按下面几个方向挨个过PasswordAuthentication no服务端直接关闭密码登录。很多人从云厂商控制台重置过密码却忘了服务器配置里根本不接受密码认证。检查/etc/ssh/sshd_config确认PasswordAuthentication yes。PermitRootLogin no或prohibit-password如果是 root 登录这个配置直接堵死密码。prohibit-password表示 root 只能用公钥登录密码登录被拒绝。很多云服务器默认就是这种策略所以 root 密码再正确也登不上。用户锁定/过期检查/etc/shadow里对应用户的密码字段如果前缀是!或*说明账号被锁。执行passwd -u user可以解锁。登录白名单AllowUsers、DenyUsers、AllowGroups、DenyGroups这些指令以及Match块里的条件都有可能把目标用户排除在外。shell 无效用户的登录 shell 如果不存在比如/bin/false、/sbin/nologin认证成功也会立刻断开表现也像登录失败。检查/etc/passwd里用户的 shell 字段。3.3 防爆破工具的“误杀”如果这台机器装过 fail2ban 或者启用了 pam_faillock那问题可能根本不在密码。反复输错几次密码后IP 或用户会被临时锁定。这时候即便输入正确密码也会被 PAM 拦截。看日志典型的痕迹是pam_faillock(sshd:auth): userroot auth_count3 unlock_time900遇到这种要么等解锁窗口过去要么有控制台权限就直接清掉计数。不同发行版的命令不太一样CentOS/RHEL 用faillock --user user --resetDebian/Ubuntu 老版本用pam_tally2 --useruser --reset。注意先确认你系统里装的是哪个不然会执行失败。4. 服务端 sshd_config 被改坏时的两条线索还有一种情况比权限更隐蔽你手里有正确的私钥服务端也认可这个公钥但 sshd 在加载配置时把“验证公钥”这个功能关掉了或者根本读错了路径。4.1 PubkeyAuthentication 与 AuthorizedKeysFile 到底是什么意思PubkeyAuthentication决定服务端是否接受公钥认证。默认是yes但如果被改成no无论你公钥放得多么规范都会在认证阶段直接失败。AuthorizedKeysFile决定公钥文件路径默认值是.ssh/authorized_keys这个路径是相对用户家目录的。它也可以写成绝对路径比如/etc/ssh/authorized_keys/%u这样多个用户共用一个目录。有些教程会让「把公钥加到服务器全局路径」方便管理但没注意路径配置问题导致用户自己的~/.ssh/authorized_keys不再被读取。还有一个细节容易被忽略较新的 OpenSSH 会引入/etc/ssh/sshd_config.d/目录并在主配置里用Include语句加载。如果你只在主配置文件里改了PasswordAuthentication yes但目录下还有一个99-custom.conf写了PasswordAuthentication no那后加载的配置会覆盖前面的。检查时别只看主文件把整个目录下的配置都过一遍。4.2 改完配置后的加载与验证修改sshd_config之后哪怕是一个拼写错误也可能导致 sshd 不能启动。常见做法是先用配置自检命令sudo sshd -t这个命令只做语法检查不启动服务。确认没问题后再重新加载sudo systemctl reload sshdreload和restart的区别要注意reload不会断开现有连接但对某些类型的配置比如监听地址、端口不生效restart会更彻底但会掉线。远程操作时如果改错了配置导致 sshd 无法启动旧连接还在还有机会补救如果restart起不来服务就彻底连不上了。所以远程改配置时我习惯先用sshd -t检查再执行 reload。想看实际生效的配置可以用sudo sshd -T | grep -i authorizationsshd -T会输出最终生效的运行时配置包括pubkeyauthentication、passwordauthentication、authorizedkeysfile这些关键项一眼看出问题在哪。4.3 实际踩过的一个案例有次帮客户处理云主机公钥配置完全没问题但登录始终失败。查日志发现公钥文件虽然存在于/root/.ssh/authorized_keys但服务端配置里写的是AuthorizedKeysFile /home/user/.ssh/authorized_keys而且那个文件里压根没有内容。原因是初始化脚本在执行时用了 root 权限把公钥写到 root 的目录但登录账号是普通用户。这类问题在云镜像自动化部署时特别容易发生尤其是用 cloud-init 或自定义脚本批量创建用户的时候。排查思路很简单先确认你登录的用户是谁再用sudo sshd -T看生效的authorizedkeysfile路径最后确认那个路径下确实有你需要的公钥。5. 用 ssh -vvv 把会话摊开一行行读调试输出排查认证问题最有效的手段就是打开调试模式。很多人一遇到问题就删日志、重装系统其实ssh -vvv的输出已经把答案写得明明白白了。5.1 debug 输出里值得记住的几个关键词执行ssh -vvv userserver你会看到一大堆 debug 信息重点看这几段debug1: Authentications that can continue: publickey,password debug1: Offering public key: /home/user/.ssh/id_ed25519 ... debug1: Server accepts key: /home/user/.ssh/id_ed25519 ... debug1: Authenticated to server (server_ip:22).Authentications that can continue服务端当前还能接受哪些认证方式相当于每轮认证后的候选菜单。Offering public key客户端在展示“我正在尝试用这把私钥”。Server accepts key服务端验证公钥通过正常下一步就是成功。如果一直没有出现Server accepts key而是反复Offering public key之后直接Permission denied说明服务端对这把私钥匹配的公钥不认可。No more authentication methods to try所有认证方式都试完了最终报错。还有一种常见情况客户端本机存在多个私钥文件ssh会按顺序挨个 offer。如果提供了太多次私钥可能触发服务端的 MaxAuthTries 限制直接断开。这时候报错是Too many authentication failures不是权限问题解决办法是用-o IdentitiesOnlyyes -i 指定私钥或者清理~/.ssh下多余的默认密钥文件。5.2 服务端日志怎么看如果客户端已经确认公钥被 offer 了服务端仍然拒绝下一步就看服务端日志。Debian/Ubuntu 系列看这个sudo tail -f /var/log/auth.logCentOS/RHEL 系列看这个sudo tail -f /var/log/secure用 systemd 的发行版也可以直接sudo journalctl -u sshd -n 50从日志里找两类信息一类是Failed password for user ...这证明客户端请求确实到达了认证阶段另一类是Authentication refused: bad ownership or modes ...这种明确提示权限不符。5.3 排障时的一个小习惯-vvv的日志量非常大在慢网络上还会拖慢连接速度。我建议平时先用-v等确认进入认证阶段后再升级到-vvv。如果需要把调试过程发给别人或存档加一个ssh -vvv userserver 2 ssh-debug.log调试信息输出到 stderr用2重定向到文件避免终端刷屏。排查结束后记得关闭调试参数有些同学用完-vvv就忘了之后再连服务器每次都慢半拍还以为是网络问题。6. 客户端伪“拒绝”密钥没加载、主机标识过期、工具选错认证项有时候问题根本不在服务端而是在客户端这边。常见的情况是服务端日志里根本没有任何认证尝试但客户端已经报了 permission denied。这是典型的“从门口就没掏出钥匙”。6.1 本地私钥与 ssh-agent先确认私钥文件确实在你本机的预期路径下。默认路径是~/.ssh/id_rsa、~/.ssh/id_ed25519、~/.ssh/id_ecdsa。如果你把私钥放在其他路径连接时要用-i指定ssh -i /path/to/my_key userserver另一个很容易忽略的问题是本地私钥文件权限太松。OpenSSH 客户端在 Linux 上对私钥文件也会做权限检查权限太开就会直接忽略这个 key并提示bad permissions: ignore key: /home/user/.ssh/id_ed25519解决办法是chmod 600。Windows 上的 OpenSSH 客户端对文件 ACL 同样敏感如果私钥文件继承的 NTFS 权限里带了 Everyone 或 Users 组的读取权限也会被忽略。这时候要去右键属性里把多余的用户权限移除。如果启用了 ssh-agent检查一下代理里到底加载了哪些密钥ssh-add -l如果私钥不在 agent 列表里连接时又没有明确指定-i客户端可能找不到可用的私钥。6.2 known_hosts 变更引发的“假报错”还有一类报错和认证无关但很容易被混淆——服务端主机密钥发生变化时客户端会拒绝连接并提示REMOTE HOST IDENTIFICATION HAS CHANGED这不是 Permission denied但很多人第一次见会以为是认证失败。通常发生在服务器重装系统、重建容器、或者别人换了 SSH 监听 key 之后。这是防止中间人攻击的机制不能随便绕过。确认服务器确实被重置过之后再删除旧的指纹记录ssh-keygen -R server_ip然后再重新连接会提示接受新指纹确认无误后回车。6.3 图形化工具与批量脚本里的坑现在用 vs code 连远程主机的人很多它的 Remote-SSH 插件本质上还是调本机 OpenSSH 客户端但会话配置里容易出问题。常见的是你明明在终端可以登录vs code 却报 permission denied多半是插件读取了~/.ssh/config里的一个旧配置指定了错误的私钥路径或者用户名。检查一下config文件里的Host块看看有没有IdentityFile或User被写死。类似的还有 Xshell、MobaXterm 这类工具。它们的会话设置里有个“认证方法”下拉框有时候你选择的是“密码”但实际服务端只允许公钥。看起来报的是密码错误实际上是因为认证方法压根不对。批量登录时如果脚本里用ssh userhost却不指定私钥每台机器的客户端可能依次尝试多个默认公钥。如果目标主机有 MaxAuthTries 限制批处理里前几台可能正常后面就开始出现Too many authentication failures。解决办法是在~/.ssh/config或脚本参数里加上ssh -o IdentitiesOnlyyes -i /path/to/your_key userhostIdentitiesOnly的作用是只使用你指定的私钥避免客户端自动尝试所有默认 key这是批量连接时非常实用的参数。7. 最后的兜底人都进不去时怎么从门外面拿回控制权如果上面所有办法都试过了还是连不上那就别指望远程修复了。这时候最稳妥的方案是利用云平台或物理机提供的带外控制台。7.1 带外控制台是最后一道保险云服务器厂商基本都有网页版 VNC/管理终端物理服务器一般有 IPMI 或带外管理卡。这个通道是独立于操作系统的即便网络配置或 sshd 坏了你也能看到登录界面。进入系统后先以 root 或单用户模式修复。如果只是 sshd 配置改错了直接编辑/etc/ssh/sshd_config恢复默认项然后sshd -t验证、重启服务即可。如果是密钥权限问题按前面第 2 章的修复命令重新设置权限就行。有人会问如果密码也忘了怎么办。这个涉及系统引导参数调整不同发行版做法不一样建议提前备份重要数据并设置密码找回策略而不是临时到处翻教程。更重要的是别把“远程登录”这条路当成唯一的救命通道。7.2 应急之后必须马上做的几件小事从事故里恢复过来之后我一般会立刻做这几件事备份当前可用的 sshd 配置cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak.YYYYMMDD。确认 sshd 的 Include 目录下没有其他配置在“捣乱”。登录测试走一遍完整流程确认从公钥认证到会话建立都正常。把这次排查中执行过的关键命令记下来尤其是查日志的路径和权限修复命令下次遇到类似问题能省一半时间。7.3 写配置时的一个经验习惯远程修改 sshd 配置无论多小的改动我都习惯先执行sshd -t再执行systemctl reload sshd而不是直接restart。因为 sshd 是保证你还能进这台机器的关键服务一旦启动失败且没有保留旧连接你就只能通过带外控制台去救资源有限的时候非常狼狈。养成“先检查、后加载、再验证”的顺序比任何技巧都重要。多说一句实际感受权限类问题占了 SSH 排障的绝大多数但它们之间又有细微差异。遇到问题别急着重装系统先看清楚你是哪一步被拒的再看服务端日志和ssh -vvv的输出链条理清了答案一般自己就会浮出来。