SSH免密登录配置与排查:从密钥原理到批量运维实战

SSH免密登录配置与排查:从密钥原理到批量运维实战 做运维这几年要问我哪个技能用得最多、最值得早点吃透我会毫不犹豫说SSH免密登录。它不光是省去输密码那点事更是后面批量管理服务器、写自动化脚本、配置Ansible、同步文件、走Git SSH协议的基础中的基础。前期把免密配置好后期你运维的效率会直接上一个台阶反过来如果哪台机器密钥没配好“明明密钥放了为什么还是连不上”这种问题能卡住你一个下午。这篇我就按自己实际操作的顺序把SSH免密登录的原理、配置过程、批量操作、故障排查和安全加固完整过一遍新手可以照着一步步来老手可以直接跳到故障排查和批量配置部分捡漏。说明一下适用范围这里讲的命令在Linux和macOS终端、以及Windows 10以上的OpenSSH客户端里都能跑通服务端以OpenSSH为主版本在7.0以上基本都适用。整篇没有用那些“企业级神秘技巧”都是日常一台台机器摸出来的经验。1. SSH免密登录的原理一次讲透1.1 公钥和私钥到底是怎么配合工作的很多人第一次配免密时容易误会以为“免密”就是不用管密码了。其实不是SSH免密登录用的是非对称加密里的“签名验证”机制跟单纯消除密码完全是两码事。客户端本地保存的是私钥服务器端保存的是你的公钥。登录时服务器会生成一串随机数据challenge发给客户端客户端用自己的私钥对这个随机数据进行签名再把签名结果返回给服务器服务器拿到签名后用之前存在authorized_keys里的公钥去验证验证通过就确实相信你是私钥的合法持有者直接放行。整个过程私钥始终没有离开客户端也不会在网络上传密码所以它比输密码更安全。要类比的话公钥像一把挂锁私钥是唯一能打开这把锁的钥匙。你把挂锁公钥挂在服务器上之后拿着钥匙私钥来就能开。公钥被人抄走了没关系因为别人没有钥匙照样进不去。这也解释了一个常见问题为什么公钥文件可以到处发而私钥文件必须像银行卡密码一样保护得严严实实。从认证流程上看SSH连接大概分这么几步客户端发起连接服务器发送自己的主机公钥识别服务器身份双方协商加密算法和会话密钥客户端向服务器提供自己私钥对应的公钥信息服务器从authorized_keys里找到匹配项服务器发challenge客户端签名返回服务器验证签名通过登录成功。运行ssh -vvv时你能看到“Offering public key”和“Accepted publickey”这两条关键日志它们分别对应签名前和验证通过后的状态。1.2 免密登录解决的痛点是什么早期管理服务器就是输密码机器数量少还好说机器一多问题就全冒出来了密码记不住、记混、临场输错写进脚本里容易泄露而且批量执行命令时每台都要手动输一次效率非常低。免密登录带来的实际改变是很具体的批量执行命令for ip in $(cat list); do ssh $ip uptime; done 这种循环能直接跑。自动化脚本同步文件rsync -avz 配合免密定时任务里同步日志、备份数据根本不担心卡在密码输入上。配置管理工具落地Ansible、SaltStack这类工具在被管理机上跑任务前提就是控制机能免密登录。Git走SSH协议GitHub、GitLab上配置SSH公钥后git clone、git push都不需要反复输账号密码。说白了免密登录是自动化运维的“第一个台阶”这个台阶不搭好后面全是空的。1.3 密钥登录的边界与其他免密方式SSH免密登录通常指密钥认证但它不是唯一的免密码方案。在大规模集群场景里还有用SSH CA统一签发和吊销证书的证书登录方式在域环境下也有基于GSSAPI的Kerberos认证。但这两种的部署和维护成本都比较高对个人和中小团队来说密钥认证已经能解决绝大部分实际问题配置复杂度也最低。我现在自己维护大概几十台机器全部用密钥认证坚持了几年没出过问题。所以别再纠结要不要上更复杂的东西先把密钥认证玩明白等真有大规模集群需求再考虑证书体系也不迟。2. 从零开始配置SSH免密登录真正配置的时候我一般分三步走生成密钥对、推送公钥、验证登录。每步都有值得注意的细节别直接复制完命令就以为完事了。2.1 生成密钥对算法选择与参数说明生成密钥对的命令很简单但算法我建议直接用Ed25519ssh-keygen -t ed25519 -a 100 -C your_comment参数解释一下-t指定算法。ed25519基于Curve25519曲线安全强度高密钥短公钥就几十字节生成和验证速度快是目前主流推荐。-a是KDF密钥派生函数的迭代次数默认值通常较小手动调高到100能让暴力破解私钥口令更困难。-C只是注释会写到公钥文件末尾建议写成机器名加用途比如ops-vm-01-manage-key方便日后认领。执行后终端会问保存路径默认是~/.ssh/id_ed25519一般直接回车就行。接着会问要不要设置passphrase也就是给私钥再加一层口令保护。我的建议很直接纯内网自动化场景可以留空因为自动化任务需要无交互跑公网服务器或者机器上有敏感数据建议设置passphrase然后配合ssh-agent只输入一次既安全又不用每次敲口令。如果你的远程服务器系统比较老比如CentOS 6或者更旧的OpenSSH版本不支持ed25519就退回RSAssh-keygen -t rsa -b 4096生成完~/.ssh目录下会多出两个文件私钥id_ed25519权限必须600除了当前用户任何人都不能读。公钥id_ed25519.pub权限644即可这个文件内容可以发给任何人、贴到任何服务器上。2.2 用ssh-copy-id一键推送公钥生成完密钥下一步就是把公钥推送到目标服务器的authorized_keys文件里。最稳妥的工具是ssh-copy-id用法ssh-copy-id -i ~/.ssh/id_ed25519.pub userserver_ip这个工具会自动完成三件事连上服务器、把公钥内容附加到~/.ssh/authorized_keys、顺手修复目录和文件权限。首次连接会让你输入目标服务器用户的密码之后连接就不用了。ssh-copy-id在macOS和绝大多数Linux发行版里自带。如果你用的系统没有这个命令比如某些精简版Windows的OpenSSH环境就手动操作下面一节会说。如果在推送时用了非默认的端口命令要加-P参数注意是大写Pssh-copy-id -i ~/.ssh/id_ed25519.pub -P 2222 userserver_ip2.3 手动配置authorized_keys的标准流程有些环境不允许你用ssh-copy-id这时手动配置也不复杂。在目标服务器上切到你要登录的那个用户执行mkdir -p ~/.ssh chmod 700 ~/.ssh touch ~/.ssh/authorized_keys chmod 600 ~/.ssh/authorized_keys echo 公钥内容 ~/.ssh/authorized_keys注意这组操作一定要用你要免密登录的那个用户身份来执行。比如你想免密登录root就在root用户下操作想登录普通用户就切到普通用户下操作。很多排查了半天才发现问题出在“公钥放错用户家目录”上。几个细节容易踩坑authorized_keys里每行放一个公钥有多台机器就依次换行追加。从Windows复制公钥内容时容易带上回车符\r导致服务器端解析失败后面会在故障排查里细说。手动配置完建议用tail -n 2 ~/.ssh/authorized_keys确认内容没有错行、没有串行。2.4 Windows客户端和VSCode场景的配置要点Windows 10 1809以后的系统自带了OpenSSH客户端PowerShell里直接能用ssh命令。它的用户主目录是C:\Users\用户名.ssh操作逻辑和Linux一致只不过有些命令的表现会有差异。我平时在Windows上最常用的其实是Git Bash里面各种Linux命令都齐ssh-copy-id也能正常使用。如果你只装OpenSSH客户端没有ssh-copy-id手动配置就好。另外一个高频场景是VSCode的Remote-SSH插件。这个插件连接远程服务器时一样走密钥认证只要本地~/.ssh下的私钥配置好了VSCode打开远程目录就不会提示输密码。有一个容易混淆的点VSCode里提示“此扩展在此工作区中被禁用因为其被定义为在远程扩展主机中运行”这是扩展归属问题跟免密配置没有关系别把两者扯到一起。Windows下如果偶发密钥权限问题可以通过icacls修复icacls $env:USERPROFILE\.ssh\* /inheritance:r /grant:r $env:USERNAME:(OI)(CI)F这个命令的作用是去掉继承的宽松权限把.ssh目录下的文件权限收拢给当前用户避免OpenSSH for Windows因为权限太开放而拒绝使用密钥。3. 多台服务器批量配置与自检机器少的时候手动推一遍没问题机器一多就要讲究批量方法了。我自己维护的服务器从小几十台开始就摸索出了一套不算优雅但很稳的批量流程。3.1 循环批量推送公钥先把需要配置的IP地址或主机名写进一个文件比如servers.txt每行一个。然后写个简单的for循环for ip in $(cat servers.txt); do echo $ip ssh-copy-id -i ~/.ssh/id_ed25519.pub admin$ip done这个方式虽然每台机器第一次还是要输一次密码但好处是思路清楚、不会漏机器。如果不想一台台输密码可以用expect脚本配合但expect脚本里需要写密码我一般只在一键初始化的时候临时用不建议长期保存这种带密码的脚本。另一种思路是先手动配好第一台“跳板机”然后从跳板机继续向外推送。这种方式在跨网段环境里经常用但要注意跳板机本身的安全性别被攻破后连累整个内网。3.2 避免首次确认提示的自动化技巧第一次ssh连接某个IP时客户端会问Are you sure you want to continue connecting? 这个确认是为了防止中间人攻击。在自动化批量操作时这个交互提示会很碍事可以临时用参数跳过去ssh -o StrictHostKeyCheckingno -o UserKnownHostsFile/dev/null user$ip hostname这里StrictHostKeyCheckingno跳过主机指纹确认UserKnownHostsFile/dev/null表示不把指纹写入known_hosts文件。要注意这种做法只适合临时测试、一次性初始化等场景不建议日常长期开启否则一旦有机器被冒充客户端根本发现不了。更稳妥的做法是在批量操作前先把目标机器的指纹批量写入known_hosts或者把StrictHostKeyChecking设回yes让流程在第一次连接时进行指纹校验。3.3 配置完成后的自检清单批量推完公钥别急着跑自动化。我习惯先做一轮快速自检检查项包括能免密登录ssh userip直接进去不需要密码。known_hosts里没有异常告警。私钥文件权限正确。目标用户的.ssh目录属主是那个用户本身。批量自检可以这样一行搞定for ip in $(cat servers.txt); do timeout 5 ssh -o BatchModeyes -o ConnectTimeout3 user$ip hostname echo $ip OK || echo $ip FAIL doneBatchModeyes很关键它表示禁止交互式输入密码。如果某台机器免密没生效命令会立刻失败而不是卡在那里等你输密码。用这个参数做批量巡检一台机器3秒超时几十台机器一分钟内就能筛出问题机器。4. 故障排查实录常见问题定位配置免密登录这块我踩过的坑几乎都可以写本书了。下面按“出问题概率从高到低”的顺序整理方便你照着查。4.1 权限问题最常见也最容易忽略最大的概率坑是权限问题。OpenSSH对文件权限非常敏感权限不对宁可拒绝公钥认证也不会冒险让你登录。先在本机看私钥权限ls -l ~/.ssh/私钥应该是600公钥644。然后在服务器上看对应用户下的目录权限ls -ld /home/user ls -ld /home/user/.ssh ls -l /home/user/.ssh/authorized_keys要求很严格用户主目录不能太开放最好755或700不能777也不能被组或其他用户可写。~/.ssh目录必须是700。~/.ssh/authorized_keys必须是600或400。文件归属必须是目标用户。遇到chmod 777的马上改回来chmod 700 ~/.ssh chmod 600 ~/.ssh/authorized_keys很多新手问“为什么我把authorized_keys改成了777反而连不上”正是因为SSH觉得“这么开放的文件可能被篡改过”直接不信任它。4.2 authorized_keys内容与属主细节权限没问题后下一个检查点是authorized_keys文件里的内容。先确认公钥是否真的在文件里grep -F 公钥开头的几个字符 ~/.ssh/authorized_keys再检查有没有隐藏的回车符\r尤其是从Windows复制过来的公钥末尾经常带着\r把一行拆成两行导致解析失败。用cat -A看一下行尾cat -A ~/.ssh/authorized_keys | tail如果行尾出现^M就说明有\r用sed清洗一下sed -i s/\r$// ~/.ssh/authorized_keys另外多个公钥之间如果没有换行也会导致全部失效。authorized_keys的标准格式是每行一个公钥开头是ssh-ed25519或ssh-rsa这种算法标识。手动粘贴的时候一定要确认换行。还有一个坑是属主。比如你用root权限把公钥写到了普通用户家目录但文件属主还是root普通用户登录时OpenSSH会认为这个文件不可信拒绝使用。解决办法chown user:user ~/.ssh/authorized_keys ~/.ssh4.3 sshd_config配置项排查如果文件和权限全部正常仍然连不上问题很可能出在服务器端sshd_config配置上。登录服务器后查看这几个关键项PubkeyAuthentication yes RSAAuthentication yes AuthorizedKeysFile .ssh/authorized_keys PasswordAuthentication yes老版本OpenSSH还需要RSAAuthentication新版本一般只关注PubkeyAuthentication。AuthorizedKeysFile要指向正确的路径默认是用户家目录下的.ssh/authorized_keys。修改完配置后先用语法检查再重启服务sudo sshd -t sudo systemctl restart sshd还有一种容易被忽视的情况sshd_config里的Match User或Match Group条件块覆盖了全局配置导致某些用户或用户组被强制禁用公钥登录。如果全局配置看起来没问题一定往下看看Match段。4.4 SELinux、防火墙与网络层干扰RHEL、CentOS、Fedora这类带SELinux的系统就算把权限和配置全部改对也可能因为SELinux上下文不对而拒绝读取authorized_keys。这时候先执行restorecon -R -v ~/.ssh如果还不行去/var/log/audit/audit.log里看有没有selinux阻断记录ausearch -m AVC -ts recent | grep sshd网络层也不能忽略。先确认22端口通不通nc -vz server_ip 22如果端口不通表现为“连接超时”而不是“密码错误”这是和认证失败最大的区别。防火墙规则拦了的话需要先放行22端口或者你的自定义SSH端口。4.5 客户端与Windows环境隐蔽问题排查完服务器还要回头看客户端。如果客户端有多个私钥或者私钥不在默认路径需要用-i参数指定ssh -i ~/.ssh/id_ed25519 -v userserver_ip调试级别-v、-vv、-vvv可以逐级提高信息量。我常用-vvv能直接看到当前正在尝试哪种认证方式、是否读取了私钥、服务器返回了什么结果。Windows还有几个容易踩的隐蔽点VSCode Remote-SSH扩展提示“此扩展在此工作区中被禁用因为其被定义为在远程扩展主机中运行”跟免密配置无关但经常会一起冒出来别混为一谈。OpenSSH Authentication Agent服务没启动时使用带口令的私钥会每次都要求输入passphrase。这时候去Windows服务里把OpenSSH Authentication Agent设为自动并启动。Git Bash和PowerShell对~/.ssh的解析路径不同别把公钥放错位置导致两边互相找不到。还有一个容易混淆的问题ssh执行远程命令时如果连接断开命令还会不会继续跑答案是通常不会。远程终端收到SIGHUP信号后命令会中断。想让命令不被中断应该用nohup或者screen/tmux。这个跟免密配置无关但排查问题的时候经常被一起问出来这里顺手说清楚。4.6 万能调试法-vvv和日志配合最后给一个排查万能套路按这个顺序基本上能覆盖90%的问题客户端执行ssh -vvv userserver_ip把输出贴到终端里。观察有没有“Authentications that can continue: publickey”这类关键词。观察有没有“Offering public key: ...ED25519...”。服务器端另开终端执行journalctl -u sshd -f或者tail -f /var/log/secureRHEL系以及/var/log/auth.logDebian系实时看日志。当看到“Accepted publickey”时说明认证已经通过。日志里几个关键关键词的含义Failed password表示密码阶段失败。Connection closed by authenticating user出现频率极高多半是权限问题或公钥不匹配。Accepted publickey表示公钥登录成功。只要把客户端-vvv输出和服务器端日志对照着看问题基本都能定位到具体环节而不是漫无目的地一项项猜。5. 安全加固与日常维护免密登录配置成功后很多人的状态是“终于不用输密码了收工”。但我建议你再花点时间做安全加固否则免密配置可能成为一把双刃剑。5.1 私钥保护与ssh-agent的正确用法免密之后私钥文件就成了你登录所有机器的“总钥匙”。哪天私钥泄露攻击者等于拿到了你整个服务器矩阵的通行证。所以私钥文件要像一卡通一样对待不随便拷到U盘、不上传公有仓库、不给其他用户任何权限。如果生成了带passphrase的私钥可以用ssh-agent来降低反复输入口令的负担eval $(ssh-agent -s) ssh-add ~/.ssh/id_ed25519执行一次后当前会话里再ssh连接就不会要求输入passphrase了。Git Bash和Linux下都可以这样用。Windows环境则需要确认OpenSSH Authentication Agent服务已经启动。注意ssh-agent转发AgentForwarding建议只在临时需要时才开启。所谓转发就是允许远程服务器“借用”你本地的agent去连接其他机器。一旦管理机被攻破攻击者可能通过agent转发获取你的认证能力去横向连接内网更多机器。默认配置里不要开ForwardAgent确需使用的时候用完立刻关掉。5.2 禁用密码登录的操作顺序免密配好之后不少人会把密码登录直接关掉这是安全加固的正确方向但操作顺序一定要讲究。我的建议顺序是确认至少有一台机器已经能稳定免密登录再开始改配置。修改sshd_config前先备份原文件。修改后先执行sudo sshd -t做语法检查。重启sshd服务但保持当前连接不要断开。新开一个终端测试免密登录确认无误后再关掉之前的连接。关键的加固配置PasswordAuthentication no ChallengeResponseAuthentication no PermitRootLogin prohibit-passwordPermitRootLogin prohibit-password表示允许root用密钥登录但禁止密码登录适合需要root直接管理的场景。如果完全不需要root远程登录可以更严格地设成PermitRootLogin no。修改后记得reload配置sudo systemctl reload sshd这里最大的禁忌就是盲目关掉密码登录后才发现私钥不在手边或者私钥文件损坏结果把自己锁在服务器外只能通过机房或云控制台紧急救援入口处理。5.3 密钥轮换与公钥清理密钥不是配好就能一劳永逸的。我的维护习惯是每半年到一年轮换一次管理机密钥。员工离职时把他机器上的公钥从所有服务器的authorized_keys里移除。定期检查所有authorized_keys删掉不再使用的公钥。批量清理命令可以这么写for ip in $(cat servers.txt); do ssh user$ip sed -i /需要删除的公钥备注/d ~/.ssh/authorized_keys done轮换密钥的步骤我坚持一个原则先推新的验证通过后再删旧的。具体来说新生成一对密钥。把新公钥推送到所有服务器。用新私钥登录验证。确认全部机器都能通过新私钥登录后再删除本地旧私钥并从服务器authorized_keys里移除旧公钥。这个顺序能避免“新密钥还没推完旧密钥已经被删掉”这种把自己锁在门外的尴尬。6. 写在最后几个实操体会临时想到一个小技巧排查时特别好用把ssh-copy-id和-v参数结合ssh-copy-id本身也支持调试输出。遇到反复查不明白的问题不要对着一个命令反复试直接开两个终端一个盯着服务器日志一个本地跑ssh -vvv日志不会骗人问题出现的环节一眼就能看出来。我个人实际使用中还有一个习惯就是每次生成密钥时-C参数一定写成机器名加用途比如ops-vm-01-manage-key。这样半年后你在授权文件里看到一堆公钥时也能迅速认出是谁的、干什么用的清理的时候不会误删。SSH免密登录虽然基础但正因为太基础很多人跳过原理直接复制命令出了故障就开始瞎猜。把这套逻辑和排查路径弄明白后面无论是批量运维、远程开发还是Git操作都会顺畅得多。希望这篇经验能帮你少走一些重复弯路。