每天在终端里敲ssh user@192.168.1.100,然后老老实实输密码,一天反复十几次,偶尔还得处理scp传到一半断掉重来的情况。后来开始写自动化脚本、用 Ansible 批量下发配置,密码认证彻底成了瓶颈——脚本一跑起来就卡在交互式输入那里,要么用 expect 蛋疼地匹配提示符,要么就得把密码硬编码进配置文件。无论哪种,体验都很糟糕。SSH 免密登录解决的就是这个问题:把"每次验证身份"变成"首次信任、之后畅通",同时安全性并不比密码登录差,甚至更好。这篇文章我基于自己在 win10 和多家 Linux 发行版上的实操经历,把两端配置、常见坑、批量管理的完整思路都铺开讲一遍,希望对正在折腾这件事的你有点帮助。
1. 折腾免密登录之前,先想清楚要解决什么
1.1 每天输密码的日子,到底输在哪了
在 Windows 10 上装个 OpenSSH 客户端非常简单,系统从 1809 版本开始就自带了ssh.exe,不需要额外安装任何东西。所以很多人的日常工作流是:打开 PowerShell 或 Windows Terminal,敲 ssh 命令连上 Linux 服务器,输入密码,开始干活。单次操作也就几秒钟,但一天下来,这些"几秒钟"会被无限放大。
更麻烦的是,当操作对象变成一批服务器时,密码认证的短板就极其明显。比如我临时要检查 10 台机器的磁盘空间,用脚本循环 ssh 过去执行df -h,密码认证就会让脚本卡死。你可能说可以用sshpass或者expect,但这类工具需要额外安装,而且把密码以明文形式写在脚本或命令行参数里,本身就是个安全隐患。免密登录的真正价值不是"少打几个字",而是让非交互式场景变得可用,让自动化工具链(Ansible、rsync、crontab 定时任务)不再受制于人工输密码。
1.2 免密登录不是无脑配置,它有自己的适用范围
这里要先泼一盆冷水:免密登录不是所有场景都该用。它的前提是"客户端身份可信"。你自己家里的电脑、公司的跳板机、云上的管理机,这类你能完全控制物理和系统安全的设备,非常适合做免密。但如果你在一台公共电脑上配置免密,那等于把进入服务器的钥匙留给了所有能登录这台公共电脑的人,风险很高。
适用场景包括:
- 个人开发机到测试服务器,减少日常操作摩擦。
- 跳板机到内网服务器,配合 SSH Agent 做转发。
- CI/CD 流水线中,构建机到部署目标机的自动发布。
- 定时脚本、rsync 备份、日志采集等无人值守任务。
需要谨慎的场景:
- 公网暴露的高权限服务器,不建议设置免密,或者至少限制来源 IP。
- 多人共用的机器、临时工位电脑、云桌面,不建议配置。
- 跨安全域跳转时,需要评估私钥落入他人手里的可能性。
1.3 我这次配置所采用的环境约定
为了后面叙述不乱,先交代一下环境。客户端我以 Windows 10 专业版 22H2 为主,Linux 发行版覆盖了 Ubuntu 22.04 LTS、Debian 12、CentOS Stream 9、龙蜥 OS 8 这几种。win10 端的 OpenSSH 客户端版本是 8.x,Linux 端 sshd 版本有 OpenSSH 8.9p1 和 9.x 等。不同版本之间密钥算法兼容性有些差异,后面会专门提到。整体流程在多数主流发行版上通用,如果你用的是其他版本,操作基本一致,只是个别配置文件路径和命令包名会有差别。
2. 免密登录背后的公钥认证流程
2.1 用"锁和钥匙"来理解公私钥
很多第一次接触免密登录的人,容易把"公钥"和"私钥"搞混。换个角度讲:公钥是一把锁,私钥是这把锁唯一的钥匙。你把锁(公钥)装到服务器的门上,钥匙(私钥)留在自己的客户端手里。别人看到了你的锁也没用,因为他没有对应的钥匙;钥匙丢了就比较麻烦,捡到钥匙的人能打开所有装过这把锁的门。所以私钥文件的权限必须严格控制,这就是后文反复强调权限的原因。
服务器只保存公钥,客户端每次连接时证明自己持有私钥。这个设计的好处是:就算服务器被入侵,攻击者拿到的是公钥,无法反推出私钥;就算公钥在传输过程中被人截获,也一样没用。所以把公钥内容随便发给别人没问题,私钥则必须当作比密码更重要的秘密来保护。
2.2 握手过程里发生了什么
SSH 公钥认证的完整流程大致是:客户端发起连接后,服务器发送一个随机挑战值;客户端用私钥对这个挑战值做签名;服务器拿到签名后用自己保存的公钥去验签,验签成功就确认客户端持有对应私钥,建立会话。
整个过程不传输私钥本身,也不需要用密码登录。这也是为什么免密登录叫做"公钥认证"而不是"无认证"——它的认证强度通常高于密码。服务器端在验证签名通过后,还会检查是否存在~/.ssh/authorized_keys文件中的对应公钥条目,以及客户端 IP、用户等信息是否被允许。严格模式下,文件属主和权限也会被检查,这就为后面的权限坑埋下了伏笔。
2.3 免密不等于不安全,前提是管好私钥
说到底,密码认证和公钥认证的区别是:密码是"你知道什么"(something you know),私钥是"你拥有什么"(something you have)。公钥认证更接近物理世界的钥匙,不会因为某次通信被监听而泄露。但前提是私钥不能丢。为了提升安全性,可以在生成密钥时设置 passphrase(口令短语),相当于给私钥文件再加一道加密。这样即使别人拷走了你的私钥文件,没有 passphrase 也用不了。日常使用中,可以用 SSH Agent 把私钥加载进内存,这样每次连接时就不需要重复输入 passphrase 了。这个组合既方便又不牺牲安全,我强烈建议在生产环境使用。
3. win10 作为客户端:生成密钥、部署公钥到 Linux 的完整操作
3.1 先确认 win10 自带的 OpenSSH 是否可用
很多教程一上来就让人下载 Putty 或第三方工具,实际上 win10 自带的 OpenSSH 客户端已经很够用了。打开 PowerShell,输入:
ssh -V能看到类似于OpenSSH_for_Windows_8.1p1, LibreSSL 3.0.2的输出就说明可用。如果提示找不到命令,可以去设置 → 应用 → 可选功能 → 添加可选功能,找到 OpenSSH 客户端安装。装了之后重启终端就行。
这里我推荐直接用 Windows Terminal 或者 PowerShell 7 来跑 ssh 相关命令,老款 conhost 对 SSH 交互式终端支持不是很好,遇到全屏程序(比如 vim、htop)时显示容易出问题。
3.2 生成密钥对:选 ed25519 还是 RSA
在 win10 的 PowerShell 中执行:
ssh-keygen -t ed25519 -C "win10-dev-key" -f $env:USERPROFILE\.ssh\id_ed25519参数说明:
-t ed25519:指定密钥算法类型。Ed25519 是目前综合推荐的选择,密钥短、速度快、安全性高。但如果你的 Linux 服务器版本较老(比如 CentOS 7 早期、Ubuntu 16.04 之前),sshd 可能不支持 Ed25519,那就改用-t rsa -b 4096。-C "win10-dev-key":添加注释,用于标识这枚密钥的来源或用途。建议写清楚是哪台机器、给谁用的,后面管理一堆公钥时会省很多事。-f:指定保存路径。Windows 默认位置是C:\Users\你的用户名\.ssh\id_ed25519,我一般会显式指定,避免密钥放到默认之外的路径后 ssh 找不到。
执行后它会提示设置 passphrase。本地开发机建议直接留空(按两次回车)即可,因为日常使用输 passphrase 也很烦;生产环境或公司电脑,强烈建议设置一个 passphrase,然后用 ssh-agent 来记住它。
生成完成后,.ssh目录下会多出两个文件:id_ed25519(私钥)和id_ed25519.pub(公钥)。私钥文件在 Windows 资源管理器里可能看不到,因为它是隐藏文件。如果想快速查看公钥内容:
cat $env:USERPROFILE\.ssh\id_ed25519.pub3.3 把公钥部署到 Linux 服务器的几种方式
这是整个流程里最容易让人懵的一步。win10 没有ssh-copy-id这个命令,不过有更好的替代方案。
第一种,直接在 PowerShell 里用管道把公钥内容追加到服务器的authorized_keys文件中:
type $env:USERPROFILE\.ssh\id_ed25519.pub | ssh root@192.168.1.100 "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"这条命令会让 ssh 先提示你输入一次密码,输入正确后,它会自动创建.ssh目录并把公钥追加进去。注意替换root@192.168.1.100为你的实际用户名和 IP。如果服务器上已经存在authorized_keys文件,上面命令会用>>追加而不是覆盖,不会影响已有公钥。
第二种,如果公钥内容比较多或不喜欢用管道,就手动复制。先在本地打印公钥内容,然后登录服务器用 vim 或 nano 编辑~/.ssh/authorized_keys,把内容粘贴到新的一行。这种方式的缺点是容易漏掉结尾换行,导致两行公钥粘在一起,后文排查会讲这个坑。
第三种,只适用于 Linux 客户端,但值得提一下:如果你手上已经有一台 Linux 管理机,也可以把公钥先传到那台机器上,然后用ssh-copy-id批量分发过去。win10 客户端本身没有这个命令,但可以通过 Git Bash 或 Cygwin 环境间接调用,我个人不太推荐为了一个命令去装全套模拟环境。
3.4 验证免密登录,注意 known_hosts 首次确认
部署完公钥后,在 win10 的 PowerShell 里执行:
ssh user@192.168.1.100如果没有设置 passphrase,正常情况下会直接进入服务器的 shell,不再提示密码。如果服务器之前从未被连接过,会出现一段 host key 确认提示:
The authenticity of host '192.168.1.100' can't be established. ED25519 key fingerprint is SHA256:xxx. Are you sure you want to continue connecting (yes/no)?这个提示是 SSH 客户端在确认你连接的是正确的服务器,防止中间人攻击。输入yes确认后会写入known_hosts文件,以后不会再提示。如果你不确定服务器的指纹是否真实,可以用ssh-keyscan或手动登录一次获取指纹进行比对,但日常内网环境下,第一次连接确认 host key 信息后也没太大问题。
如果到这里发现仍然要密码,别急着怀疑人生,八成问题出在 Linux 服务器端的文件权限上,进入下一章。
4. Linux 服务器端 authorized_keys:权限就是免密登录的命门
4.1 .ssh 目录和 authorized_keys 文件的权限要求
之前帮同事排查过一次,公钥明明已经写进authorized_keys了,还是一直提示密码认证。后来登录服务器一看,.ssh目录权限是755,authorized_keys文件权限是644。这就是典型的权限地狱。
OpenSSH 出于安全考虑,默认启用了 StrictModes,如果~/.ssh或~/.ssh/authorized_keys的权限过于宽松,sshd 会认为这些文件可能被其他用户篡改,直接拒绝使用它们做公钥认证,然后退回到密码认证。严格来说,要求的权限是:
- 用户家目录本身不能是 group 或 others 可写的,通常是
755或700。 ~/.ssh目录权限应为700,只有属主能读写执行。~/.ssh/authorized_keys文件权限应为600,只有属主能读写。
执行以下命令修复:
chmod 700 ~/.ssh chmod 600 ~/.ssh/authorized_keys chown -R $(whoami):$(whoami) ~/.ssh如果是用 root 用户配置,没有chown问题;但如果你是切换到某个普通用户,注意authorized_keys文件的属主必须是你登录时那个用户。比如你从 win10 用ubuntu用户登录,公钥就必须写在/home/ubuntu/.ssh/authorized_keys,不能写到 root 的目录下。
4.2 权限不对时,怎么从日志里看出原因
如果你配置完免密登录仍然要输密码,第一件事不是去改 sshd 配置,而是看服务端日志。在 Ubuntu/Debian 上:
sudo journalctl -u ssh -f在 CentOS/RHEL/龙蜥 OS 8 上:
sudo tail -f /var/log/secure然后从 win10 那边再发起一次 ssh 连接,观察服务端日志输出。如果权限有问题,日志里会出现:
Authentication refused: bad ownership or modes for file /home/ubuntu/.ssh/authorized_keys看到这句话,基本就是权限问题,按上一节的方法修即可。日志里还可能出现Permission denied (publickey,password),这个表示客户端尝试过公钥但服务端拒绝了,常见原因包括公钥内容不对、权限有问题、sshd 配置禁止了 pubkey 认证。
4.3 sshd_config 里可能挡路的配置项
除了文件权限,/etc/ssh/sshd_config中的几个项目也需要确认:
sudo grep -E "PubkeyAuthentication|AuthorizedKeysFile|PasswordAuthentication|PermitRootLogin" /etc/ssh/sshd_config常见情况:
PubkeyAuthentication yes必须启用,这是公钥认证的总开关。AuthorizedKeysFile .ssh/authorized_keys保持默认即可,不要乱改成绝对路径,否则会到意想不到的地方找公钥。PasswordAuthentication如果设置为no,则禁止密码登录。这个配置也是很多网站在配置完免密后为了安全而开的,但改之前一定要确认公钥认证已经能正常工作,否则一旦两边不匹配,你可能连服务器都进不去了。PermitRootLogin如果设置为prohibit-password或yes,root 用户才能用公钥直接登录。默认很多发行版是prohibit-password,也就是 root 可以用公钥登录但不能用密码登录,这其实是个不错的配置。
修改完sshd_config后需要重启 sshd 服务:
sudo systemctl restart sshd注意:重启服务不会断开现有连接,但如果你改错了配置导致 sshd 起不来,需要格外小心。远程操作时建议先执行sudo sshd -t检查配置语法,确认无误再重启。
4.4 SELinux 和 AppArmor 给免密登录设的额外关卡
CentOS、Rocky Linux、龙蜥 OS 这类基于 RHEL 的发行版,默认启用了 SELinux。即使文件权限正确,SELinux 也可能阻止 sshd 读取用户家目录中的 authorized_keys 文件。特别是当你把.ssh目录从一个地方复制过来,或者手动创建了目录但安全上下文不对时,会遇到奇奇怪怪的问题。
如果权限和配置都检查过了,仍然无法免密登录,可以在服务器上执行:
sudo restorecon -Rv ~/.ssh这是恢复目录的 SELinux 安全上下文。如果之前把.ssh放到非正常路径(比如自定义 home 目录),还需要检查该目录的 SELinux 类型是否为ssh_home_t。用ls -Z ~/.ssh可以查看:
ls -Z ~/.ssh正常输出应为unconfined_u:object_r:ssh_home_t:s0之类,如果显示var_t或其他类型,执行restorecon修正。
AppArmor 在 Ubuntu/Debian 上默认对 sshd 的限制相对宽松,一般不需要额外处理。但如果遇到报错无法读取公钥,也可以顺手查一下/etc/apparmor.d/usr.sbin.sshd规则,虽然这种情况很少。
4.5 一台服务器上放多个公钥,怎么管理不混乱
服务器上的authorized_keys文件支持一行一个公钥,也就是说可以同时放很多人的公钥。没必要给每个人都建单独的账户,除非需要区分权限。我个人的管理习惯是:
- 公钥内容后面必须带注释,标明"哪台机器、哪个用户、什么时候加的"。
- 定期清理不再使用的公钥。
- 手动编辑 authorized_keys 时,使用编辑器写完后确认最后一行有换行。
如果一个用户的authorized_keys文件行数越来越多,可以考虑把不同用途的公钥按行组织,并在上方添加注释行。比如:
# desktop win10 dev ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... win10-dev-key # jump server ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQ... jump-key注意:authorized_keys文件里不能识别#开头的注释行?实际上 OpenSSH 支持在authorized_keys中用#开头作为注释,但要注意别把整行公钥误注释掉。我自己偶尔会见到有人在文件里写了两个公钥但中间没有换行,结果两个公钥连成一行,导致两个都验证失败。遇到这种情况,把文件拆成两行即可。
5. 反过来:Linux 免密登录 win10 的配置,实现双向互通
5.1 在 win10 上安装并启动 OpenSSH Server
标题既然是"在 win10 和 Linux 上配置 SSH 免密登录",那么双向互通也是绕不开的需求。比如你有一台 Linux 服务器,想直接连到 win10 机器上执行 Windows 命令,或者在 win10 上跑自动化脚本,就需要在 win10 端装 OpenSSH Server。
安装路径:设置 → 应用 → 可选功能 → 添加可选功能,找到"OpenSSH 服务器",点击安装。安装完成后,用管理员身份打开 PowerShell,执行:
Start-Service sshd Set-Service -Name sshd -StartupType Automatic第一条命令启动 sshd 服务,第二条设置开机自启。然后再查一下防火墙规则:
Get-NetFirewallRule -Name *ssh*正常情况下安装 OpenSSH Server 时会自动创建两条防火墙入站规则,允许 22 端口访问。如果没有,手动添加:
New-NetFirewallRule -Name sshd -DisplayName 'OpenSSH Server (sshd)' -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 225.2 把 Linux 的公钥部署到 win10 的 authorized_keys
win10 的 OpenSSH Server 是基于 OpenSSH for Windows 的移植版本,认证逻辑和 Linux 基本一致。因此,在 Linux 上生成密钥对:
ssh-keygen -t ed25519 -C "linux-manage"然后查看公钥:
cat ~/.ssh/id_ed25519.pub接下来把公钥放到 win10 上。这一步有个大坑:win10 的 OpenSSH Server 默认配置中,管理员组成员的公钥文件路径不是%USERPROFILE%\.ssh\authorized_keys,而是C:\ProgramData\ssh\administrators_authorized_keys。这个文件需要手动创建,并且权限必须设置为只允许 Administrators 组和 SYSTEM 访问,否则 sshd 会拒绝使用这个文件。
我在第一次配置时就踩了这个坑:把公钥写到了C:\Users\admin\.ssh\authorized_keys,结果 Linux 连过来一直提示Permission denied (publickey)。后来看了官方文档才明白,win10 的 sshd_config 里面有这么一段:
Match Group administrators AuthorizedKeysFile __PROGRAMDATA__/ssh/administrators_authorized_keys也就是说,管理员用户登录时,sshd 不会去用户目录下找 authorized_keys,而是去C:\ProgramData\ssh\administrators_authorized_keys找。
所以配置步骤是:
- 在 win10 上以管理员身份打开 PowerShell,创建目录和文件:
mkdir $env:ProgramData\ssh notepad $env:ProgramData\ssh\administrators_authorized_keys- 把 Linux 的公钥粘贴进去,保存。
- 修正文件权限,让 sshd 能读取。注意不要使用普通方式直接赋予 Everyone 权限,否则 sshd 也会因为权限过宽拒绝。建议用 icacls:
icacls $env:ProgramData\ssh\administrators_authorized_keys /inheritance:r icacls $env:ProgramData\ssh\administrators_authorized_keys /grant "SYSTEM":F icacls $env:ProgramData\ssh\administrators_authorized_keys /grant "Administrators":F如果你登录 win10 用的是非管理员组的普通用户,那么公钥就放到C:\Users\用户名\.ssh\authorized_keys,权限要求相对宽松一些,但ssh的日志会告诉你具体去哪里找。
- 重启 sshd:
Restart-Service sshd然后回到 Linux 上测试:
ssh administrator@192.168.1.50如果没提示密码就进去了,说明双向免密配置成功。
5.3 win10 OpenSSH Server 的默认 Shell 设置
连上 win10 之后默认打开的是 Windows CMD,不是 PowerShell。如果你更习惯 PowerShell,可以在 win10 上修改注册表或者用New-ItemProperty设置默认 shell:
New-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShell -Value "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -PropertyType String -Force修改后需要重启 sshd。如果不设置,默认 CMD 也能用,只是部分命令不熟悉的话容易蒙。
5.4 Linux 连 win10 时的文件编码与路径坑
Linux 下的scp可以直接拷贝文件到 win10,但注意 Windows 路径分隔符和权限。比如:
scp ./test.txt administrator@192.168.1.50:C:/Users/administrator/Desktop/路径中尽量使用正斜杠,避免反斜杠被 shell 转义。另外,win10 的 OpenSSH 默认是 OpenSSH for Windows,和原生 OpenSSH 行为有一定区别,有些配置文件里的指令不支持(比如AuthorizedKeysCommand),遇到报错时留意一下版本兼容性。
6. 排错实战:从一条连接失败到定位根因的完整链路
6.1 先判断问题出在客户端还是服务端
免密登录失败时,先不要一股脑去改配置。我的排查顺序是:
- 在客户端执行
ssh -vvv user@host看详细输出。 - 观察输出中的关键阶段:网络连接是否成功、是否尝试了公钥认证、服务端是否接受公钥、是否退回了密码。
- 根据输出定位是客户端问题还是服务端问题。
-vvv输出非常啰嗦,但里面藏着关键信息。比如:
debug1: Authentications that can continue: publickey,password debug1: Next authentication method: publickey debug1: Offering public key: /home/user/.ssh/id_ed25519 ED25519 SHA256:xxx debug1: Server accepts key: /home/user/.ssh/id_ed25519 ED25519 SHA256:xxx看到Server accepts key之后仍然让你输密码,那大概率是服务端在公钥验证之外还有额外限制(比如权限、用户限制、Match 块)。如果输出里压根没有Offering public key这一行,就说明客户端没有找到合适的私钥,需要检查-i参数或默认密钥路径。
6.2 服务端日志是最终裁判
在服务器上开一个终端,实时跟踪日志,然后从客户端再试一次连接。Ubuntu/Debian 用:
sudo journalctl -u ssh -fCentOS/RHEL 用:
sudo tail -f /var/log/secure日志里如果有Authentication refused: bad ownership or modes,这是权限问题;如果有Failed publickey for user from 192.168.1.20 port 50000 ssh2: RSA SHA256:xxx,说明公钥验证失败,可能是公钥内容不匹配;如果有Connection closed by authenticating user,可能要检查 sshd 是否因为次数过多临时断开了连接。
另外,很多发行版默认开启了MaxAuthTries 6,反复输错或公钥尝试次数过多会被断开。如果客户端配了多个密钥文件,可能触发这个限制。
6.3 高频问题对照表
我在不同环境里遇到并解决过的问题,整理如下:
| 现象 | 可能原因 | 解决方式 |
|---|---|---|
| 连接被拒绝 | sshd 服务未启动、防火墙拦截、端口不对 | Windows 上检查自启动;Linux 用 systemctl status sshd |
| 总是提示密码 | 客户端没提供私钥,或服务端拒绝了公钥 | 排查公钥是否部署、权限是否正确、客户端是否用了正确密钥 |
| Permission denied (publickey) | 服务端关闭了密码认证,公钥又不过 | 从控制台登录检查公钥和权限 |
| Bad owner or permissions | .ssh 或 authorized_keys 权限过宽 | 设置 .ssh 为 700、authorized_keys 为 600,修正属主 |
| no matching key exchange method | 客户端算法与服务端不匹配 | 升级客户端或修改 sshd_config 添加算法 |
| Server accepts key 但还是要密码 | 用户锁了密码,或账户被禁止登录 | 检查 /etc/passwd 中 shell 是否正常,是否被锁定 |
| 第一次连接确认 host key 后仍失败 | known_hosts 记录与实际 host key 冲突 | 用ssh-keygen -R 主机IP清除旧记录 |
| win10 管理员无法公钥登录 | 公钥写入位置不对 | 放到 C:\ProgramData\ssh\administrators_authorized_keys |
6.4 客户端多密钥和 known_hosts 的诡异问题
很多人在同一台 win10 上拥有多个 SSH 密钥:一个连 GitHub,一个连工作服务器,一个连云主机。SSH 客户端默认只会尝试按顺序使用默认路径下的私钥,如果你的私钥不叫id_rsa、id_ed25519,或者不在默认目录,客户端可能压根不会去尝试。这时可以显式指定:
ssh -i C:\Users\me\.ssh\my_custom_key user@192.168.1.100或者更优雅的做法是创建 SSH config 文件,在C:\Users\me\.ssh\config中配置:
Host myserver HostName 192.168.1.100 User ubuntu Port 22 IdentityFile C:\Users\me\.ssh\my_custom_key配置之后,直接ssh myserver就能连接。SSH config 对多主机管理非常有用,后面章节会详细展开。
known_hosts的坑也很常见。当你重装了一次服务器系统,或者服务器换了 host key,客户端会因为 known_hosts 里保存的旧 host key 与当前服务器的 host key 不匹配而拒绝连接,提示类似:
WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!解决方法是清除旧记录:
ssh-keygen -R 192.168.1.100注意,这个命令清除的是 known_hosts 中该主机的记录,不会影响其他主机。重新连接后按提示接受新的 host key 即可。
7. 免密登录进阶:多主机批量管理与自动化
7.1 用 SSH config 把免密登录变成"一键直达"
公钥配置好了之后,免密登录已经能用,但每次都要敲ssh user@192.168.1.100 -p 2222这种长命令还是不够爽。在客户端配置 SSH config 可以解决这个问题。win10 上文件位于C:\Users\你的用户名\.ssh\config,Linux 上位于~/.ssh/config。
一个典型的配置示例:
Host dev-ubuntu HostName 192.168.1.100 User ubuntu Port 22 IdentityFile ~/.ssh/id_ed25519 ServerAliveInterval 60 Host prod-centos HostName 10.0.0.5 User root Port 2222 IdentityFile ~/.ssh/id_rsa_prod第一列Host后面的别名可以随便取,ssh dev-ubuntu就能免密登录到对应主机。ServerAliveInterval是发送心跳包的时间间隔,防止远程会话因为长时间无操作被防火墙断开,我这个参数基本每次都会配。
7.2 批量分发公钥到多台 Linux 服务器
当你手里有几十台服务器,逐台手动粘贴公钥显然不现实。最常见的方式是在一台 Linux 管理机上写好循环脚本,用sshpass提供密码,或者先用密码登录一台,再借助信任关系扩散。
假设你已经在管理机上生成了密钥,现在有一个hosts.txt文件,每行一个 IP,账号统一为root,用sshpass批量执行:
#!/bin/bash while read host; do sshpass -p '你的临时密码' ssh-copy-id -o StrictHostKeyChecking=no root@$host done < hosts.txt注意,sshpass需要单独安装,而且临时密码会暴露在脚本中。更好的做法是先把所有服务器都设置成一个临时密码,然后批量执行完成后立即修改密码,或者配合 CMDB 来传参。如果你已经配好了其中一台服务器的免密,也可以通过ProxyJump跳转来间接管理其他内网机器。
如果你的环境里有 Ansible,那更方便。在 Ansible 的 hosts 清单里配置好所有服务器,然后执行一个 ad-hoc 命令分发公钥:
ansible all -m authorized_key -a "user=root key='{{ lookup('file', '/root/.ssh/id_ed25519.pub') }}'" -k-k会提示输入 SSH 密码,一次性把公钥推送到所有目标机器。这种方式比写循环脚本更规范,也便于后续维护。
7.3 免密登录和 cron 定时任务配合的注意事项
免密登录最常见的一个实际用途就是定时任务。比如每天早上从数据库服务器拉取备份文件到本地:
30 2 * * * rsync -avz --delete backupuser@db-server:/backup/ /local/backup/因为配置了免密登录,rsync 不需要人工干预就能执行。但这里有几个细节:
- 定时任务执行时用的用户是谁,取决于 crontab 所属用户。不要在 root 的 crontab 里使用某个普通用户的私钥,反之亦然。
- 如果私钥设置了 passphrase,cron 环境无法输入,需要先把私钥添加到
ssh-agent,再通过SSH_AUTH_SOCK让子进程继承。这个配置比较麻烦,建议对自动化用的密钥单独生成一对,不设置 passphrase,并严格限制其在服务器上的权限。 - 服务器端可以对特定用户做限制,比如只允许 rsync 或特定命令,防止密钥泄露后造成更大危害。可以在
~/.ssh/authorized_keys中给某个公钥前面加command="/usr/bin/rsync --server ..."前缀,这是另一个进阶话题。
7.4 撤销免密权限,比配置更重要
免密登录的生命周期管理最容易被忽视。员工离职、设备重装、密钥泄露,都需要及时撤销某个公钥的访问权限。在 Linux 服务器上,撤销就是把authorized_keys文件中对应的行删除;在 win10 上,就是删除administrators_authorized_keys或用户目录下authorized_keys中对应的行。
如果你管理的机器多了,手动删除太慢,可以用脚本批量处理。还是 Ansible 最方便:
ansible all -m lineinfile -a "path=/root/.ssh/authorized_keys regexp='my_old_public_key' state=absent"此外,建议定期审计所有服务器上的authorized_keys文件,列出每台机器允许哪些公钥登录。用脚本从所有服务器拉取 authorized_keys 汇总到管理机,和 CMDB 里的备案信息比对,发现陌生公钥就及时清理。
8. 我踩过的一些真实教训,写出来帮你避坑
8.1 别把公钥追加到错误用户的 authorized_keys
有一次我帮同事配置免密,他明明粘贴的公钥没问题,但始终连不上。后来我发现他把公钥追加到了/home/ubuntu/.ssh/authorized_keys,但登录时用的用户名是ec2-user。SSH 认证时使用哪个用户名登录,就去哪个用户的家目录下找 authorized_keys,这个最基本的逻辑很多人会忽略。排查时先确认登录用户名和公钥所在目录是一一对应的。
8.2 换行符和编码问题,在 win10 下格外容易踩
win10 上记事本默认使用 CRLF 换行和 ANSI 编码。如果你在 win10 上编辑公钥文件,然后再手动复制内容到 Linux,可能出现公钥末尾带着\r的情况,导致服务器验签失败。我在 win10 上查看公钥后,复制粘贴到 Linux 的 authorized_keys 时也中过招。解决方法是:在 PowerShell 里用cat输出公钥,然后复制到 Linux 用 vim 编辑时确认没有多余字符;或者直接用上面提到的管道方式,让公钥内容原样传入,避免手动复制引起的换行符问题。
对于authorized_keys文件的末尾换行,也要注意。如果上一个公钥后面没有换行,下一个公钥紧接着追加,两行会连在一起,服务端会把它们当做一个无效的公钥,导致两个公钥都无法认证。每次追加公钥后,用cat -A ~/.ssh/authorized_keys查看文件,行尾应显示$,确保每行完整独立。
8.3 修改 sshd_config 时,永远留一条后路
这是远程管理服务器最重要的一条建议。编辑sshd_config之前,先用sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak做个备份。修改完成之后,先执行sudo sshd -t检查语法,再systemctl reload sshd或者service sshd reload,而不是 restart。虽然两者差异不大,但 reload 更平滑,不会瞬间断开所有连接。
更保险的做法是:在修改密码认证和公钥认证相关的配置时,先保持当前 SSH 会话不断开,另开一个新的终端测试是否能正常连接。如果新连接失败,还能从旧会话里改回配置。这个习惯帮我避免过至少三次把自己锁在服务器外面的尴尬局面。
8.4 密钥轮换要当成定期任务
公钥认证的安全性高度依赖私钥的保密性。长时间不轮换密钥,意味着一旦私钥泄露,攻击者有足够长的利用时间。建议半年或一年轮换一次,尤其在人员变动、设备转交时立即轮换。轮换过程不必太复杂:客户端生成新密钥对,把新公钥追加到服务器 authorized_keys,确认能登录后再删除旧公钥。Ansible 可以同时做这两步,降低操作风险。
我个人在实际操作中的体会是:SSH 免密登录的配置本身并不难,难的是把"信任边界"管理好。凡是配置成免密的机器,都意味着你默认信任持有对应私钥的人。所以在服务器端,尽量不要把所有机器都向同一把私钥开放;可以根据用途拆分成多对密钥,工作电脑一把、自动化任务一把、应急备份一把,互不干扰。这样即使某一把私钥泄露,影响面也可控。希望这篇基于实操的内容能帮你少走一些弯路,真正把 win10 和 Linux 之间的连接变成顺手的事。