1. 为什么我在 Linux 上坚持用 SSH 方式连 GitHub
1.1 HTTPS 和 SSH,两条路线的日常体验差异
第一次在 Linux 上使用 Git 连接 GitHub 时,按照搜索引擎给的默认教程走的是 HTTPS 方式。clone 一个仓库,输入用户名和密码,以为就这样了。真正开始日常维护项目才发现这条路有多难受:每次 push 都要重新验证身份,输完用户名还得输密码,遇到开启了双因素认证的账号,密码位还得填 Token,而不是真正的账号密码。一个上午推三次代码,三次都在找 Token。
后来切换成 SSH 之后,整个流程才舒服起来。SSH 和 HTTPS 最本质的区别在于身份验证的方式:HTTPS 每次操作都靠"账号密码"或者"Token"来证明你是谁,而 SSH 靠的是本机上的密钥对。只要密钥配好,push、pull、clone 全部免密完成,不需要反复输入任何凭证。对于经常在服务器上操作、或者写脚本做自动化部署的人来说,这个差异是决定性的。
用一张表来对比两者在日常使用中的区别会更直观:
| 对比项 | HTTPS 方式 | SSH 方式 |
|---|---|---|
| 首次配置 | 输入账号密码即可 | 需要生成密钥并配置到 GitHub |
| 日常 push | 每次都要输账号密码或 Token | 免密,连接后直接推送 |
| 双因素认证 | 密码位需要手动填 Token | 不受影响 |
| 脚本自动化 | 需要额外处理凭证存储 | 天然适合非交互式脚本 |
| 安全性 | 凭据有泄露风险 | 私钥不出本机,相对更安全 |
如果你只是偶尔 clone 一个公开仓库,HTTPS 也没问题。但只要你需要在 GitHub 上维护代码、参与协作、或者管理自己的项目,SSH 几乎是必选项。这篇教程就是把 SSH 这条路的所有步骤串起来,从生成密钥到克隆再到推送,完整走一遍。
1.2 SSH 免密背后的密钥机制,以及它为什么更适合脚本自动化
有些朋友会好奇,为什么 SSH 就能免密?其实它背后是一对密钥在配合工作:公钥和私钥。你可以把公钥想象成一把锁,私钥是这把锁唯一的钥匙。公钥可以大摇大摆地交给全世界,把它配置到 GitHub 上;而私钥永远只留在你自己的电脑里,谁都不给。当你连接 GitHub 时,服务器会拿着公钥发起一个只有私钥才能解开的问题,你的本机用私钥应答,验证通过,连接建立。
这个机制带来的好处不只是"不用输密码"。因为整个过程不需要人工介入,所以它可以被安全地写进 shell 脚本里。比如我有一台 Linux 服务器专门用来跑定时任务,凌晨三点自动把生成的文件推送回 GitHub 仓库,全程无人值守。换做 HTTPS 方式,这种场景几乎没法做——你不能把密码裸写在脚本里,而配置 Token 存储又是一堆额外的工作量。
理解了密钥机制,后面所有操作都顺理成章了:配置过程中要做的就是三件事——生成密钥对、把公钥交给 GitHub、用私钥去验证连接。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 从零配置 SSH 密钥:一条命令加三个不容易注意的细节
2.1 安装 Git 并确认版本
在开始折腾密钥之前,先确认系统里的 Git 是不是已经就绪。虽然很多 Linux 发行版默认装了 Git,但总有例外,尤其是精简安装的服务器镜像。
Debian/Ubuntu 系列的安装命令是:
bash复制sudo apt update
sudo apt install git -y
RHEL/CentOS/Fedora 系列用:
bash复制sudo dnf install git -y
装完以后检查版本:
bash复制git --version
看到输出类似 git version 2.40.1 就说明环境没问题了。理论上只要 Git 版本在 2.0 以上,后面所有操作都不会有兼容性障碍。如果你用的是比较老的发行版,建议顺手升级一下 Git,因为新版对 SSH 的支持和错误提示都友好得多。
2.2 ssh-keygen 生成密钥:算法选择、保存路径与 passphrase
生成密钥是整套配置的核心步骤,命令很简单:
bash复制ssh-keygen -t ed25519 -C "your_email@example.com"
这里有两个参数值得多说两句。-t ed25519 是密钥算法,现在新项目我基本都推荐 Ed25519,它比传统的 RSA 更短、更快,安全性也更高。GitHub 早已支持这种算法。如果你的环境比较特殊,比如有些老旧的 Git 客户端不认识 Ed25519,那就退回 RSA:
bash复制ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
-C 后面的内容是一个备注信息,通常写你的邮箱,作用是让 GitHub 上显示这个密钥是哪个设备创建的。这个备注不参与加密计算,所以写啥都行,但建议写自己的邮箱或者设备名,方便以后管理。
回车之后,系统会问你要把密钥保存到哪里。默认路径是 /home/你的用户名/.ssh/id_ed25519,直接回车用默认路径就行。我见过有人特意改路径,结果后面老是出问题,所以不是有特殊需求,不要动这个默认值。
接下来会提示设置 passphrase,也就是私钥的本地密码。这里有两个选择:
- 不设置,直接回车:好处是 SSH 连接一路到底,完全免密;坏处是任何能拿到你电脑的人都能用这把私钥连上 GitHub。
- 设置一个 passphrase:每次 SSH 操作时需要先输入这个密码解锁私钥,安全性更高。
我的建议是:个人电脑上日常开发,不设置 passphrase 比较省心;如果是公用的服务器或者安全要求比较高的环境,老老实实设置一个。安全性和便利性从来都是取舍,不要指望两头都占。
生成完成后,~/.ssh 目录下会出现两个文件:id_ed25519 是私钥,id_ed25519.pub 是公钥。记住,只有 .pub 结尾的那个可以给别人看。
2.3 把公钥完整贴到 GitHub 的两种做法
接下来要把公钥的内容复制出来,配置到 GitHub 上。先查看公钥:
bash复制cat ~/.ssh/id_ed25519.pub
屏幕会输出一串以 ssh-ed25519 开头、以你邮箱结尾的字符串。这个字符串要完整复制,少一个字符都不行。
复制的方式有两种:
第一种,直接用鼠标选中终端里的输出内容复制。这种方式简单,但有个隐患——公钥字符串很长,手动选中容易漏掉开头或结尾的字符。
第二种,用命令直接复制到剪贴板:
bash复制sudo apt install xclip -y
xclip -sel clip < ~/.ssh/id_ed25519.pub
这样公钥就直接进剪贴板了,粘贴到 GitHub 上就不会有遗漏。
接下来进入 GitHub 网页:点右上角头像,选择 Settings,左侧菜单找到 SSH and GPG keys,点 New SSH key。Title 随便起一个容易识别的名字,比如 my-linux-laptop。Key 区域粘贴刚才复制的公钥,然后点 Add SSH key。如果账号开启了双因素认证,这一步会让你输入验证码,按提示操作就行。
到这里,SSH 密钥的配置就算完成了。但先别急着 clone,下一步先做连通性测试,把潜在问题提前暴露出来。
3. 连通性测试与踩坑排查:优先跑通 ssh -T 这一步
3.1 第一次连接时的 fingerprint 确认与预期输出
密钥配置完以后,先跑一条测试命令,验证整个链路是否打通:
bash复制ssh -T git@github.com
注意这里的用户名是 git,不是你的 GitHub 账号名。GitHub 的 SSH 入口统一使用 git 这个用户进行连接,真正的身份识别靠的是后面验证的密钥。
第一次运行这条命令时,会看到类似这样的提示:
code复制The authenticity of host 'github.com (140.82.xx.xx)' can't be established.
ED25519 key fingerprint is SHA256:+DiY3wvvV6TuJJhbpZisF/zLDA0zPMSvHdkr4UvCOqU.
Are you sure you want to continue connecting (yes/no)?
这是在询问你是否信任这台主机。GitHub 的官方指纹是公开的,如果你用的不是上面这个常见的 SHA256 值,我建议先去查一下官方文档确认。确认无误后输入 yes 回车,这个指纹会被记录到 ~/.ssh/known_hosts 文件里,以后连接就不会再问了。
如果一切正常,接下来会看到:
code复制Hi your_username! You've successfully authenticated, but GitHub does not provide shell access.
看到这行输出,说明密钥验证已经通过,SSH 连接 GitHub 完全 OK。这个 your_username 会显示你的实际 GitHub 用户名,可以用来确认是否连对了账户。
3.2 Permission denied 的完整排查链路
如果上面那条命令返回的是 Permission denied (publickey),先不用慌,这是 SSH 连接最常见的报错。问题不在于 GitHub 侧,而在于本地没有向 GitHub 提供正确的密钥。下面是我在实际排错中总结的一套顺序,按这个顺序检查基本能解决九成问题。
第一步,确认本地密钥文件存在:
bash复制ls -l ~/.ssh/
正常情况下应该能看到 id_ed25519 和 id_ed25519.pub。如果连文件都没有,说明你跳过了生成密钥的步骤,回到上一节重新执行。
第二步,确认私钥是否被 ssh-agent 加载:
bash复制ssh-add -l
这条命令会列出当前会话中已加载的密钥。如果提示 The agent has no identities.,说明私钥没有被加载。手动添加:
bash复制ssh-add ~/.ssh/id_ed25519
这里要说明一下 ssh-agent 的作用。它是一个常驻后台的程序,专门帮你保管私钥。你在 Linux 桌面上登录后,系统通常会自己启动它,但新生成的密钥不会自动加进去,需要手动 ssh-add 一次。
第三步,确认 GitHub 上的公钥和本地公钥是否一致。重新查看本地公钥:
bash复制cat ~/.ssh/id_ed25519.pub
然后去 GitHub 的 SSH keys 页面,逐个比对。最容易踩的坑是:粘贴公钥时少复制了后面的邮箱部分,或者多复制了一个换行符。宁可重新复制一次,也不要凭肉眼判断"看起来差不多"。
第四步,如果你本地有多个密钥文件,需要通过 ~/.ssh/config 指定使用哪一把。举个例子,你为公司项目生成了一把密钥,为自己的 GitHub 账号生成了另一把,SSH 客户端会因为默认只读取 id_ed25519 而找不到正确的那把。这时候用文本编辑器打开 ~/.ssh/config:
code复制Host github.com
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_github
保存后重新执行 ssh -T git@github.com。这个配置文件的作用就是告诉 SSH:连接 GitHub 时,用 id_ed25519_github 这把私钥,而不是默认的那把。
按照这个链路走下来,大部分 Permission denied 问题都能定位到具体环节。我曾经在一次排错中发现问题的根源竟然是没有执行 ssh-add,而当天上午我明明确认了密钥生成成功、公钥贴得也没错。所以这条链路的每一步都不是多余的,建议按顺序过。
3.3 连接超时与 Host key verification failed 的处理思路
除了 Permission denied,还有两类常见的 SSH 连接问题。
第一类是长时间的 Connection timed out。这个报错说明 SSH 请求根本没有到达 GitHub,网络层面的连通性出了问题。处理方法是先确认网络本身能不能正常访问 GitHub 网页,如果网页也加载不出来,那就是网络环境的问题,不是 Git 配置的问题。反之,如果网页能打开但 SSH 一直超时,检查一下是不是有防火墙规则拦了 22 端口,或者公司内网对 SSH 端口做了限制。GitHub 也提供了 443 端口的 SSH 连接方式,作为备选方案:
bash复制ssh -T -p 443 git@ssh.github.com
这个方案在特殊网络环境下实测有效,核心原理就是把 SSH 流量走 443 端口。GitHub 官方专门为这种情况保留了 ssh.github.com 这个入口。
第二类是 Host key verification failed。这个报错和前面第一次连接时的 fingerprint 确认有关。如果你之前连接过一次,指纹已经写入了 known_hosts,之后 GitHub 的服务器密钥发生了变更(比如 GitHub 做过密钥轮换),SSH 客户端会因为指纹不匹配而拒绝连接。处理方法很简单,把旧指纹从 known_hosts 里移除:
bash复制ssh-keygen -R github.com
然后重新执行 ssh -T git@github.com,重新确认新的指纹即可。
4. 克隆项目到本地:保存目录、浅克隆与 remote 地址调整
4.1 git clone 到底把项目放哪里了
SSH 连通性测试通过之后,正式进入克隆环节。先说明一个很多 Linux 新手都会困惑的问题:执行完 git clone 之后,项目到底保存到哪里去了?
Git 的行为默认是:在当前目录下创建一个以仓库名命名的子目录,然后把所有文件放进这个子目录。比如你在 /home/user/workspace 目录下执行:
bash复制git clone git@github.com:your_username/your_project.git
那么项目会被保存到 /home/user/workspace/your_project/。这不是一个随机行为,而是 Git 设计上的约定,目的是防止克隆下来的文件散落在当前目录,把已有文件弄乱。
如果你希望把项目克隆到当前目录里,不创建额外子目录,可以在仓库地址后面加一个点:
bash复制git clone git@github.com:your_username/your_project.git .
注意这个操作要求当前目录必须是空目录,否则 Git 会拒绝执行。还有另一种需求:想换一个目录名存放项目:
bash复制git clone git@github.com:your_username/your_project.git my_project
执行完后,项目会被放到 my_project 目录里,而不是 your_project。这个技巧在项目重命名或者多项目分目录管理时很好用。
4.2 浅克隆与全量克隆的选择,以及克隆效率问题的答案
许多人在克隆大型仓库时最关心的问题是"克隆效率"。Git 的默认行为是全量克隆,也就是把这个仓库的所有分支、所有提交历史全部拉到本地。对于一个小项目来说没什么感觉,但对于一些提交历史很长的仓库,全量克隆可能要下载几百 MB 甚至上 GB 的数据。
如果只是看一下代码、跑一下编译,根本不需要完整历史,这时候用浅克隆就能大幅提升效率:
bash复制git clone --depth 1 git@github.com:your_username/your_project.git
--depth 1 的意思是只拉取最近一次提交,历史记录全部跳过。这个方案在 CI/CD 流水线里用得非常多,因为构建环境通常只需要最新的代码,不需要完整历史。
但是要注意浅克隆的后续限制。如果你在这个浅克隆仓库里做了修改想推回去,或者其他操作需要用到历史,就会遇到麻烦。解决方案很简单,等真正需要历史的时候补齐:
bash复制git fetch --unshallow
这条命令会拉取完整的历史记录,把浅克隆升级成全量克隆。
关于全量和浅克隆怎么选,我个人的建议是:日常开发维护自己的项目,直接用全量克隆,因为你会频繁用 git log、git blame 这些需要历史的命令;只是临时看代码、打包、跑测试的环境,一律用浅克隆,节省时间和带宽。
4.3 已用 HTTPS 克隆的项目如何一键切到 SSH
还有一类常见场景:你之前用 HTTPS 方式克隆了一个仓库,现在想把 remote 地址改成 SSH。不重新 clone,直接改地址就行。
先查看当前的远程地址:
bash复制git remote -v
输出大概是这样的:
code复制origin https://github.com/your_username/your_project.git (fetch)
origin https://github.com/your_username/your_project.git (push)
然后修改为 SSH 地址:
bash复制git remote set-url origin git@github.com:your_username/your_project.git
再执行一次 git remote -v 确认修改成功:
code复制origin git@github.com:your_username/your_project.git (fetch)
origin git@github.com:your_username/your_project.git (push)
这个操作不会影响本地已有的文件和历史记录,只是把之后的 fetch/push 走的路从 HTTPS 换成了 SSH。切换到 SSH 之后,之前每次 push 要输账号密码的困扰就彻底消除了。
5. 推送代码:commit、branch 与 SSH 免密的实际体验
5.1 第一次 commit 之前,必须先把 user.name 和 user.email 配好
克隆能跑通之后,大多数人的下一个动作是新建分支、改代码、commit。但如果你直接执行 commit,Git 会毫不留情地报错:
code复制Author identity unknown
*** Please tell me who you are.
原因很简单:Git 的每一次提交都要记录作者信息,否则多人协作时根本分不清谁改了什么。这个信息不来自 GitHub 账号,而是来自本机的 Git 配置,所以必须手动设置。
全局配置,作用于当前用户的所有仓库:
bash复制git config --global user.name "你的名字"
git config --global user.email "your_email@example.com"
我强烈建议把邮箱填成 GitHub 注册时用的邮箱,这样提交记录能正确关联到你的 GitHub 账号。如果你在 GitHub 上开启了邮箱隐私保护,GitHub 会给你一个形如 1234567+username@users.noreply.github.com 的替代邮箱,可以填那个。
还有一种情况是仓库级配置。比如这台机器上主要用个人身份提交,但某个项目要用公司的账号提交,可以在仓库目录里单独设置:
bash复制git config --local user.name "公司名字"
git config --local user.email "company_email@example.com"
用 --local 设置的值只对当前仓库生效,优先级高于全局配置。我的经验是:全局配置设个人身份,涉及公司的项目单独设仓库级身份,避免在多个身份之间反复切换。
5.2 从新建分支到推送成功的一条完整命令流
现在进入推送的核心流程。假设你已经完成了代码修改,想要推送到远端,完整的命令流是这样的:
查看当前仓库状态:
bash复制git status
这会显示哪些文件被修改了、哪些文件没有被跟踪。这一步很多人会跳过,我觉得属于坏习惯——提交之前确认一下改动范围,能避免把不该提交的文件一起推上去。
新建一个分支:
bash复制git checkout -b feature/my-first-feature
这条命令同时完成两件事:创建一个新分支,并切换过去。分支名用 feature/ 前缀是当前比较流行的命名习惯,实际可以根据项目规范调整。
把改动加入暂存区:
bash复制git add .
或者只添加指定文件:
bash复制git add src/main.py
生产环境我通常按文件添加,避免误提交无关改动。用 git add . 虽然省事,但把临时文件、日志文件一起 add 进去的案例我见过太多次了。
提交改动:
bash复制git commit -m "feat: add first feature"
提交信息建议遵循约定式提交的风格,feat 表示新功能,fix 表示修复,这样历史记录会整洁很多。
推送到远端:
bash复制git push -u origin feature/my-first-feature
这里的 -u 参数在首次推送时加上,作用是建立本地分支和远端分支的关联关系。以后在这个分支上直接执行 git push 或 git pull 就行,不需要再指定远端和分支名。
推送成功后,GitHub 页面上会提示你是否创建 Pull Request,参与团队协作的按流程操作即可。整个流程走完,你会发现完全不需要输入任何密码或 Token,SSH 免密的效果就体现在这里。
5.3 推送被拒时的三个常见原因与处理顺序
推送并不总是一帆风顺,尤其是协作仓库里容易碰到 push rejected 的报错。我整理了一下自己遇到过的三类最常见的情况和对应的处理顺序。
第一类:远端有本地没有的提交。这个报错信息通常是:
code复制! [rejected] main -> main (non-fast-forward)
error: failed to push some refs to 'git@github.com:...'
hint: Updates were rejected because the tip of your current branch is behind
翻译成人话就是:远端仓库里有别人新推的提交,而你的本地没有。Git 拒绝让你的推送覆盖掉这些新提交,这是保护机制在发挥作用。处理方法有两个,我推荐先拉取再推送:
bash复制git pull --rebase origin main
git push origin main
--rebase 参数会把你的本地提交"搬运"到远端最新提交的后面,这样提交历史是一条直线,不会产生多余的 merge 记录。
第二类:权限不足。报错信息带有 Permission to xxx.git denied to xxx。这说明当前密钥对应用户没有该仓库的写权限。处理方法:确认你推送的是自己名下的仓库,或者确认你已被添加为仓库的协作者。如果是新创建的仓库,检查一下是不是把项目名拼写错了。
第三类:分支名或远端分支不一致。Git 默认分支名可能是 master 或 main,新创建的 GitHub 仓库多数用 main。如果本地分支叫 master,想推送到远端的 main,需要显式指定:
bash复制git push origin master:main
这个语法含义是把本地的 master 分支推送到远端的 main 分支。
我个人的排错顺序是:先看报错信息里的提示,Git 的 hint 已经给了很多线索;再用 git status 和 git remote -v 确认本地和远端状态;最后才去查权限。大部分问题都能在第一步就定位到。
用表格整理这三类问题会更直观:
| 报错类型 | 核心原因 | 首选处理方式 |
|---|---|---|
| non-fast-forward | 远端有新提交,本地落后 | git pull --rebase 后重新 push |
| Permission denied | 没有仓库写入权限 | 检查仓库归属或协作者身份 |
| master:main 不匹配 | 本地分支与远端分支名不同 | 用 本地分支名:远端分支名 显式指定 |
SSH 配置好之后的日常推送流程,说实话体验是非常顺滑的。不用记忆 Token、不用面对密码过期的问题,这也是我为什么每次新装系统都第一时间把 SSH 密钥配好。等这套流程跑通,往后在 Linux 上和 GitHub 打交道就省心了。
