git@gitee.com: Permission denied (publickey). Could not read from remote repository.这行红字,大概是每个刚把代码往 Gitee 上搬的人都会撞一次的墙。它出现的位置很讨厌——往往是在你已经写完代码、建好仓库、复制了那条 SSH 地址、敲下git push之后,终端毫不留情地甩给你一句 Permission denied,然后告诉你 Please make sure you have the correct access rights and the repository exists。很多人第一反应是"我账号密码没错啊",第二反应是"是不是 Gitee 挂了",第三反应就开始怀疑人生。其实这条报错跟你的账号密码基本没关系,它说的是 SSH 层面的身份校验没过——Gitee 的服务器拿着你的公钥指纹去比对,没找到匹配项,于是拒绝你读取仓库。这篇文章就围绕这一个报错展开,把公钥私钥的配对逻辑、密钥生成时的参数取舍、多平台密钥共存、权限位、agent 缓存、日志读法这些平时没人细讲的东西一次讲透。不管你是刚装完 Git 的新手,还是被这个问题卡了半天准备放弃的人,照着走一遍,基本都能通。
1. 这条报错到底在说什么
1.1 字面含义与真实触发链路
先把这条报错拆开看。git@gitee.com是你在用的远程地址,git是用户名——注意,Gitee 的 SSH 接入用户名固定就是git这个字符串,不是你的账号名,也不是你的邮箱,这一点很多人会搞混。Permission denied (publickey)是 OpenSSH 客户端抛出来的,意思是"我尝试了所有能试的认证方式,只剩公钥这一条路,而公钥认证失败了"。后面那句Could not read from remote repository是 Git 对上层结果的转述,它并不代表仓库不存在,只是告诉你"因为认证没过,所以读不到"。
真实的链路是这样的:你在本地执行git push或git clone,Git 发现远程地址是git@gitee.com:xxx/yyy.git这种 SCP 风格,于是调用系统里的ssh程序,让 ssh 去连gitee.com的 22 端口,并声明要以git用户身份登录。ssh 客户端会先做主机密钥校验,再进入用户认证阶段。用户认证默认会尝试none、publickey、password等几种方式,而 Gitee 只接受公钥认证。于是 ssh 会把本地能找到的私钥逐个拿去做签名挑战,服务器用对应公钥验签,验过就放行。如果本地一个可用的私钥都没找到,或者找到的私钥在服务端没有登记对应公钥,就返回这句 Permission denied。
关键认知:这条报错是"客户端没拿出正确钥匙",不是"服务端不让你进门"。绝大多数情况下,问题在你的本地环境,不在 Gitee。
1.2 哪些操作最容易踩到这个坑
从我这几年帮人看问题的经验,触发场景其实高度集中。第一种是刚装完 Git、第一次连 Gitee,压根没生成过密钥,用的是仓库页面给的 SSH 地址,自然认证失败。第二种是生成了密钥也加到了 Gitee,但密钥文件名不是默认的id_rsa或id_ed25519,ssh 默认只扫这两个名字,扫描不到就等于没有。第三种是把公钥和私钥搞反了——把id_rsa的内容贴进了 Gitee 的公钥框,或者把id_rsa.pub留在了本地当私钥用。第四种是多平台混用,本地已经有 GitHub 的密钥,Gitee 又生成了一个新名字的密钥,没写 config 文件,ssh 不知道该给谁用。第五种最隐蔽:在 Linux 或 macOS 上,私钥文件的权限位太开放,比如644甚至777,OpenSSH 出于安全考虑会直接忽略这个私钥,然后一路走到 Permission denied。
还有一种不太算错的场景:公司网络限制了 22 端口出入,ssh 连不上 22,超时或者被重置,表现出来也可能是连接被拒。这种时候得换协议或者换入口,后面会细说。
1.3 先做三件事,别急着删密钥重来
很多人一遇到这个报错就把.ssh目录清空重来,这是最亏的做法,因为原有密钥可能还挂在别的服务上。正确的第一步是先自检。打开终端,先看目录里有什么:ls -al ~/.ssh。第二步,用 ssh 自己测一次连接:ssh -T git@gitee.com。第三步,看这句测试命令的输出到底停在哪:如果提示Host key verification failed,那是主机指纹问题;如果提示 Permission denied,那才是密钥问题;如果直接卡住不动最后超时,那是网络或端口问题。三步走完,你就知道自己面对的是哪一类故障,再去动手,效率完全不一样。
注意:
ssh -T git@gitee.com里的-T是明确告诉 ssh"不要分配终端",因为 Gitee 只做认证不给你 shell。不加这个参数会看到一句奇怪的提示,容易让人误判。
2. 公钥私钥这对东西是怎么工作的
2.1 一把锁和一把钥匙的类比
SSH 密钥认证的本质非常简单,可以用一把挂锁来理解。你生成密钥对的时候,其实同时造出了一把锁和一把钥匙。公钥就是那把锁,你可以随便复制、随便贴到任何平台上,贴到 Gitee 就相当于把锁挂在了 Gitee 的大门上;私钥是那把唯一的钥匙,它必须留在你自己的电脑里,谁拿到它谁就能冒充你。当你连接 Gitee 时,服务器会说"我这挂着一把你给的锁,你能打开它吗",你的 ssh 客户端就用本地私钥去开这把锁,开得了就证明你确实是锁的主人。
这里面有一个常被误解的点:公钥是可以公开的,贴到任何地方都不算泄露;私钥一旦泄露,必须立刻作废重新生成,因为别人拿它就能以你的身份推送代码。所以千万不要把私钥内容贴到任何网页、聊天窗口或者 issue 里,也不要为了"让别人帮你调试"而把私钥发出去。我见过有人把id_rsa的完整内容贴到论坛求帮忙,那基本等于把家门钥匙拍照发到了网上。
2.2 Gitee 校验时真正比对的四个环节
从具体流程看,Gitee 侧的公钥校验大致经过四个环节。第一,ssh 客户端发起连接,声明自己支持的认证方式;第二,服务端返回可接受的认证方式列表,其中包含 publickey;第三,客户端发送"我想用某个公钥的指纹试试",服务端在自己的公钥库里检索这个指纹,找不到就直接拒绝,这一层失败会立刻报 Permission denied,连签名挑战都不会做;第四,服务端找到指纹后,发一段随机数据让客户端用私钥签名,服务端用登记的公钥验签,验过才通过。
这四个环节对应了四类不同的排错方向:指纹根本不在库里,说明公钥没添加或添加错了;指纹在库里但验签失败,说明私钥和公钥不配对,或者本地用的私钥不是你贴上去那把对应的私钥;服务端压根没返回 publickey 选项,那通常是连错了主机;全程没到第三步就断了,那是网络层问题。
2.3 不是所有 Permission denied 都是密钥问题
有一种情况需要特别提醒:Permission denied (publickey)也有可能是你在跟错的主机说话。比如有些人的 DNS 或者 hosts 被改动过,gitee.com解析到了一个不该去的地方;又比如你在~/.ssh/config里写过Host *的通配规则,把gitee.com的 User 改成了自己的账号名,那 Gitee 收到的是"以你的账号名登录"而不是"以 git 登录",照样会拒绝。
还有一种情况是仓库地址本身就不需要 SSH。你在 Gitee 仓库页面点"克隆/下载"的时候,可以选 HTTPS 或 SSH 两种地址。如果你复制的是 SSH 地址,那必须有密钥;如果你只是想快速拉个开源项目看看代码,用 HTTPS 地址完全够用,公开仓库甚至不需要登录。搞清楚这个区别,能省掉一大半折腾。
3. 从零配一套能用的 Gitee SSH 密钥
3.1 先确认本机有没有现成的密钥
动手之前先看一眼,别重复造。打开 Git Bash(Windows)或终端(macOS/Linux),执行ls -al ~/.ssh。如果有id_rsa和id_rsa.pub这一对,或者id_ed25519和id_ed25519.pub这一对,说明你已经有默认密钥了,那就跳到第 3.3 节,直接把已有的.pub内容贴到 Gitee 即可。如果目录不存在,或者只有一个known_hosts文件,那就老老实实从头生成。
Windows 上要特别注意一件事:系统里可能同时存在两套 ssh。一套是 Windows 自带的 OpenSSH(通常在C:\Windows\System32\OpenSSH\下),另一套是 Git for Windows 自带的(通常在 Git 安装目录的usr\bin\下)。这两套读的配置文件位置可能不一样,~/.ssh的家目录解释也可能不同。判断当前用的是哪一套,执行which ssh(Git Bash 里)或where ssh(CMD 里)就能看出来。如果 Git 用的是一套、你配置密钥时命令行用的是另一套,就会出现"我明明配好了为什么还是不行"的诡异现象。
3.2 生成密钥的参数选择与取舍逻辑
生成命令的核心是ssh-keygen。最推荐的写法是:
ssh-keygen -t ed25519 -C "你的邮箱或备注"这里的-t指定算法类型,-C是给密钥加一个注释,通常填邮箱,方便你以后在 Gitee 的公钥列表里认出这是哪台机器的密钥。注释只是标签,不影响认证,但强烈建议填,因为当你手上有好几台设备时,删错公钥是很常见的事故。
算法为什么优先选 ed25519 而不是 rsa?ed25519 是近几年主流的椭圆曲线算法,密钥长度短、生成快、签名验签也快,安全性足够,而且它生成的公私钥文本都很短,贴进网页表单不会因为长度被截断。老牌的 rsa 需要至少 2048 位才有基本安全性,一般建议 4096 位,生成时会有几秒卡顿,公钥文本也比较长。如果你维护的是十年以上的老系统,可能遇到不支持 ed25519 的情况,那就退回ssh-keygen -t rsa -b 4096 -C "你的邮箱"。
执行命令后会有三个交互。第一个问保存路径,直接回车用默认的~/.ssh/id_ed25519就行;如果你要区分多平台,这里可以输入自定义路径,比如~/.ssh/id_ed25519_gitee。第二个和第三个问 passphrase,也就是给私钥再加一层密码。加了之后每次使用都要输密码,安全性更高但麻烦;如果你觉得本机是自己独占的、懒得输,直接两次回车留空也可以。我的建议是:笔记本随身带、有丢失风险的,加一个 passphrase;固定工位台式机,可以不加。
3.3 把公钥贴到 Gitee 的正确位置
生成完之后,用cat ~/.ssh/id_ed25519.pub把公钥内容打印出来。你会看到一整行以ssh-ed25519开头、以你的注释结尾的文本。注意,这里要复制的是.pub结尾的那个文件,不是没有后缀的私钥文件。
复制这整行内容,登录 Gitee,进入个人设置,找到"安全设置"里的"SSH 公钥",点击添加公钥。标题随便填,能区分设备就行,比如"办公台式机"、"家里笔记本"。公钥内容粘贴进去,保存。Gitee 会校验这串文本的格式,格式不对会直接提示你,这也是为什么不要手工改动里面任何一个字符——多一个空格、少一个换行都可能失败。
提示:Gitee 允许一个账号添加多个公钥,也允许同一个公钥被不同账号添加(因为公钥只是标识,不代表独占)。但同一个公钥反复添加是没有意义的,重复添加不会加速任何事情。
3.4 首次连接要做的主机指纹确认
添加完公钥,再回到终端,执行ssh -T git@gitee.com。第一次连接时,ssh 会问你是否信任这台主机,提示大概长这样:The authenticity of host 'gitee.com' can't be established... Are you sure you want to continue connecting (yes/no)?。这里输入yes回车,ssh 会把 Gitee 的主机指纹记到~/.ssh/known_hosts里。之后再连接,就不会问了。
如果输出类似Hi XXX! You've successfully authenticated, but Gitee.com does not provide shell access.的句子,恭喜你,认证通了。那句"不提供 shell 访问"是正常的,因为 Gitee 只用来托管 Git 仓库,不给你登录用的命令行。看到这句话就说明 SSH 这一层已经打通,接下来git clone、git push都不会再报 Permission denied。
反过来,如果还是 Permission denied,那就带着这套已经配好的环境进入下一章的排查流程。
3.5 多平台、多账号共存时 config 怎么写
现在很多人手上不止一个代码托管平台,也可能一个平台有工作号和私人号两个账号。这种情况下,默认密钥名id_rsa、id_ed25519就不够用了,必须靠~/.ssh/config来分流。假设你为 Gitee 生成了~/.ssh/id_ed25519_gitee,那 config 里可以这样写:
Host gitee.com HostName gitee.com User git IdentityFile ~/.ssh/id_ed25519_gitee IdentitiesOnly yes这段配置的含义是:当目标主机是gitee.com时,用git作为用户名,只使用id_ed25519_gitee这一把私钥。其中IdentitiesOnly yes是关键,它告诉 ssh 不要自作聪明地去尝试其他所有能找到的私钥。不加这一行,ssh 可能会把 GitHub 的密钥也拿去试,尝试次数太多会被服务端限流,反而拖慢连接。
配置文件对格式很讲究:Host顶格写,下面的选项缩进两个空格,冒号后面的值不要有多余空格。写完保存,用ssh -T git@gitee.com验证,通了就说明配置生效。如果你有多个平台,就多写几个 Host 块,互不干扰。
4. 分场景排查:不同细节对应不同病根
4.1 排查速查表
| 现象 | 最可能的原因 | 处理方式 |
|---|---|---|
| 从未生成过密钥 | 本地没有密钥对 | 执行 ssh-keygen 生成并添加公钥 |
| 有密钥但报错不变 | 公钥未添加或添加成了私钥 | 检查 Gitee 公钥内容是否为 .pub 内容 |
| 密钥名非默认 | ssh 不会自动扫描 | 写 config 指定 IdentityFile |
| Linux/macOS 权限报错 | 私钥权限过于开放 | chmod 700 ~/.ssh,chmod 600 私钥 |
| 连接直接超时 | 22 端口不通 | 换 HTTPS 协议或确认网络策略 |
| 提示 Host key verification failed | 主机指纹变了 | 清理 known_hosts 中对应条目后重连 |
| 同一个密钥多平台混用 | 未做分流 | 用 config 按 Host 指定不同密钥 |
| 添加公钥后立刻报错 | 复制时多了换行或空格 | 重新完整复制粘贴 |
这张表基本覆盖了九成以上的实际案例。遇到问题先按表格对一遍,比漫无目的试命令要快得多。
4.2 私钥权限这个坑,Linux 和 macOS 上最容易踩
Windows 上因为文件系统权限模型不一样,OpenSSH 对权限的要求相对宽松,很多人从 Windows 转到 macOS 或 Linux 后,直接把整个.ssh目录复制过来,权限位也跟着复制了,结果就是一直 Permission denied。在 macOS 和 Linux 上,ssh 对私钥文件权限是强校验的:私钥必须是600(只有自己可读写),.ssh目录建议700(只有自己可进入),公钥和known_hosts一般644。如果私钥是644或更开放,ssh 会直接跳过它,并在-v日志里留下ignoring key之类的记录。
修复命令就三条:
chmod 700 ~/.ssh chmod 600 ~/.ssh/id_ed25519 chmod 644 ~/.ssh/id_ed25519.pub改完立刻再测一次ssh -T git@gitee.com,很多人的问题到这里就结束了。
4.3 ssh-agent 到底要不要用
ssh-agent 是一个在后台帮你保管私钥的小服务。它的价值在于:当你的私钥设了 passphrase,没有 agent 的话,每次 git 操作都要重新输一遍密码;有了 agent,你只在开机后ssh-add一次,后面就不用了。是否启用看你自己的习惯。启用方式分三步:先启动 agent,eval "$(ssh-agent -s)";再把密钥加进去,ssh-add ~/.ssh/id_ed25519;最后验证,ssh-add -l会列出当前已加载的密钥指纹。
这里有个常见误区:agent 里加载的密钥和你实际要用的密钥不是同一把。比如你加载了 GitHub 的密钥,但连接 Gitee 时应该用id_ed25519_gitee,那还是不行。所以 agent 加载完,最好用ssh-add -l对一下指纹,确认加进去的那把确实是你要用的。
Windows 上还有一个额外可能:如果你用 PowerShell 而不是 Git Bash,eval这种写法不通用,得用系统服务方式启动 ssh-agent,或者干脆在 Git Bash 里操作。混用不同终端是 Windows 下排查这类问题的常见障碍。
4.4 用 ssh -vT 读日志的正确姿势
当前面的招数都用过了还是不行,就该看日志了。命令是ssh -vT git@gitee.com,-v会打印详细的握手过程。日志很长,新手容易看晕,你只需要盯几个关键行。看到Offering public key: ...说明客户端确实拿出了某把公钥;看到Server accepts key: ...说明服务端认可这把公钥的指纹;看到Authentications that can continue: publickey说明服务端要求公钥认证;最终如果出现Permission denied,往前翻几行看它到底试了哪几把密钥,路径是什么。
如果你发现日志里Offering的密钥路径跟你以为的不一样,那就是 ssh 读的目录和你配置的目录不一致,多半是 Windows 双 ssh 环境的问题,或者 config 文件的位置不对。这种时候,用ssh -vT输出里Reading configuration data那一行确认它到底加载了哪个 config 文件,是解决问题的钥匙。
5. 一次完整的报错到推送成功的现场记录
5.1 现场还原
我拿一台刚装完系统、只装了 Git for Windows 的机器复现了一次。第一步,装完 Git 后直接在项目目录里执行git remote add origin git@gitee.com:user/demo.git,然后git push -u origin master。终端立刻返回文章标题里那条完整报错。第二步,执行ls -al ~/.ssh,提示目录不存在,确认是从来没生成过密钥。
第三步,ssh-keygen -t ed25519 -C "demo@example.com",路径全默认,passphrase 两次回车跳过。第四步,cat ~/.ssh/id_ed25519.pub复制整行,去 Gitee 后台添加公钥。第五步,ssh -T git@gitee.com,输入 yes 确认指纹,返回成功认证的提示。第六步,重新git push -u origin master,这次顺利推上去了。整个流程不到五分钟。这也说明一件事:新手遇到这个报错,先别折腾,先确认自己有没有密钥,往往一步就能定位。
5.2 顺带把 HTTPS 免密也配好
有些场景下 SSH 确实不好使,比如公司网络封了 22 端口,或者你的运行环境里不方便管理密钥文件。这种时候 HTTPS 是更好的选择。Gitee 的 HTTPS 地址可以配合私人令牌使用,把令牌当成密码,就能避免每次输账号密码——虽然现在 Gitee 更多是让你用密钥和令牌结合的方式,最省事的其实是本地凭据缓存。
Git 提供了一个凭据存储机制,执行下面这条命令开启缓存:
git config --global credential.helper store之后第一次推送时输入账号和令牌,Git 会把它存到本地,后续就不用再输了。Windows 上还可以用 Git Credential Manager,图形化弹窗,体验更接近日常软件的登录方式。要注意的是store是明文存储,如果机器是多人共用的,建议换成cache模式,或者干脆继续用 SSH。
提示:HTTPS 和 SSH 两条路可以并存。同一台机器上,用一个仓库走 SSH、另一个仓库走 HTTPS 完全没问题,Git 是按仓库的 remote 地址来决定用哪种协议的。
5.3 迁移到新机器时的顺手操作
换电脑这件事很多人处理得很糙:直接把自己的.ssh目录整个拷过去,然后发现权限不对、config 里的绝对路径不对、agent 里的密钥没带过去。更稳妥的做法是只迁移必要的东西:把私钥和公钥这两对文件拷过去,把 config 文件也拷过去,然后在目标机器上重新跑一遍权限设置,重启一次 agent 并重新ssh-add。同时建议在 Gitee 后台给新机器单独加一个公钥,而不是复用旧机器的——这样万一某台机器出问题,你可以精确地吊销某一台,而不影响其他设备。
6. 几条踩过坑之后总结的心得
6.1 密钥管理上的几条硬规矩
第一条,私钥永远不出本机,不截图、不发聊天、不贴网页。第二条,一个设备一对密钥,别在多台机器之间共享同一把私钥,出了问题你都不知道是哪台泄露的。第三条,公钥在 Gitee 后台的标题写清楚设备和用途,删除的时候不犹豫,凡是"看起来不认识的公钥"都值得警惕。第四条,定期清理known_hosts里已经不存在的主机条目,虽然不影响使用,但会让日志变干净。
第五条,也是我认为最重要的:遇到认证问题,先跑ssh -T git@gitee.com再动手。这一条命令相当于一次独立体检,它把 Git 这一层剥掉了,直接测 SSH 认证。体检通过,那问题就在 Git 的 remote 地址或者仓库权限上;体检不通过,那就在密钥体系里。先把故障域缩到一半,再深挖,效率差距非常大。
6.2 几个高频疑问的直给答案
有人会问,为什么我在 GitHub 上好好的,到 Gitee 就不行?因为两个平台的公钥库是互相独立的,你在 GitHub 添加过的公钥,Gitee 那边没有记录,必须重新添加。密钥本身可以复用同一把,只是公钥要分别贴到两个平台。
还有人问,我明明把公钥加上了,为什么还报错?先检查你加的是不是.pub文件的内容,再检查你本地是不是只生成过一对密钥但生成时改了路径。前者是复制对象错了,后者是 ssh 找不到密钥。
另外常见的是"仓库地址用错了"。Gitee 仓库页面上的 SSH 地址格式是git@gitee.com:用户名/仓库名.git,如果是自己复制时多带了空格,或者把 HTTPS 地址当成 SSH 用,都会出错。用git remote -v看一眼当前远程地址,是最快的确认方式。
最后一个是"我加了 passphrase 之后每次都提示要输"。这是正常行为,嫌烦就启动 ssh-agent 并ssh-add,或者重新生成一对不带 passphrase 的密钥。没有别的原因,也不用怀疑配置。
6.3 后续还能往哪走
这套密钥体系打通之后,能延展的事情其实不少。比如你可以在 CI 流水线里配置部署密钥,让构建机器能拉取私有仓库;可以给自己的服务器配免密登录,省掉每次输密码的麻烦;也可以用同样的思路处理多平台账号共存,把工作号和私人号彻底隔开。核心逻辑始终是那一条:公钥贴到被访问的一端,私钥留在发起访问的一端,中间靠 config 文件决定给谁用哪把。
我个人在多台设备之间来回切换之后最大的体会是,密钥这件事一开始花二十分钟理解清楚,后面能省下几十次抓耳挠腮。真正让人卡住的从来不是命令有多难,而是不知道报错在说什么。现在再看到Permission denied (publickey),它对我而言就是一句话:本地没有拿出正确的那把钥匙。确认钥匙在不在、名字对不对、权限对不对、平台登记了没有,四个问题问完,问题基本就解决了。