Git 的 clone 命令看着简单但真正让人卡住的往往不是命令本身而是命令底层到底调用了哪个 ssh.exe、读了哪一份密钥、走了哪一条传输链路。这篇内容围绕 Git 基础里最容易被跳过的一环——Windows 上安装 OpenSSH以及用 HTTPS 或 SSH 协议克隆远程仓库时冒出来的各种报错——把整条链路拆开讲一遍。从协议选型、OpenSSH 的三种安装来源、密钥生成与加载到克隆实操、报错速查、长期维护全部落到可以直接照抄的命令上。刚接触 Git 的初学者能靠它把第一关过掉已经用了一段时间但一直被零散报错困扰的人也能在这里找到“为什么换个终端就失败”的答案。代码托管平台一律用example.com占位实际操作时替换成自己的仓库地址即可。1. 克隆失败到底卡在哪先把链路拆开1.1 一条 clone 命令背后的四层结构很多人把git clone当成一个原子操作失败就只盯着报错最后一行看这是排查效率最低的做法。这条命令实际上穿了四层最上层是 Git 的传输协议层决定走 HTTPS 还是 SSH往下一层是认证层HTTPS 靠凭据、SSH 靠密钥再往下是加密与握手层TLS 或者 SSH 会话最底下才是真正的网络连通性。任何一层出问题报错文案都长得不一样但很多人只记住了“克隆失败”这四个字。把层次分清楚之后排查顺序就自然出来了先确认网络能不能到对端端口再确认认证凭据对不对最后才怀疑协议版本、仓库权限这类问题。反过来从最上层猜往往会在无关的地方折腾半天。我见过有人因为证书问题改了三遍密钥也见过因为 PATH 里 ssh 用错而反复重装 Git 的都是层次没分清。这里有个容易被忽略的细节HTTPS 和 SSH 的失败表现完全不同。HTTPS 失败通常是“认证被拒”或者“证书不认”会明确告诉你是谁的问题SSH 失败则经常只给一句Permission denied (publickey)把服务端拒绝、本地没密钥、密钥没加载三种情况合并成同一句话。所以 SSH 排查必须靠 verbose 输出这一点后面会专门讲。1.2 HTTPS 与 SSH 的取舍逻辑选哪种协议不是喜好问题取决于几个具体条件。HTTPS 的优势是开箱即用、几乎不受网络环境限制、凭据由系统凭据管理器托管缺点是在自动化场景下容易卡在交互式输入上以及密码换令牌之后经常忘了更新。SSH 的优势是一次配置长期有效、适合脚本和无终端环境、传输开销略低缺点是对本机环境的要求高得多——你得有一个能用的 SSH 客户端、一对被服务端接受的密钥、一份正确的 config。我的实际选择标准是这样的如果只是临时拉一个仓库看看代码用 HTTPS三十秒搞定如果这台机器要长期做开发、要配多个仓库、要写自动化脚本那就老老实实把 SSH 配好一次投入换后面几年的省心。混合使用也完全正常同一台机器上 HTTPS 克隆的仓库和 SSH 克隆的仓库可以共存远程地址分别记录在各自的.git/config里互不干扰。需要提醒的是克隆完成之后远程地址是可以改的不用删仓库重来git remote -v git remote set-url origin gitexample.com:team/repo.git这条命令在切换协议时特别有用。有人克隆完之后发现 SSH 更好用直接把整个目录删了重新克隆白白浪费一次全量下载。改 remote 地址只动配置不动工作区也不会影响本地已有提交。2. Windows 上安装 OpenSSH先搞清楚你有几个 ssh2.1 三处可能存在的 ssh.exeWindows 上最容易踩的坑不是“没装 OpenSSH”而是“装了三份Git 用了不对的那份”。常见的位置有这么几处来源典型路径特点Git 安装包内置C:\Program Files\Git\usr\bin\ssh.exe随 Git 一起装好MSYS 环境路径处理略有差异系统可选功能C:\Windows\System32\OpenSSH\ssh.exe系统组件跟随系统更新行为最标准手动解压或包管理器自定义目录版本可控但需要自己维护 PATH先用一条命令看清楚现状where.exe ssh输出几行就说明系统里有几个 ssh 在排队。如果输出为空那就是真的一个都没装。如果输出两行以上就要关心顺序问题了——命令行里直接敲ssh时用的是排在最前面的那个而 Git 执行 clone 时用的可能又是另一个因为 Git 有自己的查找逻辑。这种“手敲能通、Git 不通”的诡异现象十有八九就是这里来的。我的建议是只保留一个主力 ssh然后用 Git 的配置显式指定它不要让 PATH 顺序去决定结果。显式指定只需要一行配置但能省掉大量玄学排查。2.2 安装步骤与验证命令系统可选功能方式是最省事的不需要下载任何安装包。先查状态Get-WindowsCapability -Online | Where-Object Name -like OpenSSH*输出里State为Installed就是已经装好了为NotPresent就需要装。安装客户端组件需要管理员权限的 PowerShellAdd-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0安装过程会联网拉取组件几十秒到几分钟不等取决于网络状况。装完之后重开一个终端窗口让 PATH 刷新然后验证ssh -V正常会输出类似OpenSSH_for_Windows_8.6p1, LibreSSL 3.0.2的版本信息注意-V是大写小写-v是 verbose 调试开关两个完全不同输错了会直接进入连接流程然后报连接失败很多人在这里被误导过一次。如果系统组件这条路走不通比如没有管理员权限、或者组件下载一直失败第二条路就是用 Git 安装包自带的那个。它其实早就在你的机器上了只是没人告诉过你 C:\Program Files\Git\usr\bin\ssh.exe -V能出版本号就说明可用。第三条路是用包管理器搜索 openssh 并安装官方客户端包适合需要精确控制版本、或者要做批量部署的场景。三条路选一条即可别三条都装——装多了只会让“到底用了哪个”这个问题变得更难回答。提示服务端组件sshd在个人开发机上基本用不到不要顺手把服务端也装上那会多开一个监听端口纯属增加攻击面。2.3 PATH 与调用优先级排查装完之后最值得花五分钟做的一件事是把“谁在用哪个 ssh”这件事钉死。先看 Git 实际调用的是哪个git config --get core.sshCommand没有输出说明 Git 用的是自己的默认逻辑。这个默认逻辑在不同版本、不同安装方式下并不完全一致所以更稳妥的做法是主动设置git config --global core.sshCommand C:/Windows/System32/OpenSSH/ssh.exe注意路径用正斜杠Windows 下 Git 的配置解析对反斜杠比较敏感正斜杠最安全。设置完之后可以验证一下生效情况GIT_SSH_COMMANDssh -v git ls-remote gitexample.com:team/repo.git这里GIT_SSH_COMMAND是临时覆盖优先级高于core.sshCommand用来做一次性调试非常方便不会污染全局配置。如果你在 verbose 输出第一行看到的是OpenSSH_for_Windows_8.6p1而不是 Git 自带的那一版说明指定生效了。还有一个容易踩的坑路径里带空格。C:\Program Files\Git\usr\bin\ssh.exe这种路径在没有引号的情况下会被截断报出来的错却是“找不到命令”或者“连接失败”完全联想不到是空格问题。所以要么用正斜杠加引号要么干脆换成系统自带那个不带空格的路径。3. 密钥体系生成、加载与多密钥管理3.1 生成密钥对的参数选择SSH 认证靠的是一对非对称密钥私钥留在本机公钥交给服务端。生成命令本身很简单难的是参数怎么选ssh-keygen -t ed25519 -C work-laptop-2024 -f $HOME/.ssh/id_ed25519_work-t指定算法。现在首选ed25519密钥短、签名快、安全性足够只有在对接一些老系统、明确只支持 RSA 的时候才退回去用-t rsa -b 4096。别再用默认的 2048 位 RSA 了那个长度在当下的算力面前已经偏薄。-C是注释会写在公钥末尾作用是在服务端那堆公钥列表里区分是谁的。这个字段一定要写点有意义的东西比如“机器名-用途-年份”。我见过一堆注释全是userlocalhost的公钥堆在同一个账号下最后要删某个失效密钥时完全分不清哪个是哪个。-f指定输出路径。默认会生成id_ed25519和id_ed25519.pub如果你只有一个身份用默认路径最省事因为 ssh 会主动去读它。如果你需要多个身份比如一个用于个人仓库、一个用于团队仓库就必须用不同的文件名否则会互相覆盖。生成过程中会问口令passphrase。这里我的建议是工作机上有磁盘加密的话可以留空图方便笔记本经常带出门、或者多人共用一台机器的务必设口令然后配合 ssh-agent 使用日常操作并不会变麻烦。留空的风险是一旦私钥文件被拿走对方可以直接以你的身份访问所有授权过的仓库。3.2 ssh-agent 与私钥加载设了口令之后每次连接都要输一遍这个体验确实劝退。ssh-agent 就是解决这个问题的启动一次把解密后的私钥加载进内存后续所有连接都由它代劳。Windows 上 ssh-agent 是一个系统服务先查状态Get-Service ssh-agent如果Status是Stopped、StartupType是Disabled就改成自动启动并拉起Set-Service ssh-agent -StartupType Automatic Start-Service ssh-agent这两条需要管理员权限。改完之后加载私钥ssh-add $HOME\.ssh\id_ed25519_work加载成功会提示Identity added。查看已加载的列表用ssh-add -l清空用ssh-add -D。这里有个非常典型的坑Git 自带的 ssh 和系统 ssh 各自有各自的 agent 通道。你用系统 ssh 加载了密钥但 Git 实际调用的是自带的那一份它连不上系统的 agent 服务结果就是“明明 ssh-add 过了Git 还是提示 Permission denied”。这个现象极其迷惑网上大量帖子把它归因为密钥权限问题其实是二进制不匹配。解决办法只有一个确保core.sshCommand指向的那个 ssh和你执行ssh-add时用的 ssh 是同一个。如果不想折腾 agent还有一个更朴素的办法——给私钥留空口令。安全性打折但对纯本地开发机来说可以接受至少把“密钥加载失败”这个变量彻底消掉了。先用空口令把链路跑通再回头加口令加 agent这是我一贯的调试顺序。3.3 config 文件写法规整当你有多个身份、多个托管主机时~/.ssh/config是必须写的东西。它把“用哪把钥匙连哪个主机”这件事从命令行里挪到配置文件里一次写好长期有效Host work HostName example.com User git IdentityFile ~/.ssh/id_ed25519_work IdentitiesOnly yes PreferredAuthentications publickey Host personal HostName example.com User git IdentityFile ~/.ssh/id_ed25519_personal IdentitiesOnly yes写完之后的克隆命令就变成了git clone work:team/repo.git短且不会记错。这个别名机制的价值在于一致性——不管换哪台机器只要把 config 拷过去命令就完全一样。IdentitiesOnly yes这一行值得单独说。不写它的话ssh 会把 agent 里所有密钥挨个试一遍遇到服务端限制尝试次数通常是 6 次密钥一多就会在试到正确的那把之前被踢掉报错还是Permission denied。加上这一行ssh 就只提供IdentityFile指定的那一把干净利落。这是多密钥环境下最常见、也最容易被忽略的一行配置。PreferredAuthentications publickey则是把认证方式锁死为公钥避免 ssh 去尝试键盘交互式认证在无终端环境下比如 CI 脚本能减少一类奇怪的挂起。Windows 上 config 文件的路径是C:\Users\用户名\.ssh\config注意没有扩展名用记事本保存时容易被自动加上.txt这是新手最常犯的错误之一。保存完用ssh -G work可以打印出解析后的最终配置确认段落有没有被正确识别。4. 两条链路的克隆实操4.1 HTTPS 链路凭据存储才是真正的坑HTTPS 克隆的第一次通常很顺利因为会弹窗让你输账号密码。真正出问题的是第二次、第三次以及改了密码之后。原因是 Windows 上 Git 默认启用了凭据管理器第一次输的凭据被存进了系统凭据库之后所有请求都用它即使你在网页上改了密码本地存的还是旧的。先确认当前用的是哪个凭据助手git config --global --get credential.helper输出通常是manager或manager-core。要清掉某条记录直接去 Windows 的“凭据管理器”里找“Windows 凭据”分类删除以git:https://example.com开头的那一条下次操作就会重新弹窗。命令行也能查看cmdkey /list | Select-String git另一种情况是根本不弹窗直接报错fatal: could not read Username for https://example.com: terminal prompts disabled这说明当前环境没有可交互的终端Git 无法提示你输入。常见于脚本、某些 IDE 的内置终端、以及被重定向输出的场景。解决方式是把凭据助手指向系统那个它会调 GUI 弹窗而不是走终端或者干脆改用带令牌的地址。用访问令牌替代密码是目前的标准做法格式上就是把令牌当密码填git clone https://username:tokenexample.com/team/repo.git这种方式能跑通但把令牌明文写在命令里会留在 shell 历史和.git/config里用完记得把 remote 地址改回不含令牌的形式。生产环境更推荐用环境变量或者凭据助手注入。还有一个高频报错是证书问题SSL certificate problem: unable to get local issuer certificateWindows 上最快的解法是让 Git 用系统证书库git config --global http.sslBackend schannelschannel是 Windows 自带的 TLS 实现直接读系统信任列表企业内网装的那些根证书它能自动认。用openssl后端的话需要单独指定 CA 包路径git config --global http.sslCAInfo C:/certs/ca-bundle.crt注意http.sslVerify false能一把关掉证书校验让报错立刻消失。但这是把安全边界整个拆掉只适合在隔离的测试环境里临时用绝不要在正式机器上留这个配置。4.2 SSH 链路首次连接与端口连通性SSH 克隆的第一次会遇到一段确认提示让你核对主机指纹The authenticity of host example.com cant be established. ED25519 key fingerprint is SHA256:xxxxxxxxxxxxxxxxxxxxxxxxxxx. Are you sure you want to continue connecting (yes/no/[fingerprint])?输yes之后指纹会写进~/.ssh/known_hosts后续不再询问。这一步不是障碍是设计——它防的是中间人。核对指纹这一步别跳过去托管平台的控制台页面把官方指纹拿来比对一下两分钟的事。如果服务端换了主机密钥或者你在known_hosts里存了错的记录就会看到Host key verification failed.清理掉旧记录再连ssh-keygen -R example.com这个命令比手动编辑 known_hosts 靠谱它会同时处理带方括号和端口号的变体格式。更麻烦的是端口不通ssh: connect to host example.com port 22: Connection timed out先去测端口别急着改密钥Test-NetConnection example.com -Port 22TcpTestSucceeded为False就是网络层到不了。这时候先换 HTTPS 试一下同样的仓库如果 HTTPS 能通、SSH 不通基本可以确定是 22 端口被拦了——很多办公网络和企业出口会屏蔽非标准业务端口。绕过办法是改用托管平台提供的备用端口通常是在配置里指定Host example-ssh HostName ssh.example.com Port 443 User git IdentityFile ~/.ssh/id_ed25519_work走 443 端口的 SSH 在多数受限网络里都能通因为 443 是业务流量必经端口一般不会被整体拦截。这不是什么特殊技巧是托管平台官方提供的常规能力。4.3 大仓库与中断场景的克隆策略仓库一大克隆就容易在半路断掉报错五花八门error: RPC failed; curl 56 OpenSSL SSL_read: Connection was reset fatal: the remote end hung up unexpectedly这类问题的本质是长时间大流量传输过程中链路的可靠性下降不一定有单点故障。有效的做法是从“减少传输量”和“提高单次传输成功率”两个方向同时下手。减少传输量最直接的是浅克隆git clone --depth 1 gitexample.com:team/repo.git只拉最近一次提交体积可能只有全量的百分之几。后续需要完整历史时再补git fetch --unshallow这个补拉过程同样是全量下载但可以在网络状况好的时候择机执行把一次长任务拆成两次短任务。另一个常见建议是调大http.postBuffergit config --global http.postBuffer 524288000但这里要说清楚它的真实作用范围这个参数只影响非分块chunked的 HTTP POST 缓冲现代托管服务端绝大多数走的是分块传输调它基本无效。网上大量“调了 postBuffer 就好了”的案例多数是巧合——重试了一次刚好成功。真要提升成功率不如强制走 HTTP/1.1git config --global http.version HTTP/1.1在某些链路上HTTP/2 的多路复用会遇到中间设备处理不一致的问题退回 1.1 后反而更可靠。这是一个用一行配置换确定性的典型场景。还有一类报错和大小无关纯粹是本地问题error: invalid path src/xxx error: unable to write file: No such file or directory在 Windows 上这通常是仓库里存在文件名包含非法字符如:*?、路径过长、或者仅有大小写差异的文件。解决靠开启长路径支持或者用稀疏检出绕过git config --global core.longpaths true git clone --no-checkout gitexample.com:team/repo.git cd repo git sparse-checkout init --cone git sparse-checkout set src/module-a git checkout先不检出工作区、把范围缩到需要的目录是处理超长路径和非法文件名仓库的通用手法。5. 报错速查与逐层定位手法5.1 认证类报错的现象与定位Permission denied (publickey)是 SSH 链路里出现频率最高、信息量最低的一句话。它把三种完全不同的原因压成同一个输出本机没有可用密钥、密钥提供了但服务端不认、服务端认了但账号没有该仓库权限。区分办法只有一个——打开 verbose。ssh -vT gitexample.com输出里要盯这几个关键行输出片段含义下一步Reading configuration data .../.ssh/config配置文件被读取检查段落名和缩进Offering public key: ... id_ed25519提供了某把公钥记住这把的指纹没有任何Offering行私钥没被找到查路径、查权限、查 agentAuthentications that can continue: publickey服务端拒绝了刚提供的公钥去服务端核对公钥是否上传Server accepts key后失败公钥验收但签名不通过通常是私钥损坏或口令错误最后一种情况很隐蔽公钥在服务端存在客户端也提供了对应的私钥但握手仍然失败。常见原因是私钥文件被改过、或者文件中混入了多余的空行和文本。用ssh-keygen -y -f 私钥路径可以重新导出公钥和.pub文件对比一致就说明私钥完好。Authentication failed for https://...则是 HTTPS 侧的同类问题区别在于它明确告诉你是认证失败而不是权限失败。处理顺序是先清系统凭据再确认账号密码或令牌是否已更新最后检查账号本身有没有被限制。5.2 传输与网络类报错的排查顺序网络类报错的迷惑性在于文案很吓人但根因往往很朴素。我习惯按这个顺序走一遍DNS 解析nslookup example.com看能不能解析出地址。端口连通Test-NetConnection example.com -Port 22或 443。TLS 握手curl -vI https://example.com看证书链是否完整。应用层git ls-remote 地址只做握手不做下载最快验证认证是否通过。第 4 步特别值得推荐。ls-remote只列出远端分支不传输任何对象几秒钟就能判断认证和地址是否正确。相比于反复clone一个几百兆的仓库来试错这个命令能把排查时间压缩一个数量级。git ls-remote gitexample.com:team/repo.git git ls-remote https://example.com/team/repo.git两条命令分别验证 SSH 和 HTTPS 链路谁通谁不通一目了然。这个手法我在排查“同一台机器一个仓库能拉一个不能拉”的时候用过很多次通常几秒钟就能定位到是地址写错还是权限缺失。还有一个常被误判成网络问题的现象Connection closed by 203.0.113.10 port 22这通常不是网络中断而是服务端主动断开。原因可能是单 IP 连接数超限、密钥尝试次数超限、或者账号触发了频率限制。等几分钟重试、或者减少IdentitiesOnly之外的密钥暴露往往就恢复了。5.3 一条命令打开调试开关Git 自己也有调试开关而且比大多数人想的更有用GIT_TRACE1 GIT_CURL_VERBOSE1 git clone https://example.com/team/repo.gitGIT_TRACE会打印出 Git 实际执行的子进程和参数这是确认“到底调用了哪个 ssh”的最直接证据。你会看到类似这样一行run_command: ssh -o SendEnvGIT_PROTOCOL gitexample.com git-upload-pack ...从这一行能读出两件事一是用的确实是 ssh二是传了什么参数。如果-o SendEnv之类的参数和你预期不符说明有环境变量或配置在干扰。GIT_CURL_VERBOSE1则把 HTTP 层的请求头、响应头、TLS 握手细节全打出来。证书问题的根因基本都能从这里看出来——看SSL certificate verify那几行能明确是链不完整、还是域名不匹配、还是过期。这两个变量都是临时的只对当次命令生效不会写进配置用完就没了。排查完记得确认没有把它们写进 shell 的启动脚本里长期开着会拖慢所有操作的速度。6. 实操心得与长期维护清单6.1 踩过的坑与对应做法第一个坑是“换了台机器全都不能用”。根因往往是只拷了id_ed25519没拷config或者拷了 config 但里面的IdentityFile还是旧的用户目录路径。正确的迁移方式是整个.ssh目录打包带走落地后检查三件事文件名没变、路径没变、权限没变。Windows 上权限这一项特别容易出问题从共享盘或者 U 盘拷过来的文件ACL 可能带着原始机器的用户信息导致 ssh 判定“权限过宽”而拒绝使用私钥Permissions for id_ed25519 are too open.修法是重设 ACL只给当前用户读权限icacls $HOME\.ssh\id_ed25519 /inheritance:r icacls $HOME\.ssh\id_ed25519 /grant:r $($env:USERNAME):R第二个坑是“只有某个 IDE 里不能用”。IDE 的内置终端通常继承的环境变量和系统终端不完全一致HOME变量指向不同会导致.ssh目录找错。验证方法是在两个终端里分别执行echo $HOMEPowerShell 里是$HOMEcmd 里是%USERPROFILE%不一致就是它。第三个坑是“昨天还能用今天不行了”。这种情况先别改配置先确认三件事私钥文件在不在、agent 里还有没有、服务端公钥有没有被清。我遇到过最无语的一次是系统更新之后 ssh-agent 服务的启动类型被重置回Disabledssh-add加载的密钥在重启后全丢表现为“突然就不认密钥了”。现在我的习惯是在换机或大版本更新之后主动跑一遍Get-Service ssh-agent确认状态。第四个坑和换行符有关。克隆下来的文件全带CRLF提交时提示一堆警告。这不是错误但会影响跨平台协作git config --global core.autocrlf trueWindows 上提交时转 LF、检出时转 CRLF这条配置能消掉绝大部分换行符噪音。仓库里如果有.gitattributes以它为准全局配置只在没有仓库级规则时生效。6.2 换机与多环境下的配置迁移把 SSH 配置当作一项需要维护的资产来对待长期看能省很多事。我的做法是维护一份最小化的config模板加上一份密钥清单说明放在私有的安全位置。模板里只保留结构性内容Host * IdentitiesOnly yes ServerAliveInterval 30 ServerAliveCountMax 3 Host work HostName example.com User git IdentityFile ~/.ssh/id_ed25519_workHost *段里的两个保活参数很实用。长时间没有数据传输的 SSH 会话会被中间设备静默掐断表现为“克隆到一半卡死然后超时”。ServerAliveInterval 30每 30 秒发一个心跳ServerAliveCountMax 3允许连续三次无响应才断开这两行能让大仓库克隆的成功率明显提升。还有一个习惯值得养成给每台机器的密钥写不同注释并且在服务端的公钥列表里定期清理。密钥是有生命周期的离职、换机、设备丢失都需要撤销对应的那一把。全用同一个注释的话事后根本不知道哪把该删。我现在统一用“设备名-用途-年月”的格式清理时一眼就能看出哪些已经过期。最后说一个诊断思路上的体会。Git 的报错信息质量参差不齐有的精确到文件行号有的只有一句Permission denied。遇到后者不要在原命令上反复重试而是换一条信息量更大的命令——ssh -vT看认证过程git ls-remote验证地址和权限GIT_TRACE1看 Git 到底调了什么。这三条命令覆盖了绝大多数场景熟练之后定位一个问题基本不会超过十分钟比在搜索引擎里翻几十个帖子快得多。
阅读完成 · 觉得有帮助?