SSH 免密配了却还要密码:一次 Linux SSH 排查与加固复盘

SSH 免密配完一登录还是要密码,公钥内容反复核对没错,问题竟在 .ssh 目录权限上。排查梳理:免密的公私钥原理、ssh-copy-id 标准流程、SSH 拒绝过松权限的安全铁律、ssh -v 看穿握手、~/.ssh/config 管理多机,以及禁 root/禁密码/改端口/fail2ban 安全加固。

2024 年我给一台新服务器配 SSH 免密登录,流程我闭着眼都能背:生成密钥、把公钥拷过去。可配完一登录,它还是不依不饶地要我输密码。我反复检查公钥内容,一个字符都没错,就是不免密。折腾到后来我才发现,问题根本不在公钥本身,而在那个 .ssh 目录的权限上——SSH 出于安全考虑,会主动拒绝一个"权限太松"的密钥。这次排查让我把 SSH 免密登录的原理、以及一台服务器的 SSH 安全加固,认认真真梳理了一遍。本文复盘这次实战。

问题背景

环境:一台新装的 CentOS 7 服务器,要配 SSH 免密登录
事故现象:
- 按标准流程生成密钥、拷贝公钥到服务器
- 再 ssh 登录,依然弹出 password 提示,免密没生效
- 公钥内容反复核对,确认没贴错、没多空格
- 用密码能正常登上去,说明网络和账号都没问题

现场排查:
# 1. 用 -v 让 ssh 把握手过程打出来
$ ssh -v root@server
debug1: Offering public key: /home/me/.ssh/id_ed25519
debug1: Authentications that can continue: password
# —— 客户端递了公钥,服务器却没认,直接转回密码

# 2. 登上服务器,看认证日志
$ tail -50 /var/log/secure
sshd: Authentication refused: bad ownership or
      modes for directory /root/.ssh
# ★ 真相:bad ownership or modes —— .ssh 目录权限不对

# 3. 看一眼那个目录的权限
$ ls -ld /root/.ssh
drwxrwxr-x 2 root root ... /root/.ssh
# 组和其他人都有 w 权限 —— SSH 认为这"太不安全"

根因(后来定位到的):
.ssh 目录(及 authorized_keys 文件)权限过于宽松。
SSH 有一条安全铁律:如果存放密钥的目录/文件
能被属主以外的人写,它会【直接拒绝】用这个密钥认证,
因为别人能写,就意味着别人能往里塞自己的公钥。
公钥内容再正确也没用 —— 权限这一关就没过。

修复 1:先理解免密登录的原理

=== SSH 密钥认证,本质是一对"钥匙" ===

ssh-keygen 会生成一对密钥:
- 私钥(id_ed25519)      : 留在【你自己的电脑】,绝不外传
- 公钥(id_ed25519.pub)  : 拷到【你要登录的服务器】上

=== 登录时发生了什么(简化版)===
1. 你 ssh 连服务器,说"我想用密钥登录"
2. 服务器在它的 ~/.ssh/authorized_keys 里
   找有没有你这把公钥
3. 找到了 -> 服务器用这把公钥出一道"加密题"
4. 只有持有【配对私钥】的人才能解开这道题
5. 你的客户端用私钥解开、答对了 -> 放行,免密登录

=== 关键的安全前提 ===
- 私钥代表你的身份,泄露 = 别人能冒充你登录
- 公钥可以公开,但服务器上存公钥的地方
  【绝不能被别人写】—— 否则别人能塞自己的公钥进来
这就是为什么 SSH 对 .ssh 目录的权限如此较真。

=== 免密 vs 密码登录 ===
密码:每次都输密码,密码会在传输中被加密,但
      仍可能被暴力破解、被钓鱼。
密钥:不传密码,基于非对称加密,安全得多,
      也是生产服务器推荐的方式。

修复 2:标准的免密配置流程

