我要提问
ARTICLE DETAIL

资讯详情

前沿编程新知与开发实战干货的深度解读。

scp报错bad permissions?SSH密钥权限排查与修复全指南

scp报错bad permissions?SSH密钥权限排查与修复全指南 同事抱着一台Windows笔记本过来说自己的scp命令彻底废了不管往服务器传什么文件远端一律回一句bad permissions。他把Windows这边的文件属性翻了个底朝天甚至把防火墙关掉重试问题纹丝不动。我跑到服务器上看了一眼日志10秒就定位了问题——这个报错根本不是Windows这一侧发出来的是Linux那边把密钥文件拒了。如果你也卡在这个报错上先别急着怀疑系统策略、杀毒软件或者文件权限这篇把整个排查链路和修复方案完整拆给你照做就能解决。1. 报错源头这句话不是Windows说给你的1.1 先搞清楚bad permissions是谁在检查很多人一看到bad permissions下意识就会去看Windows里的文件安全属性右键、属性、安全选项卡来回折腾ACL和用户组折腾半小时毫无进展。原因很简单这条报错压根儿不是Windows输出的。scp的全称是secure copy底层走的是SSH协议。你在Windows命令行敲scp本质上是用本机SSH客户端连上远端Linux服务器然后执行文件复制。服务器端的sshd进程在验证你的身份时如果使用了公钥认证它会非常认真地检查authorized_keys、~/.ssh目录以及用户主目录的权限。OpenSSH在默认配置下开启了StrictModes yes这个选项直译过来就是严格模式意思是如果你的密钥文件或者密钥目录存在任何可能被其他用户篡改的权限漏洞服务器就拒绝使用这个密钥。它宁可让你认证失败也不愿意接受一个安全性无法保证的密钥文件。所以你在客户端看到的bad permissions实际是服务器端sshd在拒绝你的authorized_keys文件。可以这么理解服务器就像一个门禁严格的保安你的公钥放在只能你自己够得着的抽屉里保安才认如果公钥被随便丢在走廊上谁来都能看、都能改保安就认为这张门禁卡不可信直接不让你进。1.2 为什么明明按网上教程配置了还是翻车绝大多数踩坑的人卡在同一个点上公钥内容明明没错authorized_keys里也写了可就是过不去。关键在于文件的权限值。OpenSSH服务端对以下三个地方的安全要求非常苛刻检查对象允许的权限作用用户主目录~不能被group或other写入防止他人在你的主目录里放恶意文件~/.ssh目录一般是700目录可被owner读、写、执行遍历但组和其他人不能碰~/.ssh/authorized_keys600只有owner能读和写其他人一律不能碰问题就出在authorized_keys上。你用scp把公钥从Windows传到Linux服务器时scp默认不会保留Windows端的文件权限属性服务器收到文件后会按照当前用户的umask默认值创建文件。绝大多数Linux发行版的umask是022这意味着新建文件的权限是644也就是-rw-r--r--组用户和其他用户都可以读这个文件。authorized_keys是644恰好撞在OpenSSH的枪口上。服务器一看这个文件大家都能读甚至理论上能被修改立刻拒绝加载里面的公钥然后退回密码认证。如果你再配上服务器端禁止密码登录那就彻底没救了看起来就像密钥明明是对的但就是连不上。1.3 两个容易混淆的报错千万别修错地方还有一个高频误解是有人把bad permissions和另一个报错混为一谈。如果你在Windows本机也遇到过Permissions for key.pem are too open.这个才是Windows端在检查你的私钥文件。意思是你的私钥文件ACL权限太宽松谁都能读OpenSSH客户端出于安全考虑拒绝加载它。别小看这个差异两者排查方向完全相反报错信息实际报错位置原因修复方向bad permissions服务器端sshdauthorized_keys或.ssh目录权限过宽、属主不对登录服务器chmod/chown修权限Permissions for key.pem are too openWindows本机ssh客户端本地私钥文件ACL权限过宽在Windows收窄私钥文件ACLPermission denied (publickey,password)认证阶段公钥不匹配、内容被CRLF污染、密钥类型不支持检查authorized_keys内容和格式所以第一步就很关键先判断你的报错原文到底长什么样。bad permissions这个描述出现在登录密钥验证阶段你的修法应该在服务器端而不是Windows端。2. 完整排查链路从客户端-v日志追到服务端auth.log2.1 复现问题与准备调试环境先交代一下我当时的排查环境和你遇到的场景应该大差不差Windows 10/11自带OpenSSH客户端路径在C:\Windows\System32\OpenSSH\scp.exeLinux服务器是Ubuntu 22.04用户是普通用户ubuntu已经用ssh-keygen生成过密钥公钥也想办法写进了服务器的~/.ssh/authorized_keys执行命令scp -i C:\Users\dev\.ssh\id_ed25519 ubuntuserver:/tmp/test.txt ./结果命令行直接回ubuntuserver: Permission denied (publickey,password).如果配合更高详细级别的输出或者打开详细日志你会看到里面有bad permissions的踪迹。这个报错不是每次都直接打印在默认输出里很多时候默认只显示Permission denied所以需要靠verbose日志挖出真实原因。2.2 客户端日志里能看到什么在Windows的PowerShell或CMD里执行ssh -v -i C:\Users\dev\.ssh\id_ed25519 ubuntuserver-v参数让客户端输出完整的调试日志然后在输出里找几行关键内容debug1: Offering public key: C:\Users\dev\.ssh\id_ed25519 ED25519 SHA256:xxxxxxxx debug1: Authentications that can continue: publickey,password debug1: No more authentication methods to try.注意看客户端确实把公钥递过去了但服务器没有回复Server accepts key而是直接跳到了Authentications that can continue说明服务器明确拒绝了这把密钥。客户端这边能做的只是我offer了但对面不收具体为什么没收要看服务器那边的日志。这一步排查的意义在于把问题边界从客户端切到服务端。如果客户端根本没有发送公钥那是Windows本地SSH配置或者私钥加载的问题如果发送了但被拒那基本可以锁定是服务器端不认可这个key文件。2.3 服务端日志才是真正的“案发现场”登录到Linux服务器上查看SSH服务日志。Ubuntu/Debian系看/var/log/auth.logCentOS/RHEL系看/var/log/secure使用systemd的现代版本也可以直接sudo tail -f /var/log/auth.log在另一个窗口重新发起一次ssh连接日志会立刻刷出关键行Jul 12 10:23:45 server sshd[12345]: Authentication refused: bad ownership or modes for directory /home/ubuntu/.ssh Jul 12 10:23:45 server sshd[12345]: Authentication refused: bad ownership or modes for file /home/ubuntu/.ssh/authorized_keys看到这两行问题就彻底水落石出了。服务器端明确告诉你~/.ssh目录或者authorized_keys文件的权限和属主不对。在CentOS系上你可能会看到sudo tail -f /var/log/secure或者是sudo journalctl -u sshd -f日志内容大同小异同样是bad ownership or modes。2.4 用一条命令确认权限现状定位到日志里的路径之后在服务器上执行stat -c %A %a %U %G %n ~/.ssh ~/.ssh/authorized_keys输出大概率是这个样子drwxr-xr-x 755 ubuntu ubuntu /home/ubuntu/.ssh -rw-r--r-- 644 ubuntu ubuntu /home/ubuntu/.ssh/authorized_keys755的.ssh目录和644的authorized_keys正好踩中OpenSSH严格模式的所有禁区内。这个命令输出的信息很全一次性把权限位、八进制权限值、属主、属组全部暴露出来比单纯用ls -l更直观。到这里排查链路已经完整走完Windows端scp发起认证 - 服务器端sshd检查key文件权限 - 权限不符合要求 - 拒绝认证 - 客户端显示Permission denied。3. 三种修复手段按场景选3.1 标准修法chmod把权限拉回正轨定位到问题之后修复非常简单在服务器上执行chmod 700 ~/.ssh chmod 600 ~/.ssh/authorized_keys chown -R $(whoami):$(whoami) ~/.ssh如果你用的是CentOS/RHEL这类开启了SELinux的系统强烈建议追加一条restorecon -Rv ~/.sshrestorecon用来恢复SELinux上下文因为即使权限改成600SELinux策略如果依然不允许sshd读取authorized_keys照样会认证失败。很多人在CentOS上修完chmod还是不行就是漏了这一步。为什么标准值是700和600而不是其他组合原因也很直接.ssh目录需要被sshd进程遍历但绝不能被组用户和其他用户写authorized_keys文件在认证阶段被sshd读取但绝不能被其他用户写入或篡改。只要组权限里出现了w写权限OpenSSH就会认为这个文件不安全。按最保险的来直接给到700和600一刀切清理干净。修复完立刻重新测试ssh -i C:\Users\dev\.ssh\id_ed25519 ubuntuserver这次不出意外会直接进入会话不需要再输密码。3.2 从源头避免ssh-copy-id和PowerShell平替修权限只是事后补救更聪明的做法是在第一次配公钥的时候就保证权限不出错。Linux/macOS下有个现成工具叫ssh-copy-id它的工作流程就是把公钥追加到服务器的authorized_keys然后把这个文件的权限顺便设置成600把.ssh目录权限设置成700。Windows 10/11自带的OpenSSH客户端不带ssh-copy-id但如果你装了Git Bash里面通常有。在Git Bash里执行ssh-copy-id -i ~/.ssh/id_ed25519.pub ubuntuserver如果没有Git Bash用PowerShell同样可以做到核心命令是管道type $env:USERPROFILE\.ssh\id_ed25519.pub | ssh ubuntuserver mkdir -p ~/.ssh chmod 700 ~/.ssh cat ~/.ssh/authorized_keys chmod 600 ~/.ssh/authorized_keys这条命令的思路很清晰先用type把公钥内容输出通过管道塞给远端shell远端一次性完成创建目录、设置目录权限、追加公钥、设置文件权限四件事。用这个办法从第一步就不会产生bad permissions。如果对这些命令行操作不太放心还有一个笨但稳妥的备选方案先把公钥用scp传到服务器的/tmp目录然后登录服务器手动移动并设置权限scp C:\Users\dev\.ssh\id_ed25519.pub ubuntuserver:/tmp/mykey.pub ssh ubuntuserver sudo mv /tmp/mykey.pub /home/ubuntu/.ssh/authorized_keys sudo chown ubuntu:ubuntu /home/ubuntu/.ssh/authorized_keys chmod 700 ~/.ssh chmod 600 ~/.ssh/authorized_keys这种手动方式看起来多敲了几行命令但不依赖管道符和远端引号解析在PowerShell环境里更不容易出幺蛾子。3.3 应急开关StrictModes no怎么用就怎么还有些紧急场景下你确实没法立刻去服务器上改权限比如你只有一张临时操作票或者服务器在客户内网本地只能干瞪眼。这时候有人会建议把服务器的StrictModes关闭。做法是修改/etc/ssh/sshd_configStrictModes no然后重启SSH服务。Debian/Ubuntu系sudo systemctl restart sshCentOS/RHEL系sudo systemctl restart sshd重启以后sshd不会再对authorized_keys、.ssh目录做严格的属主和模式检查bad permissions报错就会消失。但我必须把丑话说在前面这个方案是饮鸩止渴只建议在测试环境或者临时救急时使用。StrictModes no的本质是关掉了OpenSSH对关键文件的安全防线理论上任何能写这个文件的用户都有可能往里面塞公钥风险不小。生产服务器上用完一定要恢复成yes然后找时间把权限本身修好别留着一颗定时炸弹在线上跑。3.4 同一个坑在不同发行版上的变体我一开始以为这个问题只出现在Ubuntu上后来在CentOS、Rocky Linux、甚至一些国产化操作系统上都遇到过。它们报错日志的位置不一样但本质完全一致系统日志位置常见附加问题Ubuntu/Debian/var/log/auth.log基本只有权限模式问题CentOS/RHEL/Rocky/var/log/secure可能叠加SELinux拦截openSUSE/var/log/messages权限模式问题居多容器镜像alpinesyslog输出到stdout经常是主目录/root权限问题比如在CentOS上你chmod和chown都做了日志里还是报Permission denied这时候就得查一下SELinuxgetenforce如果输出是Enforcing那就执行前面提到的restorecon -Rv ~/.ssh。这一步常常被人遗忘但在强制SELinux模式下sshd读取authorized_keys同样会被拦截表现形式和权限错误几乎一样。4. 把“传公钥-授权限-免密登录”做成不会翻车的流水线4.1 一套可以直接抄的完整流程处理过太多次bad permissions之后我总结了一套从零到免密的流程照着走基本不会再掉坑。以Windows Ubuntu为例在PowerShell里生成密钥ssh-keygen -t ed25519 -C devwindows然后查看公钥内容Get-Content $env:USERPROFILE\.ssh\id_ed25519.pub复制输出的一整行公钥。这里要特别提醒不要用记事本打开公钥文件再全选复制。记事本在处理无换行符结尾的文件时有时候会加入奇怪的换行导致复制出来的公钥内容不干净。直接用Get-Content打印选中终端里那一行复制最可靠。把公钥追加到服务器ssh ubuntuserver mkdir -p ~/.ssh chmod 700 ~/.ssh echo 粘贴你的公钥内容 ~/.ssh/authorized_keys chmod 600 ~/.ssh/authorized_keys这里用单引号把公钥内容包住防止特殊字符被远端shell解析。追加完成后再执行一次权限修复保险ssh ubuntuserver chmod 700 ~/.ssh chmod 600 ~/.ssh/authorized_keys验证登录ssh ubuntuserver如果之前配的过程当中已经把密码认证关掉了但密钥还没配对会导致自己把自己锁在外面。这时候需要用云控制台的VNC或者管理终端登录服务器手动修复权限再测试。4.2 Windows下文本编辑器带来的隐形坑除了权限问题还有一种情况会让你误以为又是bad permissionsauthorized_keys文件内容被Windows的记事本污染了。Windows记事本保存文件时默认带UTF-8 BOM而且换行符是CRLF\r\n。服务器端解析authorized_keys时按Linux的LF\n语义处理CR会被当成公钥内容的一部分。这样一来即使权限改成600公钥内容也完全正确但服务器加载公钥时仍然不认识认证失败表现同样让人一头雾水。我遇到过最典型的场景同事在Windows上用记事本打开authorized_keys往里面粘贴了一行公钥保存后scp传到服务器。结果无论怎么chmodssh登录始终Permission denied。最后在服务器上执行sed -i s/\r$// ~/.ssh/authorized_keys把CRLF换行符全部转成LF问题立刻消失。所以我的建议是不要在Windows下用记事本编辑任何要传到Linux的配置文件。要么用VSCode这类能控制换行符的编辑器要么直接在服务器上用vi编辑要么就用上一节里的管道命令直接把文本追加到远端绕开本地编辑这一步。4.3 scp常用参数与一个经常忽略的小细节既然标题是scp报错那scp本身的一些参数值得多说两句。Windows下的scp和Linux下的scp参数基本一致但有几个细节是很多人栽过的参数作用坑点-P 端口指定远端SSH端口必须大写P小写p是保留文件属性-i 密钥文件指定私钥Windows路径有空格时要加引号-r递归复制目录传整个目录树必须加-p保留文件时间和权限属性对Windows到Linux的POSIX权限保留基本无效-C开启压缩大文件传输可以提速-v输出详细日志排查问题第一利器特别说明-p这个参数。很多人在Windows上想用scp -p把文件权限一起带过去指望服务器端文件自动变成600。实际效果是Windows的NTFS ACL权限模型和Linux的POSIX权限模型根本不是一回事scp -p对保留Windows文件权限到Linux基本无用。想解决权限问题还是老老实实到服务器上chmod没有捷径。另一个小细节远端路径如果包含通配符最好用引号包起来。比如scp -i key ubuntuserver:/home/ubuntu/logs/*.log C:\logs\不加引号的情况下通配符有可能被Windows本地的shell提前展开导致找不到文件。这个看似无关的细节在排查scp问题时也经常让人分心。5. 我这几年反复踩坑之后沉淀的几个习惯5.1 习惯一先看服务端日志再怀疑客户端现在不管是同事来问还是自己新环境出问题我第一反应永远是去服务器上看auth.log或者secure日志而不是在Windows这边瞎猜。你花半小时检查Windows防火墙、杀毒软件、文件ACL都不如一条日志命令定位得准。Linux的SSH日志非常诚实它会直接告诉你拒绝认证的原因是什么。如果日志里出现bad ownership or modes权限问题跑不掉如果是no matching key found那大概率是公钥内容有问题如果是Connection reset by peer可能是网络或者SSH版本兼容问题。日志是最高效的探针比任何猜测都靠谱。5.2 习惯二把权限修复写成一行命令经历得多了以后我干脆把这套修复命令存成了一个小脚本每次配置新服务器直接粘贴ssh userserver chmod 700 ~/.ssh chmod 600 ~/.ssh/authorized_keys chown -R \$(whoami):\$(whoami) ~/.ssh restorecon -Rv ~/.ssh 2/dev/null; true每次换服务器、换用户只要把userserver换了就行。特别是最后那个restorecon在CentOS上简直是救命稻草虽然它可能报错说没有这个命令加一个2/dev/null优雅忽略就好。这个脚本帮我省下了大量重复排错时间。5.3 习惯三给团队新人的最小检查清单后来我把这个问题沉淀成了一份给团队新人的检查清单谁再遇到scp报错先按清单逐条对authorized_keys文件是否存在于~/.ssh/目录下文件权限是否为600目录权限是否为700文件属主和当前登录用户是否一致公钥是否有完整的ssh-ed25519或ssh-rsa前缀并且只有一行文件是不是在Windows下编辑过如果是有没有残留CRLF服务器是否开启了SELinux如果开了是否执行过restorecon90%的bad permissions问题走完这六条都能解决。剩下的10%日志会告诉你方向。5.4 最后再分享一个小技巧如果你手头有多台服务器要批量处理别一台台手动配。用一个循环脚本把服务器列表放一个文本里逐个推送公钥并修正权限while read host; do ssh-copy-id -i ~/.ssh/id_ed25519.pub $host ssh $host chmod 700 ~/.ssh chmod 600 ~/.ssh/authorized_keys done servers.txt一开始我只用scp传文件遇到bad permissions时还觉得是Windows的锅折腾了半下午。后来想明白了Windows和Linux之间永远横着权限模型这堵墙scp只是那个负责送文件的快递员文件到了地方怎么放、放得合不合规矩得服务器说了算。把这个道理刻在脑子里再遇到类似的报错你也能一眼看穿问题出在哪一侧照着本文里的流程走一遍基本都能顺利解决。
返回列表