SSH配置全流程:解决Git克隆时的Permission denied报错
如果你曾经在某仓库页面点开绿色的 Clone 按钮复制了一串以gitgithub.com:开头的地址然后直接粘贴到终端回车大概率会撞上一堵名为Permission denied (publickey)的墙。我见过不止一位朋友卡在这个报错上第一反应是怀疑自己的网络有问题或者干脆认为 Git 装坏了。其实问题远没有这么玄十有八九是整条 SSH 认证链路里某一个环节没有打通。这篇文章想做的事情很具体带你从零开始把 SSH 配置、公钥添加、仓库克隆这条完整链路走通。内容完全围绕 Git 与 GitHub 入门阶段的真实操作场景不涉及源码级实现也不打算丢一堆命令让你死记硬背而是把每一步背后“为什么要这样做”讲清楚。适合刚开始接触 Git 和 GitHub、想把本地代码与远程仓库连接起来的新手也适合那些被Permission denied反复折磨、想彻底弄懂原理的朋友。在动手之前可以先建立一个整体判断从 SSH 配置到克隆仓库整个流程实际上只有四个环节——本地生成密钥、把公钥交给 GitHub、验证连接、执行克隆。任何一个环节出错都会有对应的报错。你只要理解每个环节在做什么排错就会变得非常轻松。1. 先弄清边界Git 与 GitHub 的分工以及 SSH 在协作中的作用1.1 Git 管的是本地历史GitHub 管的是远程交换很多新手对 Git 和 GitHub 的关系是模糊的以为它们是一回事。其实 Git 是安装在你自己电脑上的一套版本控制工具它负责记录项目里每一个文件的改动历史所有数据都存在项目根目录下的.git文件夹里。你执行一次git commit本地就多一个提交快照你想回退到某个版本文件就能恢复到历史状态。这个过程完全不需要联网也不需要任何远程平台参与。GitHub 则是建立在 Git 之上的代码托管平台它的核心价值是让“异地协作”和“备份”成为可能。你把本地的提交推送到 GitHub别人就能拉取别人提交了新代码你也能同步回来。在 GitHub 上的一切操作本质上都是围绕“本地 Git 仓库”和“远程仓库副本”之间的数据交换。所以你可以这样理解Git 是引擎负责管理版本历史GitHub 是停车场负责接收和分发代码。两者用一套共同的语言沟通这套语言的基础就是 Git 协议。1.2 SSH 在“克隆”这个动作里到底做了什么当你执行git clone的时候Git 会向远程服务器发起一次数据交换请求。如果走 HTTPS 协议你每次推送代码都可能被要求输入用户名和密码现在 GitHub 通常推荐使用个人访问令牌替代密码如果走 SSH 协议服务器的验证方式就变成了基于密钥的身份证明。SSH 的核心逻辑可以理解成一把锁和一把钥匙。你本地生成一对密钥私钥留在自己电脑里公钥交给 GitHub。当你的 Git 客户端连接 GitHub 时服务器会验证你是否持有与公钥匹配的私钥。验证通过服务器就直接放行不需要每次输入密码。这个机制的好处是安全又便捷坏处是一旦私钥和公钥没有配对成功你就会收获那个非常经典的Permission denied报错。这个“公钥在上、私钥在下”的模型是整个配置的核心。你后面遇到的所有连接问题几乎都可以回到这里来思考远端有没有我的公钥本地有没有对应的私钥SSH 客户端有没有找到这把私钥1.3 为什么入门阶段最推荐先打通 SSH如果你只是下载公开仓库用 HTTPS 克隆其实也很快甚至不需要任何配置。但入门之后你必然会遇到推送代码、创建自己的仓库、和队友协同这些场景这时候如果每次都处理凭据就会特别烦躁。SSH 配置虽然会多花十分钟但它是一次性投入之后所有 Git 操作都顺畅得多。这也是为什么大量团队文档和教程默认使用 SSH 方式沟通。当然SSH 也不是银弹。内网环境、特殊网络策略、或者是某些托管平台的限制都可能让你转而使用 HTTPS。但作为入门路径先把 SSH 这条最通用的链路打通后面的路会好走很多。2. 本地环境准备安装 Git 与用户信息配置中的细节2.1 不同系统下的安装方式与版本确认动手配置之前得先把 Git 装好。不同操作系统各有各的安装路径这里把主流方式都列出来Windows 用户可以直接下载 Git for Windows 官方安装包一路 Next 就好。也可以用winget install --id Git.Git -e在终端里安装。macOS 用户如果装了 Homebrew可以执行brew install git没装 Homebrew 就下载官方安装包但要注意官方 pkg 安装的版本可能不是最新的。Linux 用户用发行版自带的包管理器即可比如 Debian 系的sudo apt install gitRed Hat 系的sudo dnf install git。装完之后打开终端输入git --version能打印出版本号就说明基础环境没问题。这里建议 Git 版本不要低于 2.30因为早期版本在 SSH 和分支相关的行为上有一些差异新版在处理凭据助手、分支默认名等方面明显更友好。2.2 user.name 和 user.email提交记录里的署名必须提前配好这一步非常关键但经常被跳过。Git 每次git commit都会把用户信息写进提交记录里如果你的user.name和user.email没有配置Git 要么直接报错要么会用系统用户名拼一个默认信息凑数最后提交记录里显示的名字乱七八糟队友都不知道这段代码是谁写的。配置命令很简单git config --global user.name 你的名字 git config --global user.email 你的邮箱注意这里的邮箱并不强制要求跟 GitHub 注册邮箱一致但强烈建议一致因为 GitHub 会把提交记录和账号关联起来。改完之后用git config --global --list可以查看当前全部配置。还有一个细节--global表示全局生效。如果你手头有不同项目需要不同身份也可以在某个仓库内不加--global只对当前仓库生效。这是进阶用法新手阶段先统一全局配置就够了。2.3 确认系统 SSH 客户端可用别在环境变量上栽跟头Windows 上如果你使用的是 Git for Windows它自带了一个 OpenSSH 客户端在 Git Bash 里直接可用。macOS 和 Linux 系统一般也自带ssh。你可以打开终端输入ssh -V看看有没有输出。有些 Windows 用户会碰到ssh 不是内部或外部命令的提示这是系统环境变量里没有 OpenSSH。解决办法是在“设置 - 系统 - 可选功能”里找到 OpenSSH 客户端并安装更省事的做法是直接在 Git Bash 里操作因为 Git Bash 本身就能找到 Git 自带的 SSH 客户端。在正式生成密钥前不需要去改core.sshCommand之类的配置。默认状态下 Git 能正确调用系统 SSH除非你已经明确知道要自定义否则不要动这个设置。到这里你已经解决了两个问题Git 可用身份信息已经配置。接下来就是本次教程的重头戏——SSH 密钥生成。3. SSH 密钥生成算法选型、命令参数与文件权限3.1 为什么推荐 Ed25519 而不是默认的 RSA密钥生成的第一步是选算法。运行ssh-keygen时如果不加参数默认生成 RSA 密钥。但现在更推荐使用 Ed25519原因有三点安全性上Ed25519 是目前被广泛认可的现代非对称加密算法性能上Ed25519 密钥尺寸很小连接速度更快兼容性上GitHub 以及主流 Git 服务商都原生支持。如果你在比较老的内网环境里操作对方服务器可能还停留在旧版本 OpenSSH那就只能退回到 RSA但生成时建议加上-b 4096指定密钥长度。对于 GitHub 这种现代平台直接使用下面的命令即可ssh-keygen -t ed25519 -C 你的邮箱或备注-t指定算法类型-C相当于给这把钥匙写一个备注。备注内容不参与加密纯粹是为了让你以后能认出这是哪台机器、哪个用途的密钥。3.2 生成过程会问你三件事路径、口令、备注执行上面的命令后终端会依次询问两件事第三件其实在命令行里已经指定了。第一步是问密钥保存位置。默认路径是~/.ssh/id_ed25519。如果之前没生成过密钥直接回车使用默认路径即可。如果你已经有一对密钥想单独给 GitHub 生成一把可以改成另一个名字比如~/.ssh/id_ed25519_github。这里要特别注意改了文件名之后后续要用ssh-add把密钥加入 SSH agent或者通过第 4 章会说的 SSH config 指定路径否则客户端默认只会在~/.ssh/id_ed25519里找。第二步是设置 passphrase。你可以把它理解成私钥的访问口令。这个口令不是必须的直接回车留空也可以但设置了之后每次用私钥建立连接时都要输入一遍。如果你追求方便可以留空如果你对安全性要求高还是建议设置。真实项目里只要私钥文件泄露没有 passphrase 就等于把服务器访问权拱手让人。设置一个简单的口令相当于给私钥多上了一道保险。顺便说一下ssh-keygen并没有单独问你备注备注就是命令里-C那一段。如果你写的是邮箱生成出来的公钥末尾就会带着邮箱后缀这便于维护时辨认。3.3 生成后必须检查的密钥文件与权限密钥生成成功后~/.ssh目录下会出现两个文件id_ed25519是私钥id_ed25519.pub是公钥。.pub后缀意味着它可以公开给别人看甚至可以直接贴在服务器授权列表里。私钥的权限必须严格控制。macOS 和 Linux 下可以用下面的命令检查ls -l ~/.ssh/id_ed25519正常的权限应该是-rw-------也就是 600。如果显示的是-rw-r--r--这种过于开放的情况SSH 客户端会拒绝使用这把密钥并提示权限问题。修复命令是chmod 600 ~/.ssh/id_ed25519Windows 用户使用 Git Bash 时权限问题相对少见但如果你遇到Bad permissions提示也要检查一下用户目录在系统层面是否被其他账户可读。3.4 查看公钥内容与复制注意事项公钥内容是一行文本格式大致是ssh-ed25519 一串很长的编码 你的备注。查看命令cat ~/.ssh/id_ed25519.pub这一整行内容接下来要完整复制到 GitHub 的 SSH keys 设置页面里。要注意别漏掉末尾的备注也别画蛇添足自己多加回车换行。有些新手只复制了中间那串编码或者在复制时多带了一个看不见的换行符都会导致后面验证失败。4. 把公钥交给 GitHub添加公钥、验证连接与多账号配置4.1 在 GitHub 上添加 SSH 公钥的完整路径打开 GitHub 网站进入右上角头像下的 Settings然后在左侧找到 SSH and GPG keys 菜单点击 New SSH key。Title 可以随便填建议填一个能帮助辨认设备的名字比如我的笔记本、台式机之类的Key type 保持默认的 Authentication Key 即可然后在 Key 文本框里完整粘贴刚才用cat显示出来的公钥内容最后点击 Add SSH key。注意GitHub 只认公钥任何人都不应该把私钥文件的内容贴到任何网页上。私钥一旦泄露就相当于把你电脑的访问凭证交给了别人必须立即撤销并重新生成。4.2 验证连接ssh -T 这条命令怎么读懂输出添加完公钥先不要急着克隆验证一下连接是否通。在终端执行ssh -T gitgithub.com如果你是第一次连接这台主机会看到一个 host key 确认提示大意是问你是否确定要连接这个主机。输入yes回车。之后如果一切正常会输出类似下面的内容Hi username! Youve successfully authenticated, but GitHub does not provide shell access.很多人初次看到这句话会愣住怎么连上了又说没有 shell access这其实完全正常。GitHub 的 SSH 服务只允许你执行 Git 相关数据交换不允许你在远程服务器上执行任意命令所以才会有“does not provide shell access”的提示。看到这句话就说明认证已经通过了。如果输出的是Permission denied (publickey)说明密钥没有被识别。前面已经提到这通常是公钥没配对、私钥路径不对、或者连接时用了错误的用户。具体排查链路放到第 7 章详细讲。4.3 多平台切换时SSH config 的高级用法如果你只有一台电脑、一个 GitHub 账号那么前两节的内容已经够了。但当你同时使用多个代码托管平台或者一台电脑需要切换个人和工作两个 GitHub 账号时就需要借助~/.ssh/config文件做灵活映射。~/.ssh/config是一个纯文本文件语法非常直白。示例Host github.com HostName github.com User git IdentityFile ~/.ssh/id_ed25519 Host gitlab.com HostName gitlab.com User git IdentityFile ~/.ssh/id_ed25519_work这段配置的含义是客户端连接github.com时使用第一把密钥连接gitlab.com时使用第二把密钥。如果你在生成密钥时自定义了文件名不写这个文件的话SSH 客户端默认只会去找id_rsa或id_ed25519找不到就报错。这也是很多多密钥玩家最常踩的坑。修改完 config 之后用ssh -T gitgithub.com再测一次。如果还报错可以加-v参数看详细握手过程ssh -vT gitgithub.com-v会打印出完整调试信息能清楚看到客户端到底尝试了哪个密钥文件、在哪个环节被拒绝。排查 SSH 问题这个参数比什么工具都好用。5. 克隆仓库实战SSH 与 HTTPS 的选择、完整操作与目录规划5.1 克隆前先想清楚你要什么进入 GitHub 某个项目页面后点击绿色的 Code 按钮会有 HTTPS、SSH、GitHub CLI 几个标签。切到 SSH 一栏显示的地址是gitgithub.com:用户名/仓库名.git。复制这个地址然后在终端进入你想放置项目的目录执行git clone gitgithub.com:用户名/仓库名.git执行后Git 会在当前目录下生成一个以仓库名为名的子目录里面就是这个仓库的全部代码和.git历史目录。这里有一个容易让新人困惑的点如果你只想要这个仓库在某个特定版本的状态不想要完整的提交历史可以加--depth 1做浅克隆但如果你确定以后会持续推送自己的修改就不要加这个参数保留完整历史才有意义。大多数入门场景下直接克隆即可浅克隆是后续处理巨型仓库时的优化手段。5.2 HTTPS 与 SSH 的真实差异我在前面说过HTTPS 克隆公开仓库最省事不需要任何密钥配置。但是当你需要推送代码时HTTPS 每次都要提供凭据虽然可以配置凭据助手记住登录状态但多了一层步骤和心理负担。而 SSH 在密钥配置好之后就是无感连接。有人会问能不能两者混着用比如用 SSH 克隆但远程地址还是 HTTPS这不会出大问题但你要明白远程地址决定的是协议。任何时候都可以用git remote set-url来切换远程地址没必要把两种协议混在同一个仓库里。保持远程地址统一是非常好的工程卫生习惯。另外提一句GitHub 出于安全考虑正在逐步收紧密码认证方式HTTPS 场合大多要求使用个人访问令牌而不是账号密码。因此对新用户而言直接学会 SSH反而省掉了学习令牌的额外环节。5.3 克隆完成后目录里有什么当你看到终端里出现done字样仓库就算克隆成功了。进入目录后用ls -a能看到一个隐藏的.git文件夹这个文件夹就是 Git 的数据库里面存着所有提交历史和远程地址。它可以很大也可以被直接删除但删掉之后这个目录就只是一份普通源代码不再具备版本管理能力。你还可以用git branch查看当前分支。克隆下来的默认分支通常是main旧仓库可能是master。两者本质没有区别只是 GitHub 新仓库默认使用main早期仓库还保留着master的命名习惯。5.4 首次克隆失败时的通用稳妥方案如果你在克隆时报错但 SSH 验证已经通过那问题大概率出在地址拼写、仓库权限或者目录权限上。可以先检查地址是否以gitgithub.com:开头再看仓库可见性私有仓库只有被授权的账号才能克隆最后确认你当前登录的账号是不是有权限的那个。如果以上都正常还有一个很低级的坑当前所在目录本身不可写。比如你误入系统保护目录、或者磁盘权限设置不允许创建文件。这时候换一个用户有写入权限的普通目录即可。6. 克隆之后的第一轮工程化习惯远程管理、分支认知与提交节奏6.1 查看远程地址origin 是什么克隆完成后本地仓库会自动把远程仓库地址记录为一个名为origin的远程端。运行git remote -v可以看到origin gitgithub.com:用户名/仓库名.git (fetch) origin gitgithub.com:用户名/仓库名.git (push)如果 fetch 和 push 两行内容一致说明两个方向都用同一个远程地址。如果你拿到的是别人给你的压缩包或从别处复制的仓库想改成自己的远程地址可以用git remote set-url origin gitgithub.com:新用户名/新仓库名.git改完之后再执行git remote -v确认一下避免推送到错误的地方。6.2 分支的认知为什么不要一开始就提交到 main分支是 Git 里最核心的抽象之一。可以把main分支理解成一条主流水线它应该始终处于可运行状态。你开发新功能时更稳妥的做法是拉出一个新分支git checkout -b feature/xxx这个命令会创建新分支并切换过去。分支名并不强制团队里一般会用feature/、fix/、docs/这类前缀区分用途。为什么要这样设计因为如果直接在主分支上提交一旦出现问题想回退影响的就是整条主线而在独立分支上随便提交、随便回退都不会干扰别人。很多新手觉得搞分支是过度设计但真实协作中分支就是隔离风险的保险丝。哪怕你只是一个人开发把实验性改动放到分支上也更安全。6.3 第一次提交的完整流程与安全检查新克隆下来的仓库通常已经包含一个初始提交甚至可能是空仓库。如果你在这个仓库里修改了文件想形成自己的第一次提交流程是git add . git commit -m 描述本次修改 git push origin 分支名git add .表示把当前目录下所有变更加入暂存区commit生成一条新的提交记录push把本地提交推送到远程。推送时要注意分支名要和本地当前分支一致如果远程没有同名分支Git 会自动创建。很多新手会把git add和commit连起来执行但其实分开执行更安全。因为你可以在 commit 前用git status检查一下到底有哪些文件会被提交。刚配好 SSH、刚克隆完仓库的前两天极容易出现“我不小心把密钥文件、本地配置、或者下载的临时文件推到了远程”这种事故。Git 本身不会帮你识别敏感文件安全习惯必须在 commit 之前建立。6.4 .gitignore克隆之后立刻要检查的文件有些仓库自带.gitignore有些没有。这个文件告诉 Git 哪些文件不应该纳入版本管理。对于本地配置文件、密钥文件、编译产物、依赖包目录等都应该在第一次提交前写进.gitignore。哪怕现在只是练手项目也建议尽早养成这个习惯。因为敏感文件一旦进入历史记录删除起来非常麻烦要改写历史、要刷新凭据还容易影响所有协作者。很多团队事故的起点就是有人把一个包含密码的.env文件直接推到了远程仓库。7. 新手最常踩的坑从报错信息反推原因的完整排查链路7.1 Permission denied (publickey) 的排查顺序这是所有 Git 新人最熟悉的报错。出现这个报错说明 SSH 服务器没有收到有效的公钥认证。我的排查顺序是这样先确认你连接的是不是gitgithub.com这个固定用户名和地址。执行ssh -T gitgithub.com看是否同样报错。检查本地~/.ssh目录下是否存在对应的私钥文件。确认公钥内容是否完整添加到了 GitHub 账号的 SSH keys 列表里。用ssh -vT gitgithub.com查看详细握手过程观察 SSH 客户端加载了哪把私钥。如果加载的密钥不是你以为的那把检查~/.ssh/config是否有残留配置。这里最常见的原因有两个一是换电脑之后新公钥没加到 GitHub二是生成密钥时改了文件名却没有在 config 中指定路径。只要你理解了 SSH 的连接模型也就是私钥在本地、公钥在远端、SSH 会自动挑选可用密钥这个错误的定位就会非常清晰。7.2 Host key verification failed 是什么情况这个报错通常出现在第一次连接某台主机时或者主机指纹发生变化时。SSH 客户端会在~/.ssh/known_hosts里记录已确认过的远程主机指纹。如果你初次连接时输入了no或者重装系统导致 known_hosts 丢失又或者远程主机改换了密钥就会触发这个提示。解决方案说起来很简单如果你确认目标服务器是可信的就把 known_hosts 里对应的旧记录删掉或者直接用ssh-keygen -R github.com清除特定主机的记录然后重新连接。安全提示清除 known_hosts 相当于默认信任对方。日常操作中如果不是你自己能确认的服务器不要轻易清空整个 known_hosts 文件否则有中间人攻击的风险。7.3 commit 阶段提示未配置用户信息有些朋友在验证和克隆都顺利通过之后到了git commit这一步才收到报错内容是这样的Please tell me who you are.这对应的就是第 2.2 节说的user.name和user.email没有配置。回到终端执行那两条git config --global命令配置完重新 commit 即可。这个报错不影响 SSH只影响本地 Git 提交记录。7.4 push 被拒绝本地落后于远程这是一个越往后越常见的问题。你 clone 下来修改了代码但队友已经先一步推送了更新此时你执行git push会被拒绝提示信息可能是non-fast-forward或者rejected。正确的做法是先拉取远程最新内容再推送git pull origin main这里的pull会尝试把远程新改动与本地改动合并。如果两个人在不同文件改代码Git 通常会自动合并如果改的是同一文件的同一区域就可能产生冲突。新手遇到冲突不要慌冲突本质就是文件里出现了、、这样的标记行你只需要和队友商量保留哪部分改完后重新git add、git commit再推送一次即可。这一节看完克隆阶段的常见问题基本都能覆盖到了。之后再遇到报错你应该能顺着报错信息反推出是哪一个环节出了问题而不是漫无目的地重装 Git。我现在回想自己第一次配 SSH 的时候也曾在Permission denied上卡了大半天。后来我才意识到问题不是命令敲错了而是我对 SSH 的公私钥配合模型没有真正理解。只要把“私钥在本地、公钥在远端、指纹用于初次信任”这条主线记牢后面所有报错都会变得容易解读。如果这篇文章只能留下一个记忆点我希望是这句话SSH 配置的目标不是让你背住几行命令而是让你理解一次连接请求在本地和远程之间经历了哪些校验环节。命令只是载体理解了校验链路以后换平台、换电脑、配置多账号你都能自己定位问题。最后分享一个实用小习惯换电脑或者重装系统之后第一件事就是重新生成 SSH 密钥并把新公钥加到 GitHub 上。如果你设置了 passphrase还要记得用ssh-add把私钥加入 SSH agent否则每次连接都要输一次口令。把这个动作练熟之后从 SSH 配置到克隆仓库的整套流程对你来说就真的只是五分钟的事了。