# === 第一步:在【本地】生成密钥对 ===
$ ssh-keygen -t ed25519 -C "me@laptop"
# -t ed25519:密钥类型,ed25519 比老的 rsa 更短更安全,
#             首选它(老系统不支持时再用 -t rsa -b 4096)
# -C 是注释,写个能认出来的标识
# 一路回车,默认存到 ~/.ssh/id_ed25519(私钥)
#                和 ~/.ssh/id_ed25519.pub(公钥)
# 中间会问 passphrase —— 给私钥再加一层口令,
# 生产环境建议设(配合 ssh-agent 就不用每次输)

# === 第二步:把【公钥】拷到服务器 ===
# 方式 A(最省心):ssh-copy-id 自动帮你弄好
$ ssh-copy-id -i ~/.ssh/id_ed25519.pub root@server
# 它会自动:登录服务器 -> 创建 .ssh 目录 ->
#          把公钥追加进 authorized_keys -> 把权限设对
# ★ 强烈推荐用它,因为它会顺手把权限设对,
#   避开本文开头那个坑。

# 方式 B(手动,理解原理):
$ cat ~/.ssh/id_ed25519.pub | ssh root@server \
    "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"
# 注意是 >> 追加,不是 > 覆盖 —— 别把别人的公钥冲掉

# === 第三步:验证 ===
$ ssh root@server
# 不再要密码 = 成功

# === 一个常见困惑:私钥不叫默认名,ssh 找不到 ===
# 如果你的私钥叫 id_ed25519_prod 这种非默认名,
# ssh 默认不会用它。两种办法:
$ ssh -i ~/.ssh/id_ed25519_prod root@server   # 临时 -i 指定
# 或写进 ~/.ssh/config(见修复 5),一劳永逸

修复 3:权限——免密失败最常见的真凶

# === SSH 的安全铁律:密钥相关的文件/目录,
#     绝不能被属主以外的人写 ===
# 一旦权限太松,SSH 宁可拒绝免密、也不冒险。

# === 服务器端,正确的权限 ===
$ chmod 700 ~/.ssh
# 700:只有属主能读写进入。组和其他人一点权限都没有。
$ chmod 600 ~/.ssh/authorized_keys
# 600:只有属主能读写。
$ chown -R $(whoami):$(whoami) ~/.ssh
# 属主也必须是你本人

# === 本地端,私钥的权限同样严格 ===
$ chmod 600 ~/.ssh/id_ed25519
# 私钥如果能被别人读,ssh 会报:
#   "UNPROTECTED PRIVATE KEY FILE!" 并拒绝使用它

# === 一张权限对照表(记牢)===
# ~/.ssh                 700  (drwx------)
# ~/.ssh/authorized_keys 600  (-rw-------)
# ~/.ssh/id_ed25519      600  私钥
# ~/.ssh/id_ed25519.pub  644  公钥(公开的,可以宽点)
# ~/.ssh/known_hosts     644
# ~/.ssh/config          600

# === 还有一个隐蔽点:家目录本身的权限 ===
# 如果你的【家目录】对别人可写(比如 777),
# SSH 同样会拒绝免密 —— 因为别人能写家目录,
# 就能动里面的 .ssh。
$ chmod 755 ~          # 家目录别给 group/others 写权限
$ ls -ld ~

# === 出问题时,服务器的 /var/log/secure 最诚实 ===
$ tail -f /var/log/secure
# 一边在另一个窗口尝试登录,一边看这里,
# "bad ownership or modes" 之类的报错会直接告诉你
# 哪个文件权限不对。

修复 4:用 ssh -v 把握手过程看清楚

# === 免密不生效,最有力的排查工具:ssh -v ===
$ ssh -v root@server
# -v 输出调试信息,-vv / -vvv 更详细
# 重点看这几行:

# 1. 客户端有没有"递出"你的公钥
debug1: Offering public key: /home/me/.ssh/id_ed25519
# 没有这行 -> 客户端压根没用这把私钥
#   (私钥名字非默认、或权限不对被 ssh 跳过了)

