
1. 写在前面这可能是你与 GitHub 之间最值得花半小时搞定的事如果你已经受够了每次 push 代码都要输一遍用户名和密码或者经常被 GitHub 的“Access denied”和“Permission denied (publickey)”折磨到怀疑人生那这篇文章大概率能帮到你。我最早接触 GitHub 的时候也是从 HTTPS 协议开始用的那时候觉得“能跑就行”直到某天 push 一个稍微大点的仓库连续输入了四五次凭证再加上网络稍微一波动就直接超时整个人瞬间就炸了。后来换成 SSH 方式推送远程仓库整个流程变得顺畅很多而且不用反复输密码配合 SSH key 的权限管理在多个设备、多个账号之间切换也清晰得多。这篇内容围绕一个核心场景展开用 SSH 协议给 GitHub 远程仓库做推送。适合刚接触 Git 不久、想彻底搞懂 SSH 推送原理的入门读者也适合已经用 HTTPS 但想换一种更稳定、更省事方式的开发者。我会先解释为什么选 SSH 而不是 HTTPS再完整演示从生成密钥、配置 GitHub 到真正 push 代码的全过程最后把我这些年踩过的坑、排查问题的思路一并整理出来。标题听起来不大但里面涉及的细节其实足够写一篇很扎实的实操笔记。2. 为什么推送远程仓库我首选 SSH2.1 HTTPS 和 SSH 到底差在哪很多人第一次接触 GitHub 时默认用的就是 HTTPS 地址形如https://github.com/用户名/仓库名.git。这种方式的好处是门槛低不需要额外配置密钥只要在弹出窗口里输入 GitHub 的用户名和密码、或者 Personal Access Token个人访问令牌就能完成认证。但它的痛点也很明显每次 push 都要重新认证虽然可以用缓存工具把凭证存下来但换设备、换系统、凭证过期之后又得折腾一遍。SSH 则完全不一样。它走的是公钥加密、私钥签名的认证机制一次配置长久使用。我只需要在本地生成一对密钥把公钥放到 GitHub 账号里之后所有 Git 操作都通过 SSH 协议完成认证不需要输入密码也不依赖 HTTPS 那套令牌体系。从实际体验来看SSH 在连接稳定性上也更有优势尤其是在某些网络环境下HTTPS 反复握手失败的概率明显比 SSH 高。2.2 为什么 SSH 更适合“长期主义者”这里我不打算把 SSH 的底层原理讲得太深毕竟你只是用它来推代码不是来写 RFC 文档的。但有一个概念必须建立起来SSH 认证之所以安全是因为你手上握着私钥GitHub 那边只保存公钥。私钥不离开你的电脑任何想冒充你的人都拿不到它所以即使 GitHub 的服务器被攻破攻击者拿到的公钥也做不了什么坏事。从实际工作流来说SSH 还有一个好处支持多账号管理。如果你同时有个人 GitHub 账号、公司 GitLab 账号甚至还有自建的 Gitea 服务完全可以通过多份 SSH 配置分别指定不同的私钥互不干扰。HTTPS 想在多个账号之间切换就比较麻烦虽然也能通过凭证管理器实现但远不如 SSH 的 config 文件来得直观。提示如果你只是临时用一下 GitHubHTTPS 完全够用。但如果你打算长期维护开源项目、频繁推送代码换 SSH 是性价比最高的选择。3. 先搞清楚 SSH 推送远程仓库的整个工作流程3.1 一次 SSH 推送背后发生了什么我在最开始接触 SSH 推送时其实并不理解为什么本地明明没有“密码”GitHub 却能认识我。后来拆开看才明白整个过程可以简化成三步。第一步本地生成密钥对。常用的工具有ssh-keygen它会同时生成一个私钥默认存在~/.ssh/id_ed25519和一个公钥~/.ssh/id_ed25519.pub。私钥等同于你的“数字身份证”公钥则是身份证的公开信息。第二步把公钥内容添加到 GitHub 账号的 SSH keys 设置页面。这一步相当于告诉 GitHub“这把公钥对应的私钥持有者是我本人”。GitHub 会把公钥存到它的用户配置里。第三步执行git push。Git 通过 SSH 协议连接 GitHub 时GitHub 会向客户端发起挑战客户端用私钥签名并返回响应GitHub 用之前存储的公钥验证签名。验证通过后GitHub 就认为你是合法用户允许推送。整个过程看起来很复杂但实际操作中 Git 和 SSH 客户端早已把背后的逻辑封装好了我们要做的只是生成密钥、添加公钥、把远程地址改成 SSH 格式。3.2 SSH key 里那些绕不开的名词第一次生成密钥时命令行会提示你选择密钥类型常见的选项包括 RSA、Ed25519 等。我个人的建议是直接选 Ed25519因为它的密钥更短、生成速度更快安全性也足够好。以前老教程里喜欢用 RSA 4096只能说是历史遗留习惯现在的新项目基本都推荐 Ed25519。还需要理解~/.ssh/config文件的作用。这个文件可以按 Host 维度配置 SSH 连接的参数比如指定用哪个私钥、是否开启压缩等。对于 GitHub 这种单一场景不写 config 文件也能跑但如果你的电脑上同时使用 GitHub、GitLab、Gitee那配置一个清晰的 config 文件就非常有必要了。3.3 SSH 推送和 HTTPS 推送在命令上的区别以git remote add为例HTTPS 和 SSH 只是 URL 的写法不同其他 Git 命令完全一样。HTTPS 格式是https://github.com/用户名/仓库名.gitSSH 格式是gitgithub.com:用户名/仓库名.git。注意 SSH 格式里不是https://开头而是git开头中间的github.com就是 SSH 连接的主机地址冒号后面才是仓库路径。已经用 HTTPS 添加过 remote 的仓库不需要删掉重新克隆直接修改 remote 地址即可。下面会演示具体命令。4. 手把手实操从零配置 SSH 并推送代码到 GitHub4.1 检查本地是否已有 SSH key这一步特别容易被跳过去但我觉得值得花十秒钟看一眼。打开终端输入ls -al ~/.ssh如果这个目录下已经有id_ed25519和id_ed25519.pub这样的文件说明你之前生成过密钥。这时候有两个选择一是继续沿用旧密钥二是重新生成一份新的。我建议如果你不确定旧密钥是否安全、或者是否已经把公钥配置在了某个平台上那就重新生成一份避免后面排查问题时分不清是哪把钥匙在起作用。如果目录不存在或者没有任何文件那就直接进入下一步生成流程。4.2 生成新的 SSH 密钥执行下面的命令把your_emailexample.com替换成你自己的邮箱这个邮箱仅作为注释出现在公钥信息里方便你识别这把密钥是谁生成的并不需要和 GitHub 账号邮箱完全一致ssh-keygen -t ed25519 -C your_emailexample.com终端会提示你选择保存位置默认是/Users/你的用户名/.ssh/id_ed25519直接回车即可。接着会提示输入 passphrase口令。这里很多人会纠结不设口令吧怕私钥泄露后别人直接就能用设了口令吧每次 push 又会多一步输入。我的选择是在个人电脑上不设口令在共享电脑或公司电脑上一定设。不设口令时只要私钥文件不被别人复制走安全风险其实可控而如果设了口令也强烈建议配合 ssh-agent 使用避免每次操作都输一次。生成完成后可以用下面命令查看公钥内容后面要用cat ~/.ssh/id_ed25519.pub这串内容看起来像ssh-ed25519 一堆乱码... your_emailexample.com复制它的时候别加多余的空格和换行。4.3 把公钥配置到 GitHub 账号登录 GitHub点右上角头像进入 Settings在左侧菜单里找到 SSH and GPG keys。点击 New SSH keyTitle 栏可以填“我的 MacBook Pro”之类方便识别的名字Key 栏把刚刚复制的公钥内容粘贴进去然后保存。这里有个非常常见的坑公钥粘贴时绝对不能有额外的换行开头和结尾的空格也要留意。曾经有个朋友把公钥粘到 GitHub 上之后一直认证失败我远程帮他一核对发现末尾多了一串看不见的换行符GitHub 在校验时自然就把整个 key 当作无效了。另外如果你有多个设备可以给每台设备的公钥分别命名这样以后在 GitHub 后台看到列表时能一眼看出哪台设备对应哪个 key。4.4 先做一次连接测试别急着 push配置完成后可以在终端执行ssh -T gitgithub.com如果看到类似下面的输出说明认证已经通过了Hi yourusername! Youve successfully authenticated, but GitHub does not provide shell access.这句提示的意思是“认证成功但 GitHub 不提供 shell 访问权限”这是正常现象并不意味着出错。假如你看到Permission denied (publickey)就要回到前面的步骤检查公钥配置是否正确或者看下本地是否用了正确的私钥。我自己习惯把这一步当作标配动作不管在哪个新环境配置完 SSH都会先执行一次ssh -T做连通性测试。这样能尽早暴露绝大多数配置问题节省后面真正 push 时的等待时间。4.5 修改远程仓库地址为 SSH 格式如果你手里的仓库是用 HTTPS 克隆的不需要重新克隆直接执行git remote -v查看当前 remote 地址。如果是https://github.com/...这类格式就执行git remote set-url origin gitgithub.com:用户名/仓库名.git把 origin 换成 SSH 格式。如果你是先创建好 GitHub 远程仓库、本地还没初始化那标准的初始化流程是git init git remote add origin gitgithub.com:用户名/仓库名.git之后正常执行git add .、git commit -m first commit、git push -u origin main就行了。注意现在的 GitHub 默认分支名是main不再是以前的master。如果 push 时提示分支名不匹配用git branch -M main把当前分支改名即可。4.6 完整的推送命令序列参考我没有刻意把命令复杂化但很多人第一次操作时会搞混顺序这里贴一份完整的最小化操作序列方便你直接对照执行# 1. 生成密钥 ssh-keygen -t ed25519 -C your_emailexample.com # 2. 查看公钥 cat ~/.ssh/id_ed25519.pub # 3. 把公钥复制到 GitHub - Settings - SSH and GPG keys # 4. 测试连接 ssh -T gitgithub.com # 5. 初始化本地仓库并关联远程 git init git remote add origin gitgithub.com:用户名/仓库名.git # 6. 推送 git add . git commit -m init project git push -u origin main如果你是从 HTTPS 仓库迁移到 SSH只需把第 5 步替换为git remote set-url origin gitgithub.com:用户名/仓库名.git这里有一个容易被忽略的点git push -u origin main中的-u表示建立本地分支与远程分支的跟踪关系之后直接git push就能推送不需要每次再带完整的参数。5. 多设备多账号场景下的 config 文件管理5.1 为什么你迟早需要一份 SSH config当电脑上只有一把私钥、只连接一个 GitHub 账号时SSH 的默认逻辑足够用。但现实往往更复杂。比如我自己家里有一台 Linux 服务器公司配的 MacBook 上还连着 GitLab另外我还有一个专门维护开源项目的 GitHub 小号。这种情况下如果每个平台都用默认的id_ed25519Git 连接时就会拿同一把私钥去撞所有平台GitHub 可能认出了你GitLab 却可能直接拒掉甚至报错。解决方式就是在~/.ssh/config里为不同的 Host 分配不同的私钥和用户。这个文件不需要额外安装工具纯手动创建即可。5.2 一份可复制的多账号配置示例下面是一份很经典的配置模板假设我要让 GitHub 主号使用id_ed25519_github_mainGitHub 小号使用id_ed25519_github_workGitLab 使用id_ed25519_gitlab# 个人 GitHub 主号 Host github.com HostName github.com User git IdentityFile ~/.ssh/id_ed25519_github_main # 工作用 GitHub 小号 Host github-work HostName github.com User git IdentityFile ~/.ssh/id_ed25519_github_work # 公司 GitLab Host gitlab.company.com HostName gitlab.company.com User git IdentityFile ~/.ssh/id_ed25519_gitlab注意第二个配置的 Host 是github-work这是一个别名实际连接的 HostName 仍然是github.com。那么在使用这个别名时远程地址要写成git remote add origin gitgithub-work:用户名/仓库名.git也就是说SSH 会先拿github-work去匹配 config 里的 Host 配置找到对应的 IdentityFile再用这个私钥去连接真正的github.com。这里最容易被误导的点是很多人以为 Host 必须写成真实域名其实不是。Host 只是本地标识真正决定连到哪台服务器的是 HostName。5.3 使用 ssh-agent 减少口令输入如果给私钥设置了 passphrase每次连接都会询问口令这确实很烦。推荐用 ssh-agent 把密钥加进内存。macOS 和 Linux 下执行eval $(ssh-agent -s) ssh-add ~/.ssh/id_ed25519Windows 下如果使用 Git Bash也可以执行类似命令。做了这一步之后当前终端会话内就不再需要反复输入口令了。需要说明的是这种便利是建立在“信任当前登录用户”的前提下所以如果是公用电脑我不建议把密钥加到 agent 里长期驻留。6. 推送过程中的常见报错与排查思路6.1 Permission denied (publickey)这是最经典、也最容易踩的报错。出现这个提示时先别慌按顺序排查以下四件事一是确认公钥是否真的添加到了 GitHub 对应账号。有人可能把公钥加到 A 账号却用 B 账号的邮箱或名称去提交结果当然是认证失败。二是确认当前 SSH 是否使用了正确的私钥。如果有多个私钥文件很有可能 SSH 默认拿了一把不是 GitHub 用的私钥去连接。这时可以加-v参数输出调试信息ssh -vT gitgithub.com在输出的日志里能找到Offering public key和对应的密钥路径看看是不是你预期的那把。三是检查文件权限。在 Linux 和 macOS 下私钥文件权限过宽会被 SSH 拒绝使用比如id_ed25519的权限如果是 644 就可能报Permissions too open。需要改成 600chmod 600 ~/.ssh/id_ed25519四是检查~/.ssh/config里有没有互相冲突的规则。如果你配置了泛匹配的 Host比如把 Host 写成*它可能干扰后续规则导致 GitHub 用了错误的私钥。6.2 Bad owner or permissions on ~/.ssh/config在 Windows 上使用 OpenSSH 时我见过很多人报这个错。原因通常是 Windows 对 SSH 配置文件的权限管理方式和 Unix 不一样当前用户对C:\Users\xxx\.ssh\config文件没有足够的控制权或者权限被继承给了其他账户。解决办法是右键 config 文件进入属性 - 安全 - 高级把所有者改为当前用户然后删除多余的授权项只保留当前用户完全控制权限。也可以用 Git Bash 执行icacls命令来处理但我在实践中发现直接在 GUI 里改更直观不容易误操作。6.3 Repository not found这个报错也常见。它不一定代表仓库不存在更可能是 SSH 没有权限访问该仓库。典型情况是使用小号密钥推送到主号名下仓库或者新创建的私有仓库还没有把对应的账号加为 collaborator。如果是私有仓库记得检查仓库的 Settings - Collaborators 是否包含当前认证账号。6.4 git push 时总是提示输入用户名和密码如果你已经按上面的步骤配置好 SSH但 push 时仍然弹用户名密码要立刻回头看一眼远程地址。大概率你还在用 HTTPS 地址命令git remote -v会立刻暴露这个问题。把 HTTPS 地址改成 SSH 地址即可。类似的还有另一种情况remote 地址是对的但 Git 配置里设置了url.*.insteadOf强制改写 URL比如某些工具会自动把 SSH 地址改写成 HTTPS 地址导致 push 时走了错误协议。遇到这种“明明地址没问题却总是走 HTTPS”的诡异情况检查全局 Git 配置git config --global --list看到url.https://github.com/.insteadOf一类的规则时删掉或跳过即可。6.5 排查思路的正确打开方式我自己排查这类问题时会遵循“由近到远”的顺序先看本地密钥和权限再看 SSH 连接测试最后看 remote 地址和 GitHub 侧配置。这样一步步排除比直接搜一个报错然后按别人的代码抄要靠谱得多。很多时候同一个报错信息可能有完全不同的成因生搬硬套可能越改越乱。排查时可参考下面这个速查表现象最可能原因第一排查动作Permission denied (publickey)公钥未添加或私钥不对ssh -T gitgithub.com查看认证结果Bad owner or permissionsSSH 配置文件权限过宽检查并调整 config 文件权限Repository not found无仓库访问权限确认账号与仓库权限关系仍提示输入密码远程地址还是 HTTPSgit remote -v检查连接超时或卡住网络问题或端口被封尝试ssh -p 443 gitssh.github.com关于最后一行这里额外说一句如果默认的 22 端口在你的网络环境下不通GitHub 还支持通过 443 端口走 SSH。把.ssh/config里加一段Host github.com HostName ssh.github.com Port 443 User git很多网络环境下的连接问题能就此解决这也算是 SSH 推送时一个很实用的后手方案。7. 从 HTTPS 迁移到 SSH 时的老仓库处理细节7.1 迁移时千万别忘了本地分支跟踪关系有些仓库用 HTTPS 已经用了很久远程分支跟踪关系已经建立好了。改成 SSH 地址后git push依然能正常工作因为git remote set-url只改动远程地址不影响 branch 的 upstream 配置。但有一点需要注意如果你之前在 HTTPS 模式下设置过credential.helper改成 SSH 后这个配置虽然无用但也不会影响 SSH 认证为了保持整洁可以执行git config --unset credential.helper7.2 迁移完之后建议验证一下改完地址后我建议执行一次git fetch确保能正常拉取。然后再做一次小改动git push验证推送。不要在改动一堆代码后才发现推送不了那种情况下的排查成本会成倍上升。7.3 为什么我不推荐删掉旧仓库重新克隆很多人图省事直接rm -rf整个目录再git cloneSSH 地址。这样操作虽然也能达到目的但如果本地有未提交的改动、stash 内容、本地分支或者标签这些都会丢得一干二净。用git remote set-url切换只需要 10 秒没有任何副作用所以别用那种“推倒重来”的方法。8. 一些关于 SSH 推送的细节习惯和避坑心得8.1 提交信息里的账号不归 SSH 管这里要特别提醒一个容易误解的点SSH 只负责传输和认证GitHub 上最终显示的 commit 作者取决于你本地 Git 配置的user.name和user.email跟 SSH key 没有直接关系。也就是说即使你用 A 账号的密钥提交只要本地 Git 配置写的是 B 的邮箱commit 在 GitHub 上也会显示成 B 提交。这会导致一个很反直觉的结果你明明用私钥 A 通过了 SSH 认证但 push 上去的 commit 却可能以 B 的身份展示。原因在于 GitHub 通过 commit 里的邮箱来关联用户。如果邮箱没有关联到 GitHub 账号commit 会显示成独立的小头像。解决方式是在每个仓库或全局配置里设置期望的邮箱git config --global user.email 你的邮箱 git config --global user.name 你的昵称或者使用 GitHub 后台提供的noreply邮箱来避免暴露真实邮箱GitHub 在 Settings - Emails 页面能看到这个地址。8.2 不要随意分享你的公钥文件公钥本身是可以在网络上公开的但很多教程会把公钥文件内容直接贴到论坛里这种做法在绝大多数情况下没有实质风险却容易误导别人认为“公钥都不能给别人看”。真正需要严格保密的是私钥文件也就是~/.ssh/id_ed25519这个没有.pub后缀的文件。如果哪天怀疑私钥泄露立即在 GitHub 后台删除对应公钥并重新生成密钥对这是最稳妥的处理办法。8.3 用好 SSH key 的备注信息生成密钥时-C参数后面写的注释会跟随公钥一起出现在 GitHub 的 key 列表里。如果当时没写清楚后面在列表里看到一堆“无意义注释”的 key很难分辨哪台设备在用。我建议把注释写成“设备名用途时间”的组合比如mbp-2025-personal-main。这样管理多个密钥的时候会省很多心。8.4 连接超时不一定是你配置的问题有一类问题是“配置完全正确但就是连不上”。如果你的ssh -T gitgithub.com卡了很久才超时那多半不是配置问题而是网络原因。这时候可以试试 443 端口的方案我在 6.5 小节里已经写过这里就不再重复。另外在某些公司网络环境下也会对 SSH 连接做限制这种情况只能换网络环境或者走 HTTPS 推送没有太多技巧。8.5 避免在 Windows 上使用不完整的 SSH 环境Windows 上的 Git 安装包通常会自带 OpenSSH 组件但如果你的系统 PATH 里同时存在多个 SSH 版本可能出现签名算法不匹配、密钥路径不对等诡异问题。解决方式是用git config --global core.sshCommand明确指定要使用的 SSH 命令路径例如git config --global core.sshCommand C:/Windows/System32/OpenSSH/ssh.exe这个问题不常见但一旦出现很难排查提前指定 SSH 实现可以省去很多麻烦。9. 推送完成后这些扩展操作值得尝试9.1 用 SSH 直接拉取私有仓库到服务器SSH 配置好之后不只适用于 GitHub也适用于其他托管平台比如 Gitee、GitLab、Gitea 等。只要把公钥添加到对应平台就能实现免密克隆。实际开发中我经常在云服务器上执行git clone gitgithub.com:xxx/xxx.git整个过程不需要在服务器上保存任何明文密码安全性好得多。9.2 结合 git hooks 做提交前检查SSH 推送本身不涉及 hooks但推送前后可以借助 Git 的 pre-push 钩子做检查比如单元测试、代码格式化、禁止直接推送到 main 分支等。这也算是“推送”这个动作的延伸价值。我目前的一个小型项目里就用 pre-push 钩子自动跑ruff的代码检查如果不过就拒绝推送帮助我避免把低质量代码推到远程。9.3 给不同目录配置不同的签名密钥进阶用户还可以考虑用 Git 2.x 以上的includeIf配置按目录自动切换 Git 的user.email和 SSH 签名设置。例如[includeIf gitdir:~/work/] path ~/.gitconfig-work这样只要在~/work目录下操作Git 就会自动加载工作用的配置推送时也不会弄混身份。这一招特别适合工作和个人项目在同一台电脑上管理的情况。10. 最后再分享一个小技巧不要只在遇到问题的时候才检查 SSH 配置。我建议每隔一段时间比如一个月主动执行一次ssh -T gitgithub.com确认认证状态正常。如果输出里出现了陌生的用户名说明本地 SSH 配置可能被改过或者有多个密钥在互相干扰尽早发现能避免后面 commit 归属错乱的麻烦。还有一个小细节很多人喜欢把私钥文件直接放到~/.ssh/下但忘了设置权限这在 Linux 和 macOS 上会导致 SSH 直接拒绝加载。所以每次新生成密钥后记得执行chmod 600 ~/.ssh/id_ed25519。Windows 上虽然没有这个报错但在使用 Git Bash 时同样建议手动收一下权限防止其他软件意外读取私钥。从我个人的实际操作体会来看SSH 推送远程仓库这件事真正难的不是那几条命令而是对整套认证机制建立清晰的认知框架。一旦理解了“私钥签名、公钥验证”这个核心逻辑之后遇到各种奇怪的认证报错都能沿着方向一步步排查而不是盯着错误提示干着急。希望这篇内容能帮你省下几个小时的折腾时间下次在新环境配置 GitHub 时直接照着步骤十分钟搞定。