1. 从一次"克隆失败"说起:问题到底出在哪一环
如果你也经历过这种场景:在终端里敲下git clone,本来等着代码哗啦啦落盘,结果屏幕上弹出一整段刺眼的红色报错,那一刻的挫败感,做过版本控制的人都懂。我最近就因为在 GitHub 上克隆一个新仓库,把账号密码、Token、SSH Key 这条路从头到尾走了一遍。过程不算顺利,但正因为卡了壳,我才把之前几个一直似懂非懂的点彻底弄明白了。这篇排障实录不打算写成教科书,就按我当时排查的真实顺序,把每一步为什么这么做、报错到底在说什么、怎么修,原原本本记下来。如果你也遇到过类似问题,可以直接对照自己的情况抄作业。
1.1 事发当天的完整报错记录
当时我拿到一个仓库地址,第一反应就是用 HTTPS 方式克隆。仓库本身是内部协作项目,URL 长这样:
https://github.com/some-org/example-repo.git命令敲下去之后,终端开始正常地询问用户名和密码:
$ git clone https://github.com/some-org/example-repo.git Cloning into 'example-repo'... Username for 'https://github.com': myusername Password for 'https://myusername@github.com':到这里一切都还很"正常",直到我输入了 GitHub 的登录密码并且按下回车。接下来的输出让我愣了一下:
remote: Support for password authentication was removed on August 13, 2021. Please use a personal access token instead. remote: Please see https://github.com/settings/tokens for more information fatal: Authentication failed for 'https://github.com/some-org/example-repo.git/'我当时的第一个反应是:这仓库的权限有问题?还是我密码记错了?于是我反复确认用户名、重新输入密码,结果还是一模一样的报错。这种"看起来是密码错,但你知道密码没错"的体验,我相信不止我一个人碰到过。其实问题根本不在密码,而在于这个年代已经不允许你拿密码去执行 Git 操作了。
1.2 我第一次排查时理解的偏差
当时我对 Git 鉴权的理解还停留在"HTTPS 就是账号加密码,SSH 就是密钥"这个层面。所以一报错,我自然以为是仓库权限、网络、或者密码大小写的问题。实际上,这条报错已经说得很直白了:"Support for password authentication was removed"——意思是这个通道本身已经被关闭,而不是你的凭证写错了。
这个理解偏差其实很典型。很多人搭建 Git 环境的时间不同,接触到的默认行为也不同。早几年 GitHub 确实允许直接用账号密码去拉取和推送,网上大量教程也是这么教的。但当平台策略调整之后,那些旧教程就成了误导源。这次排障给我的第一个教训就是:遇到鉴权类报错,先读报错正文里"remote:"开头的那几行,平台通常会把真正的原因直接告诉你,而不是盯着"fatal"那一行瞎猜。
2. 为什么账号密码这一套彻底走不通了
2.1 密码认证被移除的来龙去脉
GitHub 从 2021 年 8 月 13 日开始,全面移除了对账号密码的 Git 操作认证。也就是说,你在克隆、推送、拉取时输入的那个"密码",已经不再是你的账号密码本身,而必须是一个叫 Personal Access Token(个人访问令牌)的东西。平台还给了宽限期,但到期之后就是硬性关闭,无论你密码输得多熟练,都不会再被接受。
为什么要把密码从这套流程里踢出去?核心原因是安全模型的问题。你想想,如果所有 Git 操作都用同一个账号密码来做,那这个密码一旦泄露,等于把整个账号的读写权限、个人信息、私有仓库、甚至后续操作的入口全交出去了。而且密码是长期有效的,你很难在发现泄露之后及时止损。令牌的思路完全不同:它可以单独生成、单独设置权限范围、单独设定过期时间,还能随时一键撤销。哪怕某个令牌泄露,损失的只是这一个令牌对应的那部分权限,其他功能不受影响。这和你用备用钥匙开一扇门、丢了大不了换那把锁,是同一个逻辑。
说白了,这不只是某个平台的行为,而是整个行业的趋势。你去看看其他代码托管平台、云服务商的开发接口,几乎都在朝"细粒度凭证 + 短期有效性"这个方向走。Git 本身只是一个客户端工具,它不关心你给它的是密码还是令牌,只要凭证能被服务端认可就行。所以问题从来不是"Git 怎么了",而是"GitHub 的认证策略变了"。
2.2 Personal Access Token 的正确生成姿势
如果你暂时不想折腾 SSH Key,那最快的解法就是生成一个令牌来替代密码。操作路径如下:
- 登录 GitHub 后,点击右上角头像,进入 Settings。
- 在左侧菜单最底部找到 Developer settings。
- 进入 Personal access tokens,我建议直接用 Fine-grained tokens 按需创建,但很多人第一次接触时用 Tokens (classic) 更容易上手。
- 点击 Generate new token,给它起一个能辨认用途的名字,比如"laptop-2025-clone"。
- 在权限勾选里,如果只是克隆公开或私有仓库,勾选
repo范围就够;如果要推送代码,同样需要repo;如果涉及到 Actions 相关操作,才需要workflow权限。 - 点击生成之后,页面只会给你展示一次令牌内容,形如
ghp_开头的一长串字符。复制并保存好,一旦关掉页面就再也看不到了。
使用方式有两种。第一种是临时性的:在 Git 提示输入密码时,不输账号密码,而是把用户名填上你的 GitHub 用户名,密码处粘贴这个令牌。第二种是长期性的:把令牌交给 Git 的凭据管理器保存,之后就不用重复输入了:
$ git config --global credential.helper store执行之后再克隆一次,输入一次令牌,Git 就会把凭证以明文形式存在~/.git-credentials文件里。注意这里是明文,所以个人电脑上要保证这个文件不被别人读到。更稳妥一点的做法是用系统自带的凭据管理器,比如 macOS 的 keychain 或 Windows 的 Credential Manager,可以通过credential.helper osxkeychain或 Windows 自带的 Git Credential Manager 来配置。
2.3 用 Token 时最容易踩的三个坑
第一个坑是权限勾选不够。比如只勾了read类权限就去推代码,克隆能成功,但 push 的时候会被拒绝。报错可能会让你误以为是网络问题或分支保护规则,实际上只是令牌权限不够。我的经验是:拿不准的时候,先按最小够用原则来,克隆就只克隆,推送就把repo勾上,然后缺什么补什么。
第二个坑是令牌只能看一次。不少人生成完直接复制到终端,关掉页面之后找不到备份,只能重新生成一个。这不算什么致命问题,但会打断思路。我的习惯是生成之后立即存到本机密码管理器里,命名带上下次提醒,比如"GitHub-clone-token 有效期30天"。
第三个坑是忘记看有效期。经典令牌可以设置永久有效或者最长一年,细粒度令牌往往要手动设过期时间。过期之后 Git 会报 401 或者直接提示认证失败,很多人的第一反应是"是不是被封号了",实际上只是令牌到期。所以排查时可以把令牌有效期作为一个重点怀疑对象。
到这里,账号密码这条线算是彻底弄明白了。但我在实际操作中很快发现另一个问题:Token 方案虽然能用,可我每天要频繁执行 push、pull 操作,每次都要操心令牌的权限和有效期,总觉得不够顺手。于是我把目光转向了 SSH Key——也就是这次排障的最终归宿。
3. 切换 SSH Key 之前的方案对比与原理补课
3.1 HTTPS 与 SSH 两种克隆协议怎么选
既然要切换方案,那就先得把两者放在一起比一比。很多新手只知道"能用就行",但选错协议会直接影响后续的使用体验和维护成本。
| 对比维度 | HTTPS 克隆 | SSH 克隆 |
|---|---|---|
| 克隆地址示例 | https://github.com/org/repo.git | git@github.com:org/repo.git |
| 认证方式 | 用户名 + Token | 公钥/私钥对 |
| 首次配置成本 | 低,生成 Token 即可 | 略高,要生成密钥并登记公钥 |
| 日常使用体验 | 可能反复输凭证,或依赖凭据管理器 | 配置一次后基本免密 |
| 凭证失效风险 | Token 会过期、需要续期 | 私钥不换就一直有效 |
| 安全粒度 | 令牌可按权限、按仓库授权 | 一个私钥通常对所有可见仓库生效 |
从表格可以看出,HTTPS 的优势是首次成本低,适合偶尔用一次、或者在一台临时机器上快速拉取代码。而 SSH 的优势是"一劳永逸",配置好之后几乎感受不到认证过程的存在。
我当时选择 SSH,还有一个更实际的原因:我的日常操作里有大量命令和脚本,它们会以非交互方式执行git push。HTTPS 模式下如果令牌过期或者没存进凭据管理器,脚本就会卡在认证提示那一步。而 SSH 只要私钥在、权限对,脚本执行起来从来不用操心凭证问题。如果你也有大量自动化脚本,SSH 几乎是必选项。
当然,SSH 也有自己的注意点:私钥就是一串能证明你身份的凭证,谁拿到它谁就能冒充你操作仓库。所以私钥本身的保管比令牌还要上心,后面我会专门说到权限设置。
3.2 公钥私钥对是怎么完成"免密"认证的
在没有真正配过 SSH Key 之前,我一直以为它就是"把两个文件放好,Git 自己认人"。后来仔细看了一遍它的工作流程,才发现它背后是一套非常经典的非对称加密原理,只是 Git 把它封装得让人无感了。
非对称加密的特点是:密钥不是一把,而是一对,分为公钥和私钥。公钥可以随便发给别人、放在服务器上;私钥必须自己藏好,绝不能外传。这两把钥匙的关系可以简单理解成:用公钥加密的内容,只有对应的私钥能解开;反过来,持有私钥的人能证明自己"拥有这把私钥",而验证方只需要用公钥做一次校验,不需要知道私钥本身。
放到 GitHub 场景里,整个认证流程是这样的:你在本地生成一对密钥,把公钥内容粘贴到 GitHub 的设置页面。之后每次执行git clone或git push,Git 会通过 SSH 协议连接 GitHub。服务器看到一个连接请求,不会直接拒绝,而是抛出一道"证明题":你是不是真的持有与这个公钥配对的私钥?Git 客户端会调用本地私钥完成签名,然后把签名结果发给服务器。服务器用之前登记的公钥去验证签名,验证通过,就放行。整个过程里,私钥始终没有离开过你的机器,网上传输的只是一次性的签名验证数据。
这样设计的精妙之处在于:你不需要给服务器提供任何"密码",服务器也不存储你的私钥,它只靠公钥做数学校验。即使公钥被人偷走,也无法反推出私钥,更无法冒充你。这也是为什么公钥可以大大方方地贴在 GitHub 上,而私钥文件看一眼都要紧张的原因。
原理补完课之后,剩下的就是动手。我当时以为 SSH 的配置比 Token 复杂得多,结果真正操作下来,反而比想象中顺畅。下面把完整流程一步一步拆开写。
4. SSH Key 生成、登记与首次连接的全流程
4.1 密钥算法选型:ed25519 对比 RSA
生成密钥之前,首先要选算法。网上老教程基本都让你用 RSA,并且强调位数要到 4096。但在我个人看来,新环境直接上 ed25519 才是更合理的选择。
ed25519 是目前 OpenSSH 默认推荐的算法之一,它的优势有几个:密钥长度短、生成速度快、安全性高,并且从 OpenSSH 6.5 开始就被支持。在 GitHub 官方文档里,ed25519 也是被明确推荐的选项。RSA 4096 的优势主要是兼容性——一些特别老的服务器、交换机或者旧系统可能只认 RSA。但如果你只是正常连 GitHub,不涉及那些远古环境,ed25519 完全够用。
判断方法很简单:你在终端执行ssh -V看版本号,如果 OpenSSH 版本是 7.0 以上,就可以放心用 ed25519。除非你明确知道自己需要兼容某台老设备,否则别在 RSA 上纠结。
4.2 一次完整的 ssh-keygen 实操记录
生成密钥的命令总共就一行,但有些参数值得解释一下:
ssh-keygen -t ed25519 -C "your_email@example.com"-t指定算法类型,这里填的是 ed25519。-C是给密钥加一条备注信息,通常填邮箱,也可以填任何能帮你识别这把钥匙来源的文字,比如laptop-2025。这个备注会写进公钥文件的末尾,纯粹是给自己看的,不影响认证结果。
执行之后会问你要把密钥保存在哪里,默认路径是~/.ssh/id_ed25519。如果这是你的第一把密钥,直接回车用默认路径就行。如果你已经有一把密钥了,那要手动改成新名字,比如~/.ssh/id_ed25519_work,避免覆盖旧密钥。
紧接着它会问你是否设置 passphrase(口令短语)。这一步我强烈建议不要留空,尤其是用在笔记本之类的移动设备上。passphrase 相当于给私钥加了一道本地保险:即使有人偷走了你的私钥文件,没有这个口令也解不开。当然它也有代价——每次 SSH 连接都可能让你输入一次口令,这时候就需要 ssh-agent 来缓解,后面细说。
实操输出大致长这样:
$ ssh-keygen -t ed25519 -C "dev@example.com" Generating public/private ed25519 key pair. Enter file in which to save the key (/home/you/.ssh/id_ed25519): Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /home/you/.ssh/id_ed25519 Your public key has been saved in /home/you/.ssh/id_ed25519.pub The key fingerprint is: SHA256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx dev@example.com执行结束后,~/.ssh目录下会出现两个文件:id_ed25519是私钥,id_ed25519.pub是公钥。私钥文件必须放在自己手里,公钥文件则随时准备给别人看。注意一下,如果你用 Windows,默认路径通常在C:\Users\你的用户名\.ssh\下,逻辑完全一样。
4.3 公钥登记与 ssh -T 首次验证
公钥生成之后,要用cat命令把它读出来并复制:
cat ~/.ssh/id_ed25519.pub输出的内容是一长串以ssh-ed25519开头的文本,结尾是你的备注。把这整行复制下来,然后回到 GitHub 的 Settings → SSH and GPG keys → New SSH key,标题随便填,比如"我的主力电脑",Key 类型就选 Authentication Key,然后把刚才复制的内容粘贴进去,保存。
登记完成之后,关键的一步来了:测试连接。GitHub 给的官方测试命令是:
ssh -T git@github.com我第一次执行时其实有点紧张,担心又冒出一堆看不懂的输出。结果正常情况下它返回的是:
The authenticity of host 'github.com (xx.xx.xx.xx)' can't be established. Are you sure you want to continue connecting (yes/no/[fingerprint])?这里输入yes回车。它的意思是:你本机之前没连过 GitHub,不知道这台服务器的指纹是否可信,问你认不认。输入yes之后,GitHub 会把服务器公钥写进本机的~/.ssh/known_hosts,以后就不会再问了。跳过这一步直接选no的话,连接会被中断。
继续往下,如果一切配置正确,你会看到:
Hi yourusername! You've successfully authenticated, but GitHub does not provide shell access.这句话看起来有点怪,但它是成功的意思。GitHub 那个 SSH 通道只用来跑 Git 操作,不给你开交互式终端,所以它专门加了一句"不提供 shell 访问"。看到这句话,就说明密钥认证已经没问题了,接下来把克隆地址换成 SSH 格式:
git clone git@github.com:some-org/example-repo.git这次不会再弹出任何认证提示,代码直接开始落盘。那一刻,前面卡了一个下午的挫败感基本就消掉了。
4.4 多 GitHub 账号场景下的 config 配置
如果你只用一个 GitHub 账号,上面的流程已经足够。但对不少开发者来说,工作和个人用的是两个账号,甚至还有公司内网的 GitLab。这时候就会出现一个问题:~/.ssh/id_ed25519只有一把默认私钥,Git 连接 GitHub 时默认只会用它,那另一个账号的公钥怎么办?
解法是在~/.ssh/下创建一个 config 文件,手动指定不同域名或别名对应不同私钥。先按前面的方法生成第二把密钥,命名为id_ed25519_personal,然后把公钥登记到个人账号下。接着创建配置:
Host github.com HostName github.com User git IdentityFile ~/.ssh/id_ed25519 IdentitiesOnly yes Host github-personal HostName github.com User git IdentityFile ~/.ssh/id_ed25519_personal IdentitiesOnly yes这一段里最关键的参数是IdentitiesOnly yes。它的作用是告诉 SSH:访问这个 Host 时只尝试列表里指定的私钥,不要把所有私钥都轮询一遍。如果没有这个参数,SSH 会把你~/.ssh下的所有密钥挨个拿给服务器试,GitHub 端只要发现某个公钥匹配,就按那个账号的身份放行——这很容易导致你用个人私钥连上了工作仓库、身份却对不上。
配置完成之后,克隆工作仓库时用默认的git@github.com:org/repo.git,克隆个人仓库时把地址稍作修改,变成:
git clone git@github-personal:yourname/another-repo.git这个别名机制在处理"双账号"时非常实用。我排障排到这一步时,其实还发现了一个更隐蔽的问题:就算密钥全配对了,某些环境下的 SSH 连接依然会失败,而且报错信息五花八门。于是整个排障进入第五个阶段——系统性梳理那些藏着掖着的坑。
5. 换到 SSH 之后仍然失败的排障记录
5.1 高频报错与对应处理速查表
就算你严格按照上面的流程走,SSH 连接仍然有可能失败。我自己在配置过程中就遇到了好几次,而且每次报错都长得很像。为了方便对照,我把高频报错整理成了一张速查表,排查时按图索骥比从头翻日志要快得多。
| 报错信息 | 核心原因 | 对应处理 |
|---|---|---|
Permission denied (publickey) | 服务端没有对应公钥,或客户端没提供正确私钥 | 确认公钥已登记;确认用的是正确私钥;确认IdentitiesOnly配置正确 |
git@githb.com: Permission denied (publickey) | 克隆地址里的域名拼写错误 | 检查 URL 是不是多了一个字母,改成git@github.com |
Host key verification failed | 本机 known_hosts 没有该主机指纹,或指纹已更新 | 首次连接输入 yes;若之前连接过,删除 known_hosts 里的旧记录重新连接 |
ssh: connect to host github.com port 22: Connection refused | 防火墙或网络环境屏蔽了 22 端口 | 改用 443 端口连接 SSH,配置见下文 |
Agent admitted failure to sign | ssh-agent 里没有加载对应私钥 | 执行ssh-add ~/.ssh/id_ed25519重新加载 |
Load key "/home/you/.ssh/id_ed25519": error in libcrypto | 私钥文件损坏或加密方式不被兼容 | 检查私钥文件是否完整,必要时重新生成 |
这六类是我见过最多的。前两类属于配置问题,后几类分别牵涉端口、known_hosts 和密钥代理,每一个都值得单独展开说。
5.2 权限设置、known_hosts 与 ssh-agent 的隐藏坑
先说权限。很多人在 Linux 或 macOS 上配置 SSH 时报Permission denied (publickey),排查下来发现原因居然只是~/.ssh目录或私钥文件的权限太开放。SSH 出于安全考虑,要求私钥文件不能被其他用户读写。正确权限是:目录~/.ssh是 700,私钥文件是 600,公钥文件是 644。
chmod 700 ~/.ssh chmod 600 ~/.ssh/id_ed25519 chmod 644 ~/.ssh/id_ed25519.pub这个坑很像什么?就像你家的门锁本身没坏,但你把钥匙挂在了门外谁都看得到的地方,安保系统直接判定这户不安全。修好权限之后,报错瞬间消失。
再说 known_hosts。这个文件记录了你曾经连接过的主机指纹,作用就是防止中间人攻击。如果你连接时遇到Host key verification failed,一般有两种可能:要么这是你第一次连这台主机,要么 GitHub 服务器的密钥指纹真的变了。前者输入yes即可;后者需要把旧记录删掉。查找和删除的命令如下:
ssh-keygen -R github.com执行之后,known_hosts 里所有指向 GitHub 的旧记录都会被移除,下次连接时会重新写入并询问一次。
然后是 ssh-agent。前面我提到私钥可以设置 passphrase,但每次连接都输入一次确实烦人。ssh-agent 就是来解决这个矛盾的:它启动后常驻后台,你用ssh-add把私钥加载进去,第一次需要输入 passphrase,之后只要 agent 还活着,SSH 就会自动调用它完成签名,不再重复询问。
eval "$(ssh-agent -s)" ssh-add ~/.ssh/id_ed25519macOS 上还可以配合钥匙串访问,把 passphrase 存入钥匙串:
ssh-add --apple-use-keychain ~/.ssh/id_ed25519要注意的是,如果重启电脑,agent 会清空已加载的密钥。要是此时执行 Git 操作报Permission denied,往这个方向想往往比去改 GitHub 设置要靠谱得多。
5.3 用 ssh -v 从逐行日志里定位根因
如果速查表没有覆盖你的情况,那就要拿出最后的排查手段:抓详细日志。
ssh -vT git@github.com-v参数会让 SSH 输出整个握手过程的所有细节,比正常模式啰嗦得多,但每一次失败的原因都藏在里面。我自己的习惯是先跑一遍,然后把输出里几个关键行找出来。
第一类关键行是debug1: Offering public key。这表示客户端正在尝试把某个私钥提交给服务器。如果你看到这一行后面紧跟着debug1: Authentications that can continue: publickey,说明服务器拒绝了这个私钥对应的公钥——大概率是公钥没登记,或者登记在别的账号下了。如果连Offering这行都看不到,说明客户端根本没想要用私钥,问题可能出在 config 文件的 IdentityFile 路径或者 Host 匹配上。
第二类关键行是debug1: Server accepts key。看到这行说明服务器已经认可了你的公钥,后面的流程会直接走向认证成功。如果卡在这一行之前,那就可以确信是密钥匹配层面的问题,而不是网络问题。
第三类关键行是连接阶段的连接状态,比如debug1: Connecting to github.com port 22。如果这里出现超时或者Connection refused,那问题就不在密钥,而在网络或端口上。对于端口被屏蔽的情况,GitHub 官方早就提供了一个绕开 22 端口的方案:连接ssh.github.com的 443 端口。可以在~/.ssh/config里这样写:
Host github.com HostName ssh.github.com Port 443 User git IdentityFile ~/.ssh/id_ed25519443 端口是 HTTPS 的标准端口,大多数防火墙都会放行,所以这种方法在公司网络和校园网环境下特别管用。我遇到过不止一次,换了 443 端口之后原本死活连不上的 SSH 一下就通了。这种问题在出差、连公共 WiFi 时尤其常见,值得记在心里。
6. 这轮排障过后我养成的几个 Git 使用习惯
技术问题解决之后,真正沉淀下来的其实是几个使用习惯。这次从账号密码到 SSH Key 的完整排障,让我意识到很多"卡壳"并不是因为某个单独环节有多难,而是因为认知还停留在旧版本里。这里分享几个我事后认真思考过的习惯,也算是这次排障的延伸收获。
第一个习惯是:新机器上默认先配 SSH,而不是等着 HTTPS 提示输入密码。哪怕只是临时拉一个仓库,我也会先把公钥登记好。配置一次的成本大约三分钟,换来的是之后所有 Git 操作都不再被认证流程打断。如果只是偶尔在一台公用机器上拉个代码,那我会用 HTTPS 加短时效 Token,用完就撤销,绝不在公共机器上保存任何默认私钥。
第二个习惯是:给私钥设置 passphrase,并用 ssh-agent 管理,而不是图省事直接留空。特别是笔记本这种容易丢失的设备,私钥文件只要落进别人手里,没有 passphrase 就等于裸奔。虽然每次开机要ssh-add一次有点麻烦,但和泄露仓库代码的风险比起来,这点麻烦完全不值一提。
第三个习惯是:定期检查本机所有已登记的 SSH 公钥和 Token。GitHub 的设置页面里可以看到所有生效的密钥和令牌,每隔一段时间翻一遍,把自己已经不用或者不再信任的凭证删掉。公钥和 Token 更像是钥匙,钥匙太多,哪天丢了哪把你都不知道,这才是真正让人夜里睡不安稳的事。我在这次排障之后,就把一台旧机器上遗留的一把三年没用过的公钥彻底清理了。
最后一个建议其实很简单:遇到任何鉴权报错,先花 30 秒看一眼完整报错的正文,再动手去查配置。Git 和 GitHub 的报错信息比大多数软件都要友好,它们通常已经告诉你该去哪里改、怎么改。真正浪费时间的,往往是我们凭借旧经验去猜,而不是相信眼前已经写得很清楚的提示。把这套流程走完一遍之后,再遇到类似问题,基本不会再慌了。