# 2. 服务器认不认这把公钥
debug1: Server accepts key: ...        <- 认了,会免密
# 或
debug1: Authentications that can continue: password
#   -> 服务器没认,转回密码。
#      多半是 authorized_keys 里没有这把公钥、
#      或权限不对被服务器拒了。

# === 服务器端开 sshd 调试日志,看得更透 ===
# 临时把 sshd 日志级别调高:
$ vim /etc/ssh/sshd_config
LogLevel DEBUG1
$ systemctl restart sshd
$ tail -f /var/log/secure     # 现在信息详细多了

# === 一个排查清单(免密不生效,逐条核对)===
# □ 公钥确实在服务器的 ~/.ssh/authorized_keys 里
# □ .ssh 目录 700,authorized_keys 600
# □ 家目录没有对 group/others 开放写权限
# □ 属主是登录用户本人
# □ 本地私钥 600,且和服务器上的公钥是【配对】的
# □ sshd_config 里 PubkeyAuthentication 是 yes
# □ 用的私钥文件名对(非默认名要 -i 指定)
# 这几条逐一过一遍,免密问题基本都能解决。

修复 5:用 ~/.ssh/config 管理多台服务器

# === 痛点:服务器一多,每次敲长长的 ssh 命令很烦 ===
# ssh user@1.2.3.4 -p 2222 -i ~/.ssh/xxx_key ...

# === 解法:在本地写一个 ~/.ssh/config ===
$ cat ~/.ssh/config
Host prod
    HostName 203.0.113.10
    User appuser
    Port 2222
    IdentityFile ~/.ssh/id_ed25519_prod

Host db
    HostName 203.0.113.20
    User root
    IdentityFile ~/.ssh/id_ed25519
    # 通过 prod 这台跳板机再连内网的 db
    ProxyJump prod

# === 配好之后,登录简化成一个词 ===
$ ssh prod          # 等价于一长串参数
$ ssh db            # 还会自动经 prod 跳板
$ scp file prod:/tmp/    # scp 也能直接用这个别名

# === config 文件本身权限要 600 ===
$ chmod 600 ~/.ssh/config

# === 几个实用的 config 选项 ===
# ServerAliveInterval 60   每 60 秒发个心跳,防连接被掐断
# IdentitiesOnly yes       只用指定的 IdentityFile,
#                          别把 .ssh 下所有私钥都试一遍
# ★ 如果你有很多私钥,不加 IdentitiesOnly,
#   ssh 会逐个试,试多了可能触发服务器的"认证失败次数"
#   限制,反而连不上。

# === ssh-agent:私钥设了 passphrase 又不想反复输 ===
$ eval $(ssh-agent)              # 启动 agent
$ ssh-add ~/.ssh/id_ed25519_prod # 把私钥加进去,输一次口令
# 之后这个会话里再 ssh 就不用反复输 passphrase 了

修复 6:SSH 安全加固——服务器的第一道门

# === SSH 是服务器最暴露的入口,必须加固 ===
# 配置文件:/etc/ssh/sshd_config
$ vim /etc/ssh/sshd_config

# === 1. 禁止 root 直接 SSH 登录 ===
PermitRootLogin no
# root 是所有攻击者第一个会试的用户名。
# 改成:用普通用户登录,需要时再 sudo 提权。

# === 2. 禁用密码登录,只允许密钥 ===
PasswordAuthentication no
PubkeyAuthentication yes
# ★ 这一步把"暴力破解密码"这整类攻击直接清零。
# 前提:你必须先确认密钥登录是通的,否则会把自己锁外面!

# === 3. 改掉默认 22 端口 ===
Port 2222
# 不能真正防黑客,但能挡掉绝大多数扫 22 端口的
# 自动化脚本,日志会清净非常多。

# === 4. 限制能登录的用户 ===
AllowUsers appuser deployer
# 只有白名单里的用户能 SSH,其他一律拒绝

