我当年第一次遇到 git clone 提示 access denied 的时候,第一反应是怀疑网络有问题。后来发现这个错误背后藏着十几种可能,几乎每个刚切到 Git 工作流的同事都在这上面栽过跟头。这篇文章不是从文档里抄的排查清单,是我这几年在真实项目里一个个踩出来的经验汇总。
先说清楚这个报错在说什么。Access denied 翻译过来是“访问被拒绝”,它不是一个网络连通性问题,而是 Git 服务器明确告诉你:“我认识你,但你没有权限做这件事。” 换句话说,你的电脑和服务器之间网络是通的,但你的身份认证没过关。搞明白这一点,排查方向就对了。
这篇文章适合谁看?如果你刚接触 Git 没多久,clone 别人的开源项目时突然撞上这个报错,或者你在公司内网 clone 私有仓库时明明有权限却一直被拒,又或者你想搞懂 SSH 和 HTTPS 两种 clone 方式到底差在哪——这篇都能帮你省下半天瞎折腾的时间。我会把整个问题拆成四个层面来讲,最后再放几个我真实遇到过的疑难杂症实录。
1. 先分清楚:你用的是 SSH 还是 HTTPS 在 clone
1.1 两种协议下的 Access Denied 长得不一样
Git clone 支持多种协议,但日常开发中 95% 的报错都集中在 SSH 和 HTTPS 这两种。很多人不知道的是,这两种协议下的 access denied 报错提示完全不同,排查思路也完全是两条路。
- SSH 协议 clone 的地址长这样:
git@github.com:username/repo.git,报错时通常会提示Permission denied (publickey)或git@github.com: Permission denied (publickey). - HTTPS 协议 clone 的地址长这样:
https://github.com/username/repo.git,报错时通常会提示fatal: Authentication failed for 'https://github.com/username/repo.git'或remote: Access denied
看明白了吗?SSH 模式报错带 publickey 字样,说明是密钥认证环节出了问题;HTTPS 模式报错带 Authentication failed 或 Access denied 字样,说明是账号密码或 Token 认证出了问题。一看到报错文本,你心里就应该立刻知道该走哪条排查路线,而不是瞎试。
我在带新人的时候发现一个规律:很多人根本不记得自己当初 clone 时用的是哪种协议,只是在 IDE 里点了“克隆”按钮填了个地址就完事了。所以我建议你先做一件事:把之前复制过的 clone 地址翻出来,看它是 git@ 开头还是 https:// 开头。这一步只需要五秒钟,但能让你少走两个小时的弯路。
1.2 Access Denied 的本质:不是网络问题,是身份问题
我见过太多人在遇到 access denied 时第一反应是换网络、开代理、重启电脑,折腾一圈回来还是一样报错。这里我要把话说明白:如果真的是网络不通,你看到的报错会是 Connection timed out、Could not resolve host、Connection refused 这一类,而不是 Access denied。
用生活化的方式类比一下。Connection timed out 相当于你打电话一直没人接,对方可能不在服务区;Access denied 相当于电话接通了、对方也说话了,但他一听你的声音,说“我不认识你,不能告诉你这家的事”。完全两码事。
所以当报错明确写着 Access denied 时,你的排查重心应该放在“我是谁、我怎么证明我是我”这个问题上,而不是去调整网络。这个思维定势一旦建立起来,后面所有步骤都会顺很多。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 最常见的坑:SSH 密钥没配对
2.1 先确认你当前用的到底是哪把钥匙
SSH 模式下的 access denied,九成是密钥对不上号。Git 服务器那边存着你的公钥,你的电脑里存着对应的私钥,两边要是一对上了,服务器就放你进去;对不上,就给你一句 Permission denied (publickey)。
先别急着重新生成密钥。很多人一看到这个报错就开始 ssh-keygen 一把新钥匙,结果是把原来能用的钥匙覆盖了,问题反而更麻烦。正确的做法是先检查一下你当前用的是哪把钥匙。
bash复制ssh -T git@github.com
这条命令会尝试用你本地的 SSH 配置去连接 GitHub 的 SSH 服务。如果密钥匹配,你会看到类似 Hi username! You've successfully authenticated, but GitHub does not provide shell access. 的输出。如果密钥不匹配,你会看到 Permission denied (publickey)。这一步能帮你确认两件事:你的 SSH 客户端正在尝试用哪个 key,以及服务器认不认这把 key。
如果想看更详细的调试信息,可以加 -v 参数:
bash复制ssh -vT git@github.com
输出里会有一行类似 Offering public key: /Users/xxx/.ssh/id_rsa 的内容,这就是你当前试图用来认证的私钥路径。看到这条信息,你就知道问题出在哪了——要么是这把私钥对应的公钥没有配到服务器上,要么是 SSH 客户端因为某种原因没把正确的私钥拿出来。
2.2 正确的排查顺序:本地私钥、服务器公钥、SSH agent
排查 SSH 认证问题,我习惯按照“从本地到远端”的顺序来,每一步都有对应的验证命令。
第一步,确认本地私钥文件存在。默认情况下你的密钥应该在 ~/.ssh/ 目录下,常见的名字是 id_rsa 或 id_ed25519。
bash复制ls -la ~/.ssh/
如果这个目录里什么都没有,或者压根没有 .ssh 目录,那就说明你从来就没生成过密钥。这时候才需要生成一把新的:
bash复制ssh-keygen -t ed25519 -C "your_email@example.com"
这里我推荐 ed25519 而不是传统的 rsa。ed25519 的密钥更短、生成更快、安全性也不差,而且现在主流的 Git 托管平台都已经支持了。生成过程中它会问你要不要把密钥文件存到默认路径,直接回车用默认就行。随后还会让你设置 passphrase(口令),我建议设一个,虽然每次连接要多输一次密码,但私钥文件被偷走时对方也解不开,安全级别完全不一样。
第二步,把公钥内容复制到 Git 服务器的 SSH Keys 设置页里。查看公钥的命令:
bash复制cat ~/.ssh/id_ed25519.pub
复制输出内容,然后登录你的 Git 托管平台(GitHub、GitLab、Gitee 都差不多),找到 Settings → SSH and GPG keys → New SSH key,把内容粘贴进去保存。
第三步,检查本地的 SSH agent 是否正在运行,并且是否加载了你的私钥。有些系统环境下 SSH agent 没启动会导致明明有密钥却认证失败。
bash复制eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519
执行完这三步之后,再跑一次 ssh -T git@github.com 验证,基本上就能通了。
2.3 多账号场景:为什么你配好了还是 access denied
如果你同时有个人账号和公司账号,或者在不同平台各有一个账号,那问题就复杂一些。SSH 默认只会用 ~/.ssh/id_rsa 或 ~/.ssh/id_ed25519 这一把默认密钥去连接所有服务器,但不同平台、不同账号需要的是不同的公钥。这时候就需要用到 SSH 的 config 文件来指定“哪个域名用哪把钥匙”。
假设你要在 GitHub 上用 id_ed25519_github 这把钥匙,在公司 GitLab 上用 id_ed25519_gitlab 这把钥匙,可以在 ~/.ssh/config 里这样配置:
code复制Host github.com
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_github
Host gitlab.company.com
HostName gitlab.company.com
User git
IdentityFile ~/.ssh/id_ed25519_gitlab
注意:写 config 文件时,缩进用空格还是 Tab 都有讲究,而且
IdentityFile的路径要写绝对路径。改完 config 之后建议用ssh -T git@github.com和ssh -T git@gitlab.company.com分别验证一次。
很多人在多账号场景下卡住,核心原因就是 SSH 客户端默认拿了第一把钥匙去连服务器,服务器一看不认识就拒绝了。配置了 config 之后,SSH 会按照 Host 字段自动匹配对应的 IdentityFile,问题自然就解决了。
2.4 一个经常被忽略的细节:服务器端公钥有没有配错地方
还有一种情况是本地一切正常,ssh -T 也能认证成功,但 clone 私有仓库时依然 access denied。这时候问题往往出在权限模型上——你可能在 GitHub 上有账号,但不是这个私有仓库的 collaborator,服务器认出了你是谁,却发现你没有访问这个仓库的权限。
这种报错和密钥问题不一样,它的文本可能是:
code复制remote: Permission to username/private-repo.git denied to other-username.
fatal: unable to access 'https://github.com/...': The requested URL returned error: 403
注意看这行文本里出现了两个用户名:username 是仓库所有者的用户名,other-username 是你当前认证的账号。这说明你的密钥或凭证对应的是一个账号,但那个账号没有被加到仓库的协作者列表里。解决方法是找仓库所有者把你加进去,或者在仓库的 Settings → Collaborators 里添加你的账号。
还有个比较隐蔽的场景:公司内部的 GitLab 或 Gitea 服务器,项目分组的权限可能是继承的,光把你加进项目还不够,还得把你加进项目所在的 Group。这种时候报错文本不会明确告诉你缺哪个权限,你只能找管理员确认账号在组里的角色是不是 Developer 或以上。
3. HTTPS 场景:密码、Token 与凭证管理器
3.1 为什么输对了密码还是被拒
HTTPS 模式下的 access denied 通常长这样:
code复制fatal: Authentication failed for 'https://github.com/username/repo.git'
很多人的第一反应是“我密码明明输对了啊”。问题在于,Git 托管平台早就不是拿密码做认证的时代了。GitHub 在 2021 年就正式取消了账号密码的 Git 操作认证,你必须用 Personal Access Token(个人访问令牌)来替代密码。也就是说,你在弹窗里输入密码的位置,应该输入一个 Token 而不是你的账号密码。
怎么生成 Token?以 GitHub 为例:Settings → Developer settings → Personal access tokens → Tokens (classic) → Generate new token。生成的时候勾选 repo 权限,这是 clone 私有仓库所必需的。生成之后记得立刻复制保存,因为关闭页面后就再也看不到了。
拿到 Token 之后,你可以在 clone 时直接输进去,也可以把它写进 URL 里:
bash复制git clone https://username:ghp_xxxxxxxxxxxx@github.com/username/repo.git
但我不建议把 Token 直接写在 URL 里。因为这样 Token 会留在 shell 的历史记录里,而且如果别人看到你的屏幕,Token 就直接泄露了。相对安全一点的做法是先用不带 Token 的地址 clone,等弹出账号密码提示时再输入:
bash复制git clone https://github.com/username/repo.git
弹出的用户名框填你的 GitHub 用户名,密码框粘贴 Token。这样 Token 就只存在内存里,不会落盘。
3.2 凭证管理器:为什么改完密码还是旧凭证
Windows 用户使用 HTTPS clone,最经典的坑就是系统的凭证管理器缓存了旧的账号和 Token。你明明在网页上改了密码、换了新 Token,但 Git 在弹窗确认时用的还是缓存里那条旧凭证,结果就是反复 access denied。
解决办法是在 Windows 的“控制面板 → 用户账户 → 凭据管理器 → Windows 凭据”里,找到和 git:https://github.com 相关的条目,删掉或编辑成新的用户名和 Token。macOS 用户则需要在“钥匙串访问”里搜索 github.com,找到对应的条目删除。
也可以直接命令行操作。Windows 下:
bash复制git credential-manager erase
然后按提示输入 protocol=https、host=github.com,最后再空行确认。macOS 下先清掉 git 凭证:
bash复制git credential-osxkeychain erase
host=github.com
protocol=https
这种操作之后,下次 clone 时 Git 会重新弹窗要你输入,这时候把新的 Token 填进去就行。
3.3 多人共用电脑时的账号串号问题
还有一种场景很让人抓狂:公司电脑是配置好的,之前同事用他的账号 clone 过一个仓库,你接手后 clone 另一个私有仓库,结果永远 access denied。原因就在于凭证管理器里缓存的是前一个同事的账号信息,Git 默认用缓存里的凭证去认证,根本没机会让你输新账号。
这种时候有两种解决办法。第一种是按上面说的,清掉凭证管理器里的缓存,下次 clone 时重新输入。第二种是在具体仓库里覆盖 user 配置:
bash复制git config user.name "你的用户名"
git config user.email "你的邮箱"
但要注意,user.name 和 user.email 只管提交记录里的作者信息,和认证凭证是两码事。真正管认证的还是凭证管理器。以前见过不少同事折腾半天 user.name,结果发现 access denied 一点没变,就是因为没搞清楚这个区别。
3.4 HTTP 还是 HTTPS?URL 协议不一致导致的奇怪问题
还有一个容易被忽略的坑:公司的 Git 服务器可能同时开了 HTTP 和 HTTPS 两种入口,但两种入口背后的认证逻辑完全不一样。之前遇到过这样一个真实案例:同事用 http://gitlab.company.com/group/repo.git 这条地址 clone 一直是 access denied,但换个入口、用 https://gitlab.company.com/... 就秒过。原因就是公司内网的反向代理只对 HTTPS 入口做了透传认证,HTTP 入口则被安全策略拦截。
遇到这类问题,看不出个所以然的时候可以先试试两种协议各 clone 一次。如果 HTTPS 能通而 HTTP 不能,那就是服务器中间层的问题,不是你本地配置的问题。把 HTTP 改成 HTTPS 地址再 clone 一次,多半就解决了。
4. 比认证更深一层的 Access Denied:仓库权限与 URL 本身
4.1 404 和 Access Denied 是一对孪生兄弟
在很多大型 Git 托管平台上,当你访问一个不存在的仓库,或者访问一个没有权限的私有仓库时,服务器会统一返回 404 Not Found,而不是明确的 Access Denied。为什么?这是安全设计——服务器不想让没有权限的人知道“这个仓库到底存不存在”,以防信息泄露。
所以一个看似矛盾的排查点出现了:有些私有仓库 clone 时报 access denied,其实根本原因是仓库地址写错了,或者仓库是私有的但你压根没被授权。区分这两者有个土办法:在浏览器里登录你的账号,直接访问仓库地址。如果你登录后能看到页面,而 clone 时被拒,那是 Git 认证环节的问题;如果你登录后也看不到页面,那是仓库权限设置的问题,跟本地 Git 配置无关。
4.2 仓库所有者改名的连锁反应
一个很容易遗漏的坑:当仓库的 owner(所有者)改了用户名,或者仓库本身被 transfer(转移)到了另一个账号下,老地址会失效。Git 平台的 404 策略会让它看起来像“权限被拒绝了”,但实际上只是地址不存在。
我自己就踩过这个坑。有一次 clone 一个老项目的地址,报 access denied,折腾了半天密钥和凭证,最后发现是项目从个人账号转到了组织账号下,仓库地址从 https://github.com/olduser/project.git 变成了 https://github.com/orgname/project.git。改一个 URL,问题直接消失。
所以排查 access denied 时,先确认一下仓库地址是不是最新的,尤其是那种老项目、从别人那里交接过来的项目。最简单的方式是问一下仓库当前的管理员要一条最新的、确认能用的 clone 地址。
4.3 URL 里的细节:大小写、.git 后缀、分支名
URL 写错不会总报 404,有时候也会伪装成 access denied。几个我实际遇到过的细节问题:
- URL 大小写:Git 平台的仓库名是区分大小写的。
MyRepo.git和myrepo.git可能是两个完全不同的仓库。特别是在 Linux 服务器上自建的 Git 服务,这个坑尤其常见。 - 少了
.git后缀:大部分平台下https://github.com/username/repo也能正常 clone,但一些自建 Git 服务要求地址必须以.git结尾,不然就返回认证失败的提示。 - 分支问题:clone 默认拉取远端仓库的默认分支。假设远端默认分支叫
main而仓库里只有master分支,某些老旧 Git 版本配置不当的时候,clone 会失败并报unexpected disconnect,但一些中间代理层会把它翻译成 access denied 之类的提示。
遇到这种问题,最简单的办法就是让仓库管理员发一条他本地“实测正常”的 clone 地址给你,然后你逐字节对照排查。
4.4 公司内部代理:一条隐藏很深的 Access Denied 来源
接着开头说的“不是网络问题”,现在得补充一个例外:当你的网络流量要经过公司代理时,Access denied 有可能确实是网络层拦截的产物,但它依然不是“网络不通”的问题,而是“代理不让你过去”的问题。
这种场景下,报错文本里通常会带一些特征,比如 fatal: unable to access 'https://github.com/...': The requested URL returned error: 403,或者 Received HTTP code 403 from proxy after CONNECT。如果是后者,说明你的 Git 走了代理,而代理服务器返回 403 拒绝了这个请求。
检查一下你的 Git 是否配置了代理:
bash复制git config --global --list | grep -i proxy
或者直接看全局配置文件:
bash复制git config --global -e
查到了 http.proxy 或 https.proxy 的配置,就说明 Git 在通过代理访问远端。如果你当前环境不需要代理,可以直接关掉:
bash复制git config --global --unset http.proxy
git config --global --unset https.proxy
但注意,有些情况是反过来——你所在的内网必须走代理才能访问外网的 Git 服务。这时候强行去掉代理反而会报 Could not resolve host。之前帮一个同事排查,他报 access denied 是因为公司代理做了一层层校验,只放行白名单域名,GitHub 的某些域名不在白名单里。这种只能找网络管理员申请,你在本地无论怎么改 Git 配置都绕不过去。
5. 疑难杂症排查实录与常用速查表
5.1 一张表搞清楚 Access Denied 的常见原因和对应解法
为了让你在踩坑现场能快速定位问题,我把最常见的场景做成了一个速查表。遇到 access denied 时,对着表格一条条自查,多数情况能在十分钟内解决。
| 现象特征 | 可能原因 | 快速解法 |
|---|---|---|
SSH clone 报 Permission denied (publickey) |
本地没有匹配的私钥,或公钥未配置到服务器 | 执行 ssh-keygen 生成密钥,把 .pub 内容粘贴到服务器 SSH Keys 设置里 |
| 多账号下 SSH clone 报错 | SSH 用了错误的密钥连接 | 配置 ~/.ssh/config,用 IdentityFile 指定每个域名对应的密钥 |
| HTTPS clone 输密码后被拒 | 平台已不支持密码认证 | 改用 Personal Access Token 填入密码框 |
| 私有仓库 clone 报 404 或 Access Denied | 当前账号不是仓库协作者 | 找仓库所有者添加你为 Collaborator,或加入对应 Group |
| Windows 上改完密码仍报错 | 凭证管理器缓存了旧凭证 | 删除 Windows 凭据管理器中 git:https://github.com 条目 |
| 同事用过的电脑 clone 报错 | 缓存了前一个账号的凭证 | 清掉凭证缓存,重新认证 |
| URL 看起来对但一直报错 | 仓库被转移或所有者改名 | 联系管理员要最新 clone 地址 |
| 公司网络下 clone 外网仓库报 403 | 代理拦截 | 检查并调整 http.proxy / https.proxy 配置 |
5.2 一个真实案例:同一个报错,三种不同解法
说一个让我印象非常深刻的项目经历。当时团队里有三个同事几乎同时遇到了 access denied,报错文本一模一样,都是 Permission denied (publickey),但最后解决方式完全不同。
第一位同事是新入职的,他的问题最简单——本地压根没有生成过 SSH 密钥,~/.ssh 目录都不存在。让他跑了一遍 ssh-keygen 再把公钥配上平台,两分钟就好了。
第二位同事的 ~/.ssh 目录里有一堆旧钥匙,是从以前的公司带过来的。他的问题是 SSH agent 默认加载了旧公司的私钥,GitHub 上配置的又是他自己新生成的公钥,两边对不上。解决方法是把新的私钥 ssh-add 进 agent,并在 ~/.ssh/config 里明确指定 IdentityFile。
第三位同事最惨,他在家用自己的电脑 clone 同一个仓库一切正常,到公司就报错。排查到最后发现,公司给他配的电脑上全局 Git 配置里还留着一个工作邮箱,而他 GitHub 账号绑定的邮箱不是这个,他访问的私有仓库又属于另一个组织,最终被平台的权限模型判定为“另一个账号”,于是拒绝访问。解决方法是把电脑上全局的 user.name 和 user.email 改成 GitHub 账号对应的信息,再重新认证。这个案例说明,access denied 表面上是技术问题,有时候背后是账号体系、权限模型和配置残留三者叠加的结果。
5.3 终极调试三板斧:verbose、空仓库、日志
如果速查表都试过了还是卡住,那就上最后的调试手段。Git 本身提供了详细的调试输出,只是很多人不会用。
第一板斧:给 SSH 加上 -v 参数,看它到底在用哪把钥匙连服务器。连续使用 -vvv 可以输出更详细的信息,能看到客户端和服务器握手过程中每一步的状态。
bash复制ssh -vT git@github.com
第二板斧:用一个全新的空目录做测试,排除本地仓库残留配置的干扰。有些仓库的 .git/config 文件里可能残留了一些特殊的认证配置,在当前目录里怎么改都没用,换个目录反而正常。
bash复制mkdir /tmp/test-clone
cd /tmp/test-clone
git clone https://github.com/username/repo.git
第三板斧:设置 Git 的 HTTP 调试输出,看 HTTPS 模式下 git 和服务器之间的实际交互情况。设置 GIT_TRACE=1 环境变量后,Git 会把底层的 HTTP 调用过程打印出来,包括请求头、响应状态码。这些信息对判断中间层问题特别有用。
bash复制GIT_TRACE=1 GIT_CURL_VERBOSE=1 git clone https://github.com/username/repo.git
如果调试输出的最后停在某个 HTTP 状态码上,比如 401 或 403,那问题就夯实了:服务器明确拒绝了这个请求。至于是凭证问题、权限问题还是代理问题,对照前面几个章节逐项排查即可。
5.4 一个容易被忽略的收尾步骤:clone 完之后的远端地址检查
很多问题在 clone 成功那一刻就被人遗忘了,但真正的坑往往在 clone 之后就埋下了。比如有人为了临时绕过认证,用带 Token 的 URL clone 了仓库,之后这个仓库的 remote.origin.url 就带着明文 Token。就算他后来清理了 shell 历史,Token 也已经在 Git 配置里落地了。
检查当前仓库的远端地址:
bash复制git remote -v
如果看到 URL 里带了 username:ghp_xxx@ 这种形式,立刻修正:
bash复制git remote set-url origin https://github.com/username/repo.git
这是我个人很有体会的一个细节。有一次帮同事排查后续的 push 权限问题,翻 git remote -v 才发现他 clone 时用的 URL 里带了账号信息,而且这个信息是绑定在他个人 Token 上的,他一离职整个仓库的认证就断了。把这个 URL 清干净之后,换成走凭证管理器的正常认证方式,后续再也没有出过类似问题。
写在最后的个人经验
实际工作中遇到 git clone access denied,我通常不急着改配置,而是先花一分钟整理信息:用的是什么协议、报错完整文本是什么、仓库是公有还是私有、当前账号在平台上有没有权限。这四件事问清楚,问题基本就定位了一半。
几个我反复跟团队强调的习惯,在这里也分享给你:第一,SSH 密钥不要轻易重新生成,先检查是不是配置问题;第二,HTTPS 模式下所有需要输密码的地方,优先用 Token 而不是账号密码;第三,多账号环境一定要维护好 ~/.ssh/config 和凭证管理器,靠临时改配置只能顶一时;第四,公司电脑交接入职离职时,第一时间清理或替换凭证缓存,避免上一个人的认证信息污染你后续的操作。
踩过的坑多了以后你会发现,Git 的认证链路其实很清晰:本地凭证 → 传输协议 → 服务器认证 → 仓库权限模型。只要这个链路上任何一个环节出了偏差,都会表现为 access denied。顺着链路一个个检查下去,所有问题都有解。
