2024 年的一天,我像往常一样 SSH 登一台服务器,结果终端直接甩给我一行:Permission denied (publickey)。我愣了一下——这台机器我用了大半年,一直是免密码的密钥登录,从没出过问题,今天怎么突然就把我拒之门外了?我第一反应是:是不是我的私钥文件坏了,或者被我误删了。我去 ~/.ssh/ 一看,私钥好端端地在那;我又怀疑是不是服务器上的 authorized_keys 被谁清空了,可我从控制台的 VNC 进去一看,authorized_keys 文件也在,里面我那把公钥一个字符都不少。私钥在、公钥在、内容也对得上,这两把钥匙明明是配对的,服务器却就是不认。我又试着用别的同事的密钥登,也是同样的 Permission denied。这就说明问题不在我某一把钥匙上,而是服务器这边,对所有人的密钥登录都"罢工"了——一定是它身上某个统一的环节出了问题。我盯着这个"钥匙没错、锁却不开"的现象想了很久,最后才意识到:SSH 的密钥认证,要成功,光有"钥匙配对"是不够的;服务端在用你的钥匙之前,还会先检查一圈"放钥匙的地方安不安全",这一圈检查没过,它会直接当你没钥匙。这件事逼着我把 SSH 公钥认证的完整流程、sshd 的 StrictModes 安全检查、文件权限这一整套彻底理清了。本文复盘这次实战。
问题背景
环境:CentOS 7,一台服务器,一直用 SSH 密钥免密登录
事故现象:
- 某天起 SSH 登录直接 Permission denied (publickey)
- ★ 私钥还在、服务器上的 authorized_keys 也在、公钥内容也对
- 换别人的密钥登,同样被拒 —— 不是某一把钥匙的问题
现场排查:
# 1. 客户端加 -v,看握手到底卡在哪
$ ssh -v root@服务器
...
debug1: Offering public key: /home/me/.ssh/id_ed25519
debug1: Authentications that can continue: publickey
debug1: No more authentication methods to try.
Permission denied (publickey).
# ★ 客户端把公钥递过去了,服务端没接受
# 2. ★ 真正的答案在服务端的 sshd 日志里
$ tail -30 /var/log/secure # CentOS;Ubuntu 是 /var/log/auth.log
sshd[2910]: Authentication refused: bad ownership or modes for
directory /root
# ^^^^^^^^^^^^^^^^^^^^^^^^ ★ 关键:/root 这个目录的属主或权限不对!
# 3. ★ 去看 /root 的权限
$ ls -ld /root
drwxrwxr-x. 2 root root ... /root # ★ 权限是 775 —— 组和其他人都能写
# 4. 对比 .ssh 和 authorized_keys
$ ls -ld /root/.ssh
drwxr-xr-x. 2 root root ... /root/.ssh # ★ 755 —— 也偏松(组/其他人可读)
$ ls -l /root/.ssh/authorized_keys
-rw-rw-r--. 1 root root ... authorized_keys # ★ 664 —— 同组可写
根因(后来想清楚的):
1. ★ SSH 公钥认证能成功,不只是"私钥和公钥配对"这
一件事。在比对钥匙【之前】,sshd 还会先做一轮
安全检查 —— 检查"存放公钥的地方"够不够安全。
2. sshd 有个默认开启的选项 StrictModes,它要求:
你的家目录、.ssh 目录、authorized_keys 文件,
★【不能被属主以外的人写】。
3. 之前有人为了某个操作方便,给 /root 执行了
chmod 775 —— 让同组用户也能写 /root。
4. ★ sshd 一看 /root 居然组内可写,判定"这个目录
不安全,放在里面的 authorized_keys 不可信",
于是【直接忽略 authorized_keys】,当你没有公钥。
5. 结果:钥匙明明是对的,但 sshd 压根没去读那把锁
里的钥匙串 —— 它在更前面那道安全门就把你拦了。
密钥登录失败 ≠ 钥匙错了,常常是"放钥匙的地方"权限太松。
修复 1:第一步——让 SSH 自己告诉你为什么拒绝
# === ★ 别猜:SSH 有非常详细的诊断信息,只是你没问它 ===
# === 客户端视角:ssh -v ===
# 加 -v(verbose),把整个登录握手过程打出来:
$ ssh -v root@服务器
# 想看更细,-vv 或 -vvv。重点看这几行:
debug1: Offering public key: .../id_ed25519 # 客户端递出了哪把公钥
debug1: Authentications that can continue: publickey
Permission denied (publickey).
# ★ 客户端视角能告诉你:它【试了】哪把钥匙、服务端
# 允许哪些认证方式。但它【看不到】服务端为什么拒 ——
# 真正的原因,在服务端。
# === ★ 服务端视角:sshd 日志(这才是答案所在)===
# 登录失败的真正原因,sshd 会写进系统认证日志:
$ tail -50 /var/log/secure # CentOS / RHEL
$ tail -50 /var/log/auth.log # Ubuntu / Debian
$ journalctl -u sshd --since '10 min ago' # systemd 通用
# ★ 实时盯着看,然后另开一个窗口去登一次:
$ tail -f /var/log/secure
# === ★ sshd 日志里几种典型的"拒绝原因" ===
# 1. bad ownership or modes for directory /root
# -> ★ 权限问题(本文主角)。某个目录/文件权限太松。
# 2. Authentication refused: bad ownership or modes for
# file /root/.ssh/authorized_keys
# -> ★ authorized_keys 文件本身权限太松。
# 3. Connection closed by ... [preauth]
# -> 可能是认证方式被禁、或更底层的问题。
# 4. User root not allowed because ... / not in AllowUsers
# -> ★ 是 sshd_config 的访问控制策略挡了(见修复 4)。
# === ★ 服务端排查更猛的一招:临时开 debug 模式 ===
# 如果日志还不够清楚,可以在另一个端口临时跑一个
# debug 模式的 sshd(★ 不要动正在用的那个):
$ /usr/sbin/sshd -d -p 2222
# 然后另开窗口:ssh -p 2222 root@服务器
# ★ -d 模式会把服务端每一步判断【全打在屏幕上】,
# 它会明明白白告诉你:它检查了哪个目录、嫌它哪里
# 不对、于是忽略了 authorized_keys。排完记得 Ctrl+C。
# === 认知 ===
# ★ Permission denied (publickey) 是个【最终结论】,
# 不是【原因】。原因永远在 sshd 日志 / sshd -d 里。
# 不看日志就猜,基本是浪费时间。
修复 2:sshd 为什么对权限这么"挑剔"——StrictModes
# === ★ 理解 sshd 的"安全洁癖":StrictModes ===
# === 公钥认证,服务端到底信的是什么 ===
# 公钥登录的本质:服务端用 ~/.ssh/authorized_keys 里
# 存的那把公钥,去验证你手上私钥的签名。
# ★ 所以 authorized_keys 这个文件,是服务端【信任的
# 根据】。谁能往这个文件里写东西,谁就能给自己
# "配一把能进来的钥匙"。
# === ★ 一个安全漏洞:如果别人能改你的 authorized_keys ===
# 设想:你的 authorized_keys,或者它所在的 .ssh 目录,
# 或者你的家目录,是【别的用户也能写】的。
# 那么任何一个能写的人,只要往你的 authorized_keys
# 里加一行他自己的公钥,他就能【冒充你】登录进来。
# ★ 这是一个严重的提权漏洞。
# === ★ StrictModes:sshd 的防御 ===
# 为了堵这个洞,sshd 有一个【默认开启】的选项:
$ grep -i strictmodes /etc/ssh/sshd_config
#StrictModes yes # 注释掉也是默认 yes
# StrictModes yes 的意思是:在使用你的 authorized_keys
# 之前,sshd 先【检查一圈权限】。它要确认:
# - 你的家目录、.ssh 目录、authorized_keys 文件,
# ★【除了属主本人,任何人(组、其他人)都不能写】。
# - 这些路径的【属主】必须是你本人(或 root)。
# ★ 只要有一项不满足,sshd 就判定"这个 authorized_keys
# 不可信",干脆【当它不存在】—— 直接忽略你的公钥。
# === ★ 关键认知:它不是"报错",是"装看不见" ===
# 这里最坑的地方:sshd 检查不通过时,它【不会】提示你
# "权限不对、请修复";它只是默默地【跳过】你的
# authorized_keys。
# 对你来说,表现就是 "Permission denied (publickey)"
# —— 和"你压根没配公钥"长得一模一样。
# ★ 所以"钥匙明明是对的却被拒",十有八九就是
# StrictModes 在背后悄悄否决了你。
# === 别去关 StrictModes ===
# 你可能想:那我把 StrictModes 关了不就好了?
# ★ 千万别。关掉它 = 主动打开上面那个提权漏洞。
# 正确的做法,永远是【把权限改对】,而不是
# 把检查这个权限的安全机制关掉。
修复 3:到底哪些权限要对——一份精确清单
# === ★ StrictModes 到底要求哪些路径、什么权限 ===
# === 核心原则:一条链上,任何一环都不能"组/其他人可写" ===
# 从家目录,到 .ssh 目录,到 authorized_keys 文件 ——
# 这一整条链上的每一环,都【不能被属主以外的人写】。
# === ★ 1. 家目录 ===
$ ls -ld ~
# 要求:★ 组和其他人【不能有写权限】。
# 推荐 700(只有自己能进),755 也可以(关键是 group
# 和 other 没有那个 w)。
# ★ 绝对不行:775、770、777 —— 只要 group 或 other 有 w。
$ chmod 750 ~ # 或 700;去掉 group/other 的 w 即可
# 我这次的病根:/root 被人 chmod 成了 775。
# === ★ 2. .ssh 目录 ===
$ ls -ld ~/.ssh
# 要求:★ 必须 700(drwx------)。只有属主能读写进。
$ chmod 700 ~/.ssh
# === ★ 3. authorized_keys 文件 ===
$ ls -l ~/.ssh/authorized_keys
# 要求:★ 600(-rw-------),最宽松也就 644。
# 核心还是:group / other 绝对不能有 w。
$ chmod 600 ~/.ssh/authorized_keys
# === ★ 4. 属主必须正确 ===
# 上面这些路径的【属主】,必须是登录的那个用户本人。
# 一个常见坑:你用 sudo / root 操作过这些文件,
# 把属主改成了 root,普通用户登录就过不了检查。
$ chown -R 用户名:用户名 ~/.ssh
# === ★ 私钥文件(在【客户端】这边)也有要求 ===
# 别忘了客户端!你本地的私钥权限太松,ssh 客户端
# 会【直接拒绝使用它】并警告:
# "UNPROTECTED PRIVATE KEY FILE!"
$ ls -l ~/.ssh/id_ed25519
$ chmod 600 ~/.ssh/id_ed25519 # 私钥必须 600(或 400)
# === ★ 一键修复:把整条链的权限和属主都设对 ===
$ chmod 700 ~ # 家目录(按需 700/750/755)
$ chmod 700 ~/.ssh
$ chmod 600 ~/.ssh/authorized_keys
$ chown -R "$(whoami)":"$(whoami)" ~/.ssh
# 客户端那台机器上:
$ chmod 600 ~/.ssh/id_ed25519
# === 权限速记 ===
# 家目录:不能 group/other 可写 | .ssh:700 | authorized_keys:600
# ★ 一句话:这条链上,"写"权限只能属于你自己一个人。
修复 4:密钥登录失败的其他常见原因
# === ★ 权限是头号原因,但不是唯一原因 ===
# === 原因 1:authorized_keys 内容/格式不对 ===
# - 公钥必须是【一整行】,中间不能有换行(复制粘贴
# 时极易被断行)。
# - 一个文件里多把公钥,要【一把一行】。
$ cat -A ~/.ssh/authorized_keys # -A 能看出隐藏的换行/制表符
# ★ 确认:你客户端的【公钥】(.pub)内容,和服务端
# authorized_keys 里的某一行,要【一字不差】。
# === ★ 原因 2:sshd_config 的访问控制把你挡了 ===
$ grep -Ei 'PubkeyAuthentication|AllowUsers|DenyUsers|AllowGroups|PermitRootLogin|AuthorizedKeysFile' /etc/ssh/sshd_config
# 几个关键项:
# - PubkeyAuthentication no -> ★ 公钥认证被整个关了
# - PermitRootLogin no -> ★ 禁止 root 登录(你正用 root)
# prohibit-password / without-password = 允许 root 但只能用密钥
# - AllowUsers / AllowGroups -> ★ 白名单。你不在名单里就被拒
# - DenyUsers / DenyGroups -> 黑名单。你在名单里就被拒
# - AuthorizedKeysFile -> 公钥文件的路径。被改过的话,
# sshd 根本不在 ~/.ssh/authorized_keys 找钥匙
# === ★ 原因 3:改了 sshd_config 却没重启 sshd ===
# 改完配置不生效,十有八九是忘了重载:
$ sshd -t # ★ 先测语法,别改错了重启不来
$ systemctl reload sshd # 语法 OK 再 reload
# === 原因 4:SELinux 把 .ssh 的安全上下文搞乱了 ===
# 常见于:你把 authorized_keys 从别处 mv 过来,
# 它带着错误的 SELinux 上下文。
$ ls -Z ~/.ssh/authorized_keys # 看安全上下文
$ restorecon -Rv ~/.ssh # ★ 把 .ssh 的上下文恢复成标准值
$ getenforce # 看 SELinux 是否 Enforcing
# === 原因 5:磁盘满了 ===
# 极端情况:服务器根分区满了,sshd 某些操作失败。
$ df -h /
# === ★ 通用方法:还是回到日志 ===
# 上面每一种,sshd 日志 /var/log/secure 里几乎都会
# 留下对应的线索。所以 ——
$ tail -f /var/log/secure # 边看日志边登一次,准没错
修复 5:正确解法——修对权限,并留一条后路
# === ★ 解法:对症把权限改对,绝不靠关安全机制 ===
# === ★ 解法 1:按 sshd 日志的指向,修那条权限链 ===
# 我这次日志明说 "bad ownership or modes for directory
# /root",那就直接修 /root:
$ chmod 700 /root # ★ 去掉 /root 的组/其他人写权限
# 再把整条链一并修对(见修复 3):
$ chmod 700 /root/.ssh
$ chmod 600 /root/.ssh/authorized_keys
$ chown -R root:root /root/.ssh
# ★ 改完立刻能登。这次根治,就这么简单 —— 难的从来
# 不是修,是【知道要修这里】。
# === ★ 解法 2:验证(别关掉旧连接去试)===
# ★ 极其重要:改 SSH 相关的东西,【千万不要】关掉
# 你当前这个还连着的会话。要【另开】一个新终端
# 去测试登录。万一改错了,你手上这个旧会话还在,
# 还能救。
$ ssh -v root@服务器 # 新窗口测,-v 看细节
$ tail -f /var/log/secure # 旧窗口同时盯日志
# === ★ 解法 3:绝不要用"关 StrictModes"来"解决" ===
# 网上有些文章会教你:登不上就把 StrictModes 设成 no。
# ★ 这是把房子着火了、于是把烟雾报警器拆掉。
# StrictModes 报警,说明你的 .ssh 真的不安全了。
# 正确做法永远是修权限,不是让 sshd 别管。
# === ★ 解法 4:权限被改乱,先想"是谁、为什么改的" ===
# 我这次 /root 被 chmod 775,是同事为了让另一个账号
# 能读 /root 下某个文件,图省事直接放开了 /root。
# ★ 正确的共享方式从来不是 chmod 775 家目录,而是:
# 把要共享的文件单独放到 /opt、/srv 这类公共目录,
# 再按需设权限 —— 家目录这条 SSH 命脉,不能动。
# === ★ 解法 5:永远留一条"密钥进不去"的后路 ===
# 这次能修复,全靠我还能从云控制台的 VNC / 串口
# 进去。所以:
# - 服务器一定要保留一个【控制台登录】的途径
# (云厂商的 VNC、物理机的 IPMI/串口)。
# - ★ 知道 root 的密码(或有应急账号),别让自己
# "只剩 SSH 密钥这一条路",否则密钥一坏就彻底锁死。
# === 验证清单 ===
$ ls -ld ~ ~/.ssh # 家目录无组/他人写,.ssh 是 700
$ ls -l ~/.ssh/authorized_keys # 600
$ ssh -v 新窗口登一次 # 能进
$ tail /var/log/secure # 日志里不再有 bad modes
# ★ 四项都对,才算这次 SSH 锁门事故真正根治。
修复 6:SSH 登录排查纪律
# === 这次事故暴露的认知盲区,定几条纪律 ===
# === 1. ★ Permission denied (publickey) 是结论不是原因 ===
# 原因永远在服务端 sshd 日志里,不看日志就是瞎猜。
# === 2. ★ 客户端 ssh -v,服务端 tail -f /var/log/secure ===
$ ssh -v root@服务器
$ tail -f /var/log/secure # 边看边登
# === 3. ★ "钥匙对却被拒",头号嫌疑是权限太松(StrictModes)===
# 家目录/.ssh/authorized_keys 任一环组或他人可写就被否决。
# === 4. ★ 权限链:家目录无组他人写,.ssh 700,authorized_keys 600 ===
$ chmod 700 ~ ; chmod 700 ~/.ssh ; chmod 600 ~/.ssh/authorized_keys
# === 5. 这些路径的属主必须是登录用户本人,别被 root 占了 ===
$ chown -R 用户:用户 ~/.ssh
# === 6. ★ 绝不用关 StrictModes 来"解决",那是拆报警器 ===
# === 7. 改 SSH 配置/权限,务必另开窗口测,留着旧会话兜底 ===
# === 8. ★ 永远保留控制台(VNC/IPMI)这条不依赖 SSH 的后路 ===
# === 9. 排查"SSH 密钥登不上"的步骤链 ===
$ ssh -v root@服务器 # ① 客户端视角:递了哪把钥匙
$ tail -f /var/log/secure # ② 服务端日志:真正的拒绝原因
$ ls -ld ~ ~/.ssh ; ls -l ~/.ssh/authorized_keys # ③ 查权限链
$ grep -Ei 'Pubkey|AllowUsers|PermitRoot' /etc/ssh/sshd_config # ④ 查策略
$ chmod 修对权限 / 改对配置 reload sshd # ⑤ 对症修复
# 按这个顺序,SSH 登录失败基本能定位、能根治。
命令速查
需求 命令
=============================================================
客户端看登录握手细节 ssh -v root@服务器(-vv/-vvv 更细)
服务端看认证日志 tail -f /var/log/secure(Ubuntu:auth.log)
服务端看 sshd 日志(通用) journalctl -u sshd --since '10 min ago'
临时开 debug 模式 sshd /usr/sbin/sshd -d -p 2222
看家目录/.ssh 权限 ls -ld ~ ~/.ssh
看 authorized_keys 权限 ls -l ~/.ssh/authorized_keys
修家目录权限 chmod 700 ~(去掉组/他人的 w)
修 .ssh 权限 chmod 700 ~/.ssh
修 authorized_keys 权限 chmod 600 ~/.ssh/authorized_keys
修属主 chown -R 用户:用户 ~/.ssh
测 sshd 配置语法 sshd -t
重载 sshd 配置 systemctl reload sshd
恢复 .ssh 的 SELinux 上下文 restorecon -Rv ~/.ssh
口诀:Permission denied 是结论不是原因,原因在服务端 sshd 日志里
钥匙对却被拒头号嫌疑是权限太松,家目录无组他人写 .ssh 700 authorized_keys 600
避坑清单
- Permission denied publickey 只是最终结论不是原因,真正的原因永远在服务端 sshd 日志里
- 排查 SSH 登录失败客户端用 ssh -v 看握手细节,服务端 tail -f /var/log/secure 边看边登
- SSH 公钥认证成功不只是私钥公钥配对,sshd 在比对钥匙前还会先检查存放公钥的地方安不安全
- sshd 默认开启 StrictModes,要求家目录 .ssh 目录 authorized_keys 都不能被属主以外的人写
- StrictModes 检查不过时不会报错提示,而是默默忽略 authorized_keys,表现得像你没配公钥
- 权限链家目录不能组或他人可写,.ssh 必须 700,authorized_keys 必须 600,任一环松了都不行
- 这些路径的属主必须是登录用户本人,被 sudo 操作改成 root 属主也会导致检查不通过
- 绝不能用关掉 StrictModes 来解决登录问题,那等于拆掉烟雾报警器,正确做法永远是修对权限
- 改 SSH 相关配置或权限务必另开窗口测试,保留当前还连着的旧会话作兜底,改错了能补救
- 永远保留控制台 VNC 或 IPMI 这条不依赖 SSH 的后路,别让自己只剩密钥一条路彻底锁死
总结
这次"钥匙明明是对的、服务器却把我锁在门外"的事故,纠正了我一个关于"认证"的、想当然的简化。在我的脑子里,SSH 密钥登录这件事,长久以来就是一个干净利落的"对锁"动作:我手上有一把私钥,服务器上有一把配对的公钥,登录的瞬间,无非是这两把钥匙碰一下,严丝合缝就放行,对不上就拒绝。在这个模型里,登录的成败,完全、且仅仅,取决于"这两把钥匙配不配对"这一件事。正因为我心里只有这么一个简单的二元判断,所以当 Permission denied 跳出来时,我的全部注意力,都死死地盯在了"钥匙"本身:是不是我的私钥坏了?是不是服务器上的公钥被删了?我反复地确认私钥还在、公钥还在、两者内容也对得上——我把"钥匙"这件事查到了无可再查,然后就彻底卡住了,因为在我那个模型里,钥匙没问题就等于登录该成功,可现实偏偏不成功,这就成了一个无解的悖论。我从来没有想过,问题可能根本不出在钥匙上,而出在一个我压根不知道其存在的、钥匙之外的环节。复盘到根上,我才明白,SSH 的公钥认证,远不是"两把钥匙碰一下"那么简单。在 sshd 真正拿起你的公钥去验证签名【之前】,它还有一道我从未留意过的前置关卡:它要先检查,"存放这把公钥的地方,到底安不安全"。这道关卡的逻辑,其实严谨得无可挑剔——你的 authorized_keys 文件,是服务器决定"信任谁"的根本依据,谁能往这个文件里写东西,谁就能给自己伪造一把合法的钥匙。所以 sshd 用一个叫 StrictModes 的默认开启的机制,坚持要求:从你的家目录,到 .ssh 目录,再到 authorized_keys 文件,这一整条链上的每一环,都绝不能被你本人以外的任何人写。只要有一环松了——比如我这次,有人为了图方便把 /root 改成了组内可写——sshd 就会判定:这个 authorized_keys 已经不可信了,因为别人有机会动过它。而它接下来的处理方式,正是这次事故最坑人、也最让我恍然大悟的地方:它不会报错,不会提示你"权限不对",它只是【默默地、彻底地忽略】你的 authorized_keys,就当它根本不存在。于是,在我这一侧看到的,就是一句和"你压根没配过钥匙"一模一样的 Permission denied。我的钥匙从头到尾都是对的,可 sshd 在更前面那道安全门就把我拦下了,它根本没走到"对钥匙"那一步。这次最大的收获,是我意识到,我对一个熟悉系统的理解,常常停留在"它对我做了什么"这个使用者的表层,而很少深入到"它为了安全,还在背后默默做了什么"这个设计者的层面。我每天用 SSH 密钥登录,享受着它的便利,却从来没有想过,这份便利的背后,系统其实一直在为我做着一系列我看不见的安全校验——它不只在验证我"是不是我",它还在替我守着"那个证明我是我的凭据,有没有被人动过手脚"。我把这套精密的安全机制,粗暴地理解成了一个"对钥匙"的简单开关,于是当那套机制基于它自己的、完全正确的理由否决我时,我就彻底失去了理解眼前现象的能力。一个安全系统拒绝你,理由未必是"你不是合法的人";也可能是"你存放凭证的方式,已经让你的凭证变得不值得信任了"。所以下一次,当我又遇到一个"凭证明明没问题、却依然被拒绝"的认证难题时,我不会再在凭证本身上钻牛角尖了。我会把视野从那把钥匙上挪开,去问一个更靠后的问题:这个系统,在真正使用我的凭证之前,还默默地、出于安全考虑,检查过些什么?——答案,往往就藏在那些它从不向我明说、却一直在严格执行的规矩里。
—— 别看了 · 2026