# === 5. 收紧一些参数 ===
MaxAuthTries 3            # 一次连接最多试 3 次认证
LoginGraceTime 30         # 30 秒内没认证完就断开
ClientAliveInterval 300   # 空闲超时

# === 改完:先测试配置语法,再重启 ===
$ sshd -t                 # -t 检查配置文件有没有语法错
$ systemctl restart sshd

# ★★★ 黄金法则:改 sshd 配置时,
#     【保持当前这个 SSH 会话别关】,
#     另开一个新窗口测试能不能登录成功。
#     确认新窗口能进,再关旧窗口。
#     否则配置一改错,你就把自己反锁在门外了。

# === 6. 装 fail2ban,自动封禁暴力破解的 IP ===
$ yum install -y fail2ban
$ systemctl enable --now fail2ban
# 它会盯着 /var/log/secure,发现某个 IP 反复
# 登录失败,就自动用防火墙把它封掉一段时间。
$ fail2ban-client status sshd     # 看封了哪些 IP

命令速查

需求                        命令
=============================================================
生成密钥对                  ssh-keygen -t ed25519 -C "注释"
拷贝公钥(自动设权限)      ssh-copy-id -i 公钥 user@host
调试免密握手过程            ssh -v / -vvv user@host
看服务器认证日志            tail -f /var/log/secure
.ssh 目录权限               chmod 700 ~/.ssh
authorized_keys 权限        chmod 600 ~/.ssh/authorized_keys
私钥权限                    chmod 600 ~/.ssh/id_ed25519
指定私钥登录                ssh -i 私钥文件 user@host
检查 sshd 配置语法          sshd -t
重启 sshd                   systemctl restart sshd
把私钥加进 agent            ssh-add ~/.ssh/xxx
看 fail2ban 封禁情况        fail2ban-client status sshd

加固要点:禁 root 登录 / 禁密码登录 / 改端口
         / 限制 AllowUsers / 装 fail2ban

口诀:免密不生效 -> ssh -v 看握手 -> /var/log/secure 看真因
      多半是权限太松,记住 .ssh 700 keys 600

避坑清单

  1. 免密登录基于公私钥,私钥留本地绝不外传,公钥拷到服务器 authorized_keys
  2. 配免密优先用 ssh-copy-id,它会顺手把目录和文件权限设对,避开权限坑
  3. 免密失败最常见的真凶是权限太松,.ssh 要 700,authorized_keys 要 600
  4. 家目录本身若对 group/others 可写,SSH 同样拒绝免密,家目录别给写权限
  5. 本地私钥必须 600,权限太松 ssh 会报 UNPROTECTED PRIVATE KEY 并拒用
  6. 免密不生效用 ssh -v 看握手,分清是客户端没递公钥还是服务器没认
  7. 服务器 /var/log/secure 最诚实,bad ownership or modes 直接点出权限问题
  8. 私钥文件名非默认时 ssh 不会自动用,要 -i 指定或写进 ~/.ssh/config
  9. 改 sshd 配置务必保留当前会话、另开窗口测试,确认能登再关旧窗口
  10. SSH 加固:禁 root 登录、禁密码只留密钥、改默认端口、装 fail2ban 封暴破

总结

