Windows到Ubuntu Server密钥免密登录:从原理到实战避坑指南
1. 为什么要把密码登录换成密钥认证先说个我自己的经历。早些年管服务器每天要SSH登录十几台Ubuntu机器密码来回敲手指头都麻了。后来用上了密钥免密登录配合Windows Terminal的标签页基本上一个终端窗口就能管理所有机器。但省事只是最表面的好处真正让我下定决心全面切换的原因是安全性。密码认证有几个天生的问题密码太短容易被爆破太复杂又记不住同一个密码如果多台机器复用一台泄露等于全部沦陷而且密码认证每次登录都要走一次交互流程做自动化脚本的时候特别别扭。密钥认证用的是非对称加密简单说就是一对钥匙一把公开的锁公钥一把私藏的钥匙私钥服务器只认公钥而能打开这把锁的只有你手里的私钥。就算服务器被拖库黑客拿到公钥也推不出私钥你本地私钥不泄露服务器就一直是安全的。另外密钥认证还有一个隐藏优势可以做完全的自动化。运维脚本、rsync同步、git push只要配置好密钥这些操作就能无人值守地跑。我之前在Windows上写批处理脚本往Ubuntu服务器传文件一开始每次都要输入密码导致脚本卡住换成密钥之后整个流程瞬间顺畅了。这套方案适合谁学如果你是刚接触Linux服务器的Windows用户想摆脱输密码的繁琐或者你手上有几台Ubuntu Server想做安全的远程管理、自动化部署再或者你已经被密码爆破骚扰过想立刻加固服务器——这篇文章都适用。我下面会从原理讲到实操再讲到踩坑记录保证你看完能自己完整跑通。2. 原理先行密钥免密登录到底是怎么工作的2.1 非对称加密的通俗理解很多人一听“非对称加密”就觉得高大上其实打个比方就明白了。想象你有一个信箱投信口是敞开的任何人都能往里塞信但只有你手里的钥匙能打开信箱门取信。信箱投信口就是公钥任何人包括服务器都有你的钥匙就是私钥只有你自己有。别人想冒充你取信没有你的钥匙就打不开。SSH密钥认证的原理就是这个。你在Windows本地生成一对密钥把公钥上传到Ubuntu服务器上追加到~/.ssh/authorized_keys文件里。当你SSH登录时服务器会发一个校验请求你的SSH客户端用私钥签名服务器用公钥验证。验证通过直接放行密码都不需要输入。这里有个关键点要搞清楚私钥绝对不能离开你的Windows电脑更不要传给任何人。公钥可以随意分发哪怕贴到论坛上都行。私钥一旦泄露等于别人有了你所有服务器的“万能钥匙”后果不堪设想。2.2 为什么比密码更安全密码认证是“你知道什么”——知道密码就能登录这意味着任何人都可能通过猜测、撞库、钓鱼拿到你的“知识”。而密钥认证是“你拥有什么”——你手里有私钥文件攻击者不仅要偷文件还要破解私钥的加密口令passphrase双重要求让安全性上了个台阶。还有一点密码是每个服务器独立的你管理十台机器就要记十组密码很容易用“一个密码走天下”。密钥则是一对多一把私钥对多台服务器的公钥管理成本低安全性反而高。配合禁止密码登录之后服务器就只剩密钥认证这一条路SSH爆破基本就废了。3. Windows端准备工作从检查客户端到生成密钥3.1 确认Windows自带OpenSSH客户端我在网上看到有人还在到处下载SSH工具其实Windows 101809版本之后和Windows 11都已经内置了OpenSSH客户端不需要额外安装任何软件。你可以先验证一下打开PowerShell或者CMD输入ssh -V如果输出类似OpenSSH_for_Windows_8.6p1, LibreSSL 3.4.3这样的版本信息说明系统自带客户端可以直接用。如果提示“不是内部或外部命令”那可能是系统版本太老或者OpenSSH功能没开启。这时候去“设置 → 应用 → 可选功能 → 添加功能”找到“OpenSSH客户端”安装即可。注意我强烈建议用Windows自带的OpenSSH而不是装什么第三方SSH工具。自带客户端兼容性好还支持后面会讲的~/.ssh/config配置文件功能一点不比第三方差。另外我建议把Windows Terminal用起来。从Windows官方商店可以免费安装它能把SSH会话、PowerShell、CMD统一管理起来配色也舒服。你后面日常操作服务器体验会好很多。3.2 生成密钥对用ed25519还是RSA这是第一个关键决策点。ssh-keygen支持多种密钥类型我推荐优先用Ed25519它比传统RSA更安全、速度更快、密钥更短。你可以这样生成ssh-keygen -t ed25519 -C your_email_or_comment执行后它会问你要保存到哪里默认是C:\Users\你的用户名\.ssh\id_ed25519直接回车用默认路径就好。接着它会让你设置私钥的passphrase口令这个建议一定要设置。很多人觉得设置口令麻烦但换个角度想私钥文件相当于你家的钥匙passphrase相当于给钥匙又加了道锁。就算有人偷走了私钥文件没有passphrase也解不开。如果你实在不想每次登录都输口令可以配合后面的ssh-agent机制来避免重复输具体我后面会说。如果你的环境比较老某些老旧系统和网络设备只支持RSA那就生成RSA密钥注意密钥长度至少3072位推荐4096ssh-keygen -t rsa -b 4096 -C your_email_or_comment生成完成后~/.ssh目录下会出现两个文件id_ed25519私钥绝对不能泄露和id_ed25519.pub公钥可以分发。你可以用type命令查看公钥内容type $env:USERPROFILE\.ssh\id_ed25519.pub输出是一串以ssh-ed25519开头的长字符串后面跟你的注释。这一整串就是要上传到服务器的公钥内容。4. 把公钥安全地送到Ubuntu Server上4.1 Linux下好用的ssh-copy-idWindows怎么替代在Linux或macOS上一键下发公钥的工具叫ssh-copy-id但Windows自带OpenSSH没有这个命令。很多教程到这一步就含糊过去了其实有几种办法我逐个说。最直观的办法手动编辑服务器上的authorized_keys文件。先用密码登录Ubuntu服务器然后mkdir -p ~/.ssh chmod 700 ~/.ssh nano ~/.ssh/authorized_keys把Windows上id_ed25519.pub的内容整个复制过去保存退出。然后设置权限chmod 600 ~/.ssh/authorized_keys这个操作逻辑很简单但有个坑如果.ssh目录已经存在直接mkdir -p不会报错没问题但如果.ssh目录之前被root或者其他用户创建过属主不对会导致密钥认不出来。所以最好执行一下chown -R 你的用户名:你的用户名 ~/.ssh保证目录属主正确。如果你觉得手动复制麻烦我可以分享一个更“优雅”的方式在Windows的PowerShell里用管道直接把公钥内容传给服务器的bash命令。Windows 10的OpenSSH是支持管道的你可以运行type $env:USERPROFILE\.ssh\id_ed25519.pub | ssh 用户名服务器IP mkdir -p ~/.ssh cat ~/.ssh/authorized_keys这个命令会把公钥内容作为标准输入在服务器端追加到authorized_keys文件末尾。注意这里首次连接时会提示确认服务器指纹也就是host key输入yes回车即可。执行完建议紧接着设置好权限ssh 用户名服务器IP chmod 700 ~/.ssh chmod 600 ~/.ssh/authorized_keys4.2 用PEM证书的几个坑网上热搜词里出现“windows 怎么安装pem”我觉得有必要讲一下。有些云厂商比如AWS给你的不是用户名密码而是一个.pem私钥文件。这种文件其实就是OpenSSH私钥的另一种格式PEM编码你不需要“安装”它只需要放到Windows的.ssh目录下然后让它成为Windows能够识别的私钥文件。实际操作中把下载的.pem文件复制到C:\Users\你的用户名\.ssh\下然后在PowerShell里执行ssh -i C:\Users\你的用户名\.ssh\your-key.pem 用户名服务器IP就可以登录。如果报权限错误可能是因为Windows上有权限检查问题——OpenSSH要求私钥文件不能“Everyone”组可读写。解决办法是右键文件 → 属性 → 安全 → 高级把继承的权限全部禁用只保留你自己的完全控制权限。这一步经常有人卡住下文排查清单还会提。4.3 登录后的目录权限检查公钥放好之后强烈建议先检查一下服务器端的关键权限。很多人漏掉这一步结果密钥死活不生效。我一般用这一条命令检查ls -la ~/.ssh正常的输出应该是drwx------ 2 ubuntu ubuntu 4096 Jan 10 10:00 . drwxr-xr-x 6 ubuntu ubuntu 4096 Jan 10 09:50 .. -rw------- 1 ubuntu ubuntu 400 Jan 10 10:00 authorized_keys也就是.ssh目录权限700authorized_keys文件权限600属主是你的登录用户。这个权限要求是OpenSSH的安全底线——只要权限太宽比如644sshd直接拒绝使用这个公钥。这是一个经典坑我后面还会细说。5. 服务端配置修改sshd_config并测试登录5.1 关键配置参数逐条解析公钥放上去之后理论上再执行ssh 用户名服务器IP如果你没设passphrase应该已经可以免密登录了。但为了彻底加固服务器还要修改sshd的配置文件把密码登录关掉。配置文件路径一般是/etc/ssh/sshd_config。修改前先备份这是所有运维操作的铁律sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak.20250110然后编辑配置文件sudo nano /etc/ssh/sshd_config我建议重点关注这几个参数PubkeyAuthentication yes开启公钥认证。绝大多数发行版默认是yes确认一下没被注释就行。PasswordAuthentication no禁用密码认证。这是关掉“后门”的关键改完这个密码登录就废了。PermitRootLogin prohibit-password允许root用户用密钥登录但禁止密码登录。如果你直接用root管理服务器这个配置最合适。如果你的环境比较老不支持prohibit-password这个值也可以用without-password含义相同。AuthorizedKeysFile .ssh/authorized_keys指定公钥文件路径。一般默认即可不用动。强烈提醒在改PasswordAuthentication no之前一定要确保密钥已经配置好并且能成功登录。很多人在这个环节翻车把自己锁在外面只能去VNC或者云控制台改回来。修改完成后测试配置文件的语法然后重载sshdsudo sshd -t sudo systemctl reload sshdsshd -t是检查语法有错误会直接提示。reload是平滑重载不会断开现有连接。有些系统服务名是ssh不是sshd比如Debian/Ubuntu的传统用法用sudo systemctl reload ssh也行。5.2 首次免密测试与常见错误处理现在回到Windows的PowerShell执行ssh 用户名服务器IP如果一切正常你会直接进到服务器的shell中间不再出现密码提示。如果你设置了passphrase它会先让你输入私钥口令但不会再要服务器的登录密码。如果这里出现了问题最常见的就是sign_and_send_pubkey: no mutual signature supported这种情况一般是客户端生成的密钥类型太新服务端认证算法不支持。比如有的老Ubuntu版本OpenSSH版本较低客户端默认用的是ssh-ed25519旧服务端不认识。解决办法有两种要么用RSA密钥要么在客户端指定算法ssh -o PubkeyAcceptedAlgorithmsssh-ed25519 用户名服务器IP如果真的出现了这种兼容性问题更一劳永逸的做法是把服务器端OpenSSH升级到较新版本或者统一改用RSA 4096密钥。5.3 用日志定位服务端拒绝原因如果密钥就是不生效别瞎猜直接看服务端日志。Ubuntu的sshd日志可以通过journalctl查sudo journalctl -u ssh -f或者看认证日志sudo tail -f /var/log/auth.log然后Windows这边再执行一次SSH登录观察日志输出。如果你看到Permission denied (publickey)通常是密钥本身没对上如果看到Authentication refused: bad ownership or modes for directory /home/user/.ssh那是我前面说的权限问题去改权限即可。实操中80%的免密失败都是权限问题剩下的则是公钥内容粘贴的时候丢了字符或者多了空格。这两个方向排查一遍基本都能解决。6. Windows侧的高级玩法用config管理多台服务器6.1 配置文件的语法与路径当你手上有多台Ubuntu服务器时每台都敲ssh 用户名IP确实能忍但加了一堆参数指定端口、指定密钥、指定跳板机之后命令就长得没法看了。这时候就该用~/.ssh/config文件。Windows上路径是C:\Users\你的用户名\.ssh\config这个文件最初可能不存在自己新建一个即可。我可以给一份我常用的模板Host ubuntu-main HostName 192.168.1.100 User ubuntu Port 22 IdentityFile ~/.ssh/id_ed25519 Host ubuntu-backup HostName 192.168.1.101 User ubuntu Port 2222 IdentityFile ~/.ssh/id_rsa_backup然后你在命令行里只要敲ssh ubuntu-main就会自动用ubuntu用户、id_ed25519私钥去连192.168.1.100的22端口。省心省力还不会因为多台机器端口不同而记混。另一个常用配置是跳板机代理跳转。比如你访问内网服务器需要先经过一台跳板机可以在config里这样写Host bastion HostName 公网IP User jumpuser IdentityFile ~/.ssh/id_ed25519 Host internal-server HostName 192.168.10.20 User ubuntu IdentityFile ~/.ssh/id_ed25519 ProxyJump bastion这样ssh internal-server会自动先连跳板机再连内网目标很实用。实操心得Windows下编辑config文件别用记事本容易把编码搞乱。推荐用VS Code它自带一个Remote-SSH插件能直接管理服务器配置文件。你还可以在VS Code里直接编辑远程服务器的文件配合免密登录整个操作丝滑得很。6.2 ssh-agent告别每次输passphrase很多人设置了passphrase之后发现每次登录都还要输一遍觉得不划算。其实可以配合ssh-agent把私钥“缓存”在内存里。思路是第一次把私钥加入agent并输入passphrase之后同一会话窗口内再登录其他机器就不再问密码了。在Windows上启动ssh-agent服务并添加私钥# 启动agent服务执行一次系统自动记住 Start-Service ssh-agent # 添加私钥会提示输入passphrase ssh-add $env:USERPROFILE\.ssh\id_ed25519之后在当前PowerShell窗口里再执行ssh登录OpenSSH会优先从agent里取私钥不会再问passphrase。注意agent可能不跟随每个终端会话自动启动你可以自己查一下服务状态Get-Service ssh-agent | Select Status如果没启动就Set-Service ssh-agent -StartupType Automatic再Start-Service。这里我也踩过坑PowerShell窗口如果关了agent里缓存的密钥就没了除非配置了自动加载。所以日常我习惯在Windows Terminal里先跑一次ssh-add然后整个标签页生命周期里都能免密操作。7. 那些年我们一起踩过的坑排查清单与安全建议7.1 高频问题速查表我自己在帮朋友、同事排查SSH免密问题时发现很多错误高度重复。我整理了一个速查表丢出来供你直接对照。故障现象大概率原因解决思路Permission denied (publickey)公钥没加到authorized_keys或者粘贴错了重新粘贴公钥确认首尾无多余字符bad ownership or modes for ~/.ssh.ssh目录或authorized_keys权限太宽目录改成700文件改成600Bad owner or permissions on C:\Users\xxx/.ssh/configWindows下config文件权限被继承太宽文件属性里禁用继承只保留自己完全控制no matching key exchange method found服务器OpenSSH版本太老指定算法参数或升级服务器OpenSSHHost key verification failedknown_hosts里记录的主机指纹变了比如重装了系统用ssh-keygen -R 主机IP清掉旧的known_hosts记录能免密但每个终端窗口都要输passphraseqssh-agent没运行或私钥没加入启动ssh-agent并用ssh-add添加私钥Could not open a connection to your authentication agentWindows ssh-agent服务没启动Start-Service ssh-agent再ssh-add表格里的第4个“bad ownership”问题在Windows上有个很麻烦的细节即使文件所有者是你如果权限里包含了Authenticated Users这种组OpenSSH照样报错。解决方法是右键config文件 → 属性 → 安全 → 高级 → 禁用继承 → 将继承的权限转换为显式权限后只保留你当前用户不要勾选其他组或用户。7.2 安全性加固的追加建议密钥免密登录搞定了不代表可以高枕无忧。我习惯每次在服务器上完成免密配置后再做这几件事关闭密码登录后再额外做一次验证新开一个终端确认确实不能密码登录了。装死口的告别仪式不能省。配置Fail2ban即使只剩公钥认证也不建议让服务器裸奔。Fail2ban能自动封杀多次登录失败的IP是第二道防线。定期轮换密钥建议每半年到一年换一次。方法是生成新密钥对追加到authorized_keys后先测试测试通过再从文件里删掉旧公钥。关掉你不用的服务端口网上搜“windows 关闭端口号”的教程很多反过来Linux服务器上也要审视哪些端口对外开放。多开一个端口就多一份暴露面。这里面我还想多提一句如果你在Windows上日常使用VS Code的Remote-SSH插件连服务器配置好免密之后插件的连接速度也会快不少因为少了交互式输密码的等待。插件会读取你.ssh/config的配置只要你本机已经能免密VS Code里打开远程文件夹就是秒开。8. 最后一次顺手点到的检查项写到这其实已经把完整的流程走通了。从原理、客户端准备、密钥生成、公钥下发、服务端加固一直到最后的高级配置和排障覆盖了Windows到Ubuntu Server免密登录的全链路。按照我的个人经验最值得反复检查的就是服务器端权限和Windows的config文件权限这两个点。前者决定了公钥能不能被信任后者决定了OpenSSH会不会直接拒绝读取配置。这两个坑一旦踩进去往往可以卡上好几个小时。最后分享一个我自己的小习惯密钥配置完成之后我总会在服务器上留一个没有任何公钥、纯密码登录的新用户作为应急入口当然这个用户的口令也做了强密码加Fail2ban双层防。这不算什么复杂操作但以防哪天密钥出问题、远程解决不了时还能从VNC或者控制台走一条备用路径。这套流程无论你手头是5台机器还是50台机器都照样管用。