这次 SSH 免密登录的排查,表面上是个再小不过的问题——不就是配个免密嘛,可它却结结实实地教了我一课,让我重新理解了 SSH 这套机制背后的设计哲学。出问题的时候我特别困惑:流程我一步没漏,密钥生成了,公钥也拷过去了,公钥的内容我对着屏幕一个字符一个字符地核验过,确认没有贴错、没有多出空格或换行,可它就是不免密,每次登录都固执地弹出密码提示。我把全部注意力都放在了"公钥内容对不对"上,却完全没想到,问题压根不在公钥本身,而在那个存放公钥的 .ssh 目录的权限上。直到我用 ssh -v 看清了握手过程、又登上服务器翻到 /var/log/secure 里那句 bad ownership or modes,我才恍然大悟。这就引出了我想牢牢记住的、关于 SSH 的第一条核心认知:SSH 对密钥相关文件的权限,有一条近乎偏执的安全铁律——存放密钥的目录和文件,绝对不能被属主以外的任何人写,一旦权限太松,SSH 宁可拒绝你免密、把你打回去输密码,也绝不冒这个险。这个设计初看有点不近人情,可你顺着它的逻辑想一遍就会明白它的良苦用心:authorized_keys 这个文件的作用,是登记"哪些公钥可以登录这个账号",如果这个文件、或者它所在的 .ssh 目录、甚至再往上家目录,能够被别人写,那就意味着任何一个别的用户都能悄悄往里追加一把他自己的公钥,从而堂而皇之地登录你的账号。所以 SSH 对权限的较真,根本不是吹毛求疵,而是它整个安全模型的基石——它必须确保,那个决定"谁能进来"的名单,只有你自己能改。理解了这一点,.ssh 目录必须是 700、authorized_keys 必须是 600、本地私钥也必须是 600 这一串规矩,就不再是需要死记硬背的教条,而是一个能自己推导出来的、合乎逻辑的结论。也正因为权限这道关如此关键又如此容易被忽略,我才特别推荐用 ssh-copy-id 来拷公钥,因为它会在追加公钥的同时,顺手把目录和文件的权限都设到正确的值,等于替你自动绕开了这个最常见的坑。这次排查的第二个收获,是 ssh -v 这个调试利器。免密为什么不生效,光靠猜是猜不出来的,而 ssh -v 会把客户端和服务器握手的全过程摊开给你看,让你能精准地把问题切成两半:如果调试信息里连"Offering public key"这一行都没有,说明客户端根本没有把你的私钥递出去,问题在本地,多半是私钥文件名不是默认名、或者私钥权限不对被 ssh 自己跳过了;如果客户端递了、但服务器没认、直接转回了密码认证,那问题就在服务器端,要么是 authorized_keys 里根本没有这把公钥,要么就是权限太松被服务器拒了。这一刀切下去,排查范围立刻就清晰了。最后,这次"配免密"的小事,也顺势推着我把整台服务器的 SSH 安全加固做了一遍,因为我意识到,SSH 是一台服务器暴露在公网上最显眼的那扇门,几乎每时每刻都有自动化脚本在扫它、在试它的弱口令。加固的几条核心动作其实都不复杂:禁止 root 直接登录,因为 root 是所有攻击者第一个会去试的用户名;关掉密码认证、只保留密钥认证,这一步等于把"暴力破解密码"这一整类攻击彻底清零;把默认的 22 端口换掉,虽然挡不住真正有针对性的黑客,却能让满世界扫 22 端口的自动化脚本扑个空,日志会清净得多;再装一个 fail2ban,让它自动盯着认证日志,发现某个 IP 反复失败就用防火墙把它封掉。但在做这一切的时候,有一条黄金法则我必须时刻提醒自己——改任何 sshd 配置,都一定要保留住当前这个还开着的 SSH 会话,另外开一个新窗口去测试能不能成功登录,确认新窗口进得来,再去关那个旧窗口;因为一旦配置改错了、又把旧会话关了,你就会把自己彻彻底底地反锁在服务器门外。这次从一个小小的免密失败出发,我最终收获的,是对 SSH"为什么这样设计"的理解,以及一套"出了问题该怎么一步步看清真相"的排查方法——而这,远比单纯记住几条配置命令,要值钱得多。

—— 别看了 · 2026
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理 邮箱1846861578@qq.com。
Linux教程

Permission denied 到底差在哪:一次 Linux 文件权限排查复盘

2026-5-20 17:31:39

Linux教程

端口明明开了却连不上:一次 Linux 防火墙排查复盘

2026-5-20 17:38:23

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索