换一台新电脑、换一个操作系统,或者第一次从 HTTPS 方式切到 SSH 方式时,几乎每个用 Git 的人都会被 SSH 密钥配置这关卡一下。我之前帮同事排查过几次git push权限报错,发现大多数问题不是命令记不住,而是对“公钥和私钥的工作原理”没有一个清晰的画面:密钥生成完不知道该把哪一半交给 GitHub、哪一半锁在本地,Windows、macOS、Linux 三套系统的配置路径又各有差异,一步错就一路错。
这篇就把 Git 配置 SSH 密钥这件事从头到尾拆开讲一遍。内容覆盖跨平台完整实操:先讲明白非对称加密下公钥私钥到底怎么工作,再分别给出 Windows、macOS、Linux 下从环境准备、密钥生成、托管平台配置、ssh-agent 多密钥管理,到最后报错排查的完整链路。无论你是刚接触 Git 的小白,还是从 HTTPS 迁移到 SSH 的老手,都可以按图索骥跟着操作。
1. SSH 密钥的原理与为何比 HTTPS 更值得配置
1.1 HTTPS 和 SSH 两种 remote 方式的本质区别
Git 连接远程仓库有两种常见协议:HTTPS 和 SSH。HTTPS 的方式最直观,clone 的时候直接用https://github.com/xxx/repo.git,push 的时候输入用户名和密码(或 token)就能用。但问题也藏在“直观”里:每次都输入凭据不说,很多平台已经不再接受密码,必须去生成 Personal Access Token,然后把这串很长的 token 复制到剪贴板再粘贴到命令行。偶尔一次还能忍,天天 push 就非常烦。
SSH 方式则走的是“密钥对”的认证思路。你本地生成一对密钥:一把公钥、一把私钥。公钥上传到 GitHub/Gitee/GitLab 等托管平台,私钥留在本地绝不公开。之后每次git push、git pull,Git 都会自动基于这对密钥完成身份认证,不需要你再输入任何密码。配置一次,长期免密,这也是几乎所有开发者的最终选择。
这里有一个很多人踩过的误区:不小心用了 HTTPS clone,却以为配置好 SSH 密钥后 push 会自动走 SSH。实际上 remote 地址是什么协议,Git 就走什么协议。你要么一开始就用 SSH 地址 clone,要么之后手动把 remote 改成 SSH 地址,否则密钥配得再正确也白搭。
1.2 对称加密和非对称加密:先搞清楚这两个概念
要理解 SSH 密钥,得先分清两类加密方式。
对称加密就像你用同一把钥匙锁门和开门:加密和解密用的是同一把密钥。它的优点是快,缺点是这把钥匙怎么安全地交给对方?如果通过网络传,中途被截获,整条链路就废了。生活中常见的 AES 就是对称加密算法。
非对称加密则是一对钥匙:公钥和私钥。你可以把公钥当成一把谁都能看的挂锁,私钥是只有你有的开锁工具。公钥加密的数据,只有私钥能解密;反过来,私钥签名的数据,别人用你的公钥就能验证这个签名确实出自你手。
SSH 密钥认证正是建立在非对称加密的签名机制上。服务器并不需要知道你私钥的任何信息,只需要保存你的公钥。你连接时,服务器生成一段随机挑战数据发给你,你的客户端用私钥给它签名,服务器再用公钥验证签名是否有效。验证通过,就认定你是私钥的合法持有者。整个过程私钥从未离开你的电脑,这就是它安全的根本原因。
1.3 “公钥放平台、私钥锁本地”是铁律
在理解公钥私钥的工作原理之后,有一个原则必须刻在脑子里:公钥可以随便分发,私钥必须像银行卡密码一样保护。
公钥传到 GitHub、Gitee、GitLab、内网 GitLab,甚至公开贴到网上都不怕,它本来就是给别人看的。但私钥一旦泄露,等于别人拿到了一把能冒充你身份的工具,可以以你的名义向代码仓库提交代码、拉取私有仓库内容。
我见过有人图省事把.ssh目录整个打包发到聊天工具里,或者把私钥贴到 GitHub Gist 上,这是非常危险的。保存私钥的~/.ssh/id_ed25519或id_rsa文件,在 Linux/macOS 上务必设置成600权限,在 Windows 上也要保证只有当前用户能访问。
2. 环境准备:确认 Git 和 OpenSSH 客户端各就各位
2.1 三平台通用的检查命令
配置 SSH 密钥之前,先确认基础环境:Git 已安装,并且系统里有ssh-keygen命令可用。ssh-keygen是 OpenSSH 套件里的密钥生成工具,一般随系统自带或随 Git 一起安装。
检查方法是在终端里分别输入:
git --version ssh-keygen第一条能正常输出类似git version 2.40.1的版本号,说明 Git 已装好。第二条如果提示命令找不到,说明缺少 OpenSSH 客户端。不同平台处理方式不一样,往下看。
2.2 Windows:Git for Windows 是一站式方案
Windows 上最容易踩的坑是环境变量 PATH 没配好。强烈建议直接安装 Git for Windows,它自带 Git Bash、OpenSSH 客户端,以及整套 Unix 风格命令行工具。安装时注意一个选项:选择 “Use Git from the Windows Command Prompt” 或 “Use Git and optional Unix tools from the Command Prompt”,这样git和ssh-keygen才能直接在 CMD 和 PowerShell 里被找到。
也用 winget 一条命令装:
winget install --id Git.Git -e --source winget装完最好重启一次终端,再验证git --version。很多人装完 Git 不重开终端,环境变量没有刷新,然后就跑去问为什么命令找不到,这个细节值得留意。
Windows 下的 OpenSSH 客户端其实系统也自带,在“可选功能”里可以启用 Windows OpenSSH Client。但既然已经装了 Git for Windows,直接用它的ssh-keygen就行,没必要再折腾系统组件,逻辑上更统一。
2.3 macOS:CommandLineTools 足够
macOS 通常自带git和ssh-keygen。如果输入git --version提示需要安装命令行开发者工具,会弹窗引导你安装 CommandLineTools,也可以手动执行:
xcode-select --install装完后git、ssh-keygen、ssh-agent全部可用。如果你习惯用 Homebrew 管理工具链,也可以brew install git,但对 SSH 密钥这件事来说没什么必要。
2.4 Linux:根据发行版选择包管理器
Linux 下如果是 Debian/Ubuntu 系:
sudo apt update sudo apt install git openssh-client -yFedora / RHEL 系:
sudo dnf install git openssh-clients -y装完同样验证git --version。Ubuntu 服务器最常遇到的ssh 无法连接问题,一部分是服务端没装 openssh-server,另一部分是密钥没配对好,这在后面的排查章节会详细展开。
2.5 顺手把 Git 全局身份和基础行为配好
SSH 密钥负责认证“你是谁”,但 Git 提交记录里也要留下“你是谁”。这两者不冲突,但建议一起配好:
git config --global user.name "Your Name" git config --global user.email "you@example.com"在 Windows 上,我还建议多做两个配置。一个是处理中文文件名转义:
git config --global core.quotepath false不然git status会显示"\346\226\207\344\273\266"这种八进制转义序列,中文路径全变乱码。另一个是换行符处理,如果团队统一用 LF,可以执行:
git config --global core.autocrlf input这些配置不直接影响 SSH 密钥,但属于 Git 环境初始化的一部分,配好之后后续操作顺滑很多。
3. 跨平台密钥生成:算法选型、命令细节与私钥保护
3.1 选 ED25519 还是 RSA 4096
ssh-keygen支持多种算法,现在最主流的选择是 ED25519 和 RSA。两者对比:
| 对比项 | ED25519 | RSA 4096 |
|---|---|---|
| 密钥长度 | 固定,约 256 位 | 可指定,常用 4096 位 |
| 性能 | 快,密钥短 | 相对慢,密钥长 |
| 安全性 | 现代密码学,足够安全 | 经典方案,兼容性好 |
| 兼容性 | 需要较新的 OpenSSH 和托管平台支持 | 兼容最广,包括老服务器 |
| 适用场景 | 推荐的新项目首选 | 需要兼容旧系统的场景 |
现在 GitHub、Gitee、GitLab 都支持 ED25519,所以我的建议非常简单:新配置一律用 ED25519。除非你要连接一台内核很旧的服务器,或者一些历史遗留的内部 Git 服务,才考虑 RSA。
3.2 ssh-keygen 命令逐项拆解
打开终端,执行:
ssh-keygen -t ed25519 -C "your_email@example.com"参数含义:
-t ed25519:指定密钥算法。-C "your_email@example.com":给密钥加一个注释,通常写你的邮箱,方便在托管平台上识别这把密钥是哪台机器的。
执行后会有三次交互:
- 第一行要求输入保存路径,默认是
~/.ssh/id_ed25519。如果你想用默认路径,直接回车。 - 第二行要求输入 passphrase(口令)。这是一个额外保护层,可以留空直接回车,也可以设置一段口令。
- 第三行是再输入一遍确认。
生成完成后,.ssh目录下会出现两个文件:
id_ed25519:私钥,永远不要泄露。id_ed25519.pub:公钥,等下要复制到托管平台上。
3.3 passphrase 到底要不要设
passphrase 就像是给私钥加了一层密码保护。即使别人偷走了你的私钥文件,不知道 passphrase 也用不了。
但这里有个体验上的矛盾:如果你设置了 passphrase,每次 SSH 连接时理论上都要输入它一次,这感觉又回到了输密码的老路。解决办法是配合 SSH Agent 缓存私钥,只在开机后第一次使用输一次 passphrase,之后自动完成认证。
我的建议是:设置 passphrase,并且让 ssh-agent 帮你记住它。如果完全不设,私钥文件一旦泄露就等于城门大开。尤其笔记本容易丢,没有 passphrase 的私钥落到别人手里,对方可以直接拿去访问你的代码仓库。
3.4 Windows、macOS、Linux 生成密钥的差异细节
命令本身三个平台通用,但有些细节不同:
Windows 上如果用的是 Git Bash,交互体验和 Linux 完全一致。如果你习惯用 PowerShell,同样可以运行ssh-keygen,但路径显示会是C:\Users\你的用户名\.ssh\id_ed25519。注意 PowerShell 里默认路径中的~也能正常解析。
macOS 上生成密钥后,很多人的习惯是把公钥直接放进剪贴板:
cat ~/.ssh/id_ed25519.pub | pbcopyLinux 桌面环境没有 pbcopy,老老实实用cat输出再手动复制:
cat ~/.ssh/id_ed25519.pubWindows 上可以用:
type %USERPROFILE%\.ssh\id_ed25519.pub或者:
clip < %USERPROFILE%\.ssh\id_ed25519.pubclip命令会把输出内容放到剪贴板,很方便。但要注意.ssh目录里如果存在config文件且格式有问题,ssh-keygen或ssh命令可能会抱怨bad owner or permissions on C:\Users\...\.ssh\config,这在后面的排查章节会专门讲。
3.5 私钥文件的权限保护
Linux/macOS 上执行:
chmod 700 ~/.ssh chmod 600 ~/.ssh/id_ed25519.ssh目录的权限别开太大,700表示只有你本人能进入目录。私钥文件600表示只有你本人能读写。如果权限设置过宽,OpenSSH 会直接拒绝使用这把私钥,提示权限不安全。
Windows 上无法用chmod达到同样效果,需要保证私钥文件不能有Everyone等用户的访问权限。最简单的判断方法是:如果 SSH 连接时出现UNPROTECTED PRIVATE KEY FILE的报错,就说明文件权限有问题,需要调整 ACL。
4. 把公钥交给托管平台:从 GitHub 到 Gitee、GitLab
4.1 GitHub 添加 SSH Key 的操作路径
登录 GitHub,进入Settings,左侧菜单选SSH and GPG keys,点击New SSH key。Title 栏写这台机器或这个密钥的用途,建议写成ThinkPad-Windows、MacBook-Pro这种你能认出来源的名字。Key type 选Authentication Key(新版界面有区分签名密钥和认证密钥,认证密钥用于 Git 的 push/pull,选 Authentication 就对了)。然后把你复制的公钥内容粘贴到 Key 栏。
Linux/macOS 上手动复制公钥内容:
cat ~/.ssh/id_ed25519.pub输出是一整行以ssh-ed25519开头,以你的邮箱注释结尾的文本,全部复制,不要漏行。
4.2 Gitee 的公钥配置入口
Gitee 是国内常用的代码托管平台,入口在右上角头像 →设置→安全设置→SSH 公钥。和 GitHub 的逻辑一致,把公钥粘贴到输入框并保存。
Gitee 早期某些服务对 ED25519 支持不完善,现在基本都兼容了。如果你在 Gitee 用 ED25519 测试连接不通过,可以临时生成一把 RSA 密钥试试,但在大多数新场景下 ED25519 都没有问题。
4.3 GitLab 的配置入口
公司内部自建 GitLab 或使用 gitlab.com 的,入口在头像 →Preferences(偏好设置)→ 左侧SSH Keys。粘贴公钥后,GitLab 会实时显示这把密钥的指纹信息,用于后面对照。同样,Key 名称建议写清楚来源。
4.4 把本地远程地址从 HTTPS 改成 SSH
这一步非常关键。如果你之前是用 HTTPS 地址 clone 的仓库,现在配置好 SSH 密钥,还得改 remote 地址。
在仓库目录下先查看当前地址:
git remote -v如果输出形如:
origin https://github.com/yourname/your-repo.git (fetch) origin https://github.com/yourname/your-repo.git (push)说明你还在用 HTTPS,需要改成 SSH 地址:
git remote set-url origin git@github.com:yourname/your-repo.gitGitee 对应改成git@gitee.com:yourname/your-repo.git,GitLab 改成git@gitlab.com:yourname/your-repo.git。改完后再次git remote -v确认,接下来 push 和 pull 就会走 SSH 协议了。
4.5 测试连接:每个平台的验证命令
配置好公钥并修改 remote 地址后,可以单独测试 SSH 认证是否成功:
GitHub:
ssh -T git@github.com第一次连接时会出现:
The authenticity of host 'github.com (IP)' can't be established. ED25519 key fingerprint is SHA256:+DiY3wvvV6TuJJhbpZisF/zLDA0zPMSvHdkr4UvCOqU. Are you sure you want to continue connecting (yes/no/[fingerprint])?输入yes回车。这个提示的意思是这台主机首次连接,OpenSSH 要确认它记录的指纹是否可信。回车后主机指纹会被记录到~/.ssh/known_hosts,下次不会再问。
连接成功的返回:
Hi yourname! You've successfully authenticated, but GitHub does not provide shell access.Gitee 则返回:
Hello yourname! You've connected to Gitee.com by SSH successfully!GitLab 返回:
Welcome to GitLab, @yourname!看到类似信息说明 SSH 密钥链路已经通了。如果这一步失败,直接跳到第 6 章排查。
5. ssh-agent 与多密钥管理:一台电脑管多平台账号
5.1 ssh-agent 解决什么问题
如果你设置了 passphrase,每次 SSH 连接都要输入一次显然不可接受。ssh-agent 就是一个帮你保存已解锁私钥的后台进程:你先把私钥“加入”它,并输入一次 passphrase,之后 agent 一直替你持有这把已解锁的私钥,后续连接不再要求输入。
在配置多个账号时,ssh-agent 还有一个隐藏作用:当你改动 Git remote 指向不同平台时,agent 会提供不同的私钥去尝试认证。为了避免密钥太多导致服务器端“认证次数超限”直接拒绝,推荐配合.ssh/config指定每个平台用哪把密钥。
5.2 三平台启用 ssh-agent 的差异
Windows 的 Git Bash:
eval "$(ssh-agent -s)" ssh-add ~/.ssh/id_ed25519这里有个体验差异:每次新开一个 Git Bash 窗口,agent 进程都会重新启动,私钥需要重新ssh-add。觉得麻烦的话,可以在~/.bashrc里加一行eval "$(ssh-agent -s)",然后让系统每次启动时自动运行一次ssh-add。但这又涉及把 passphrase 自动化的问题,比较复杂。
macOS 上比较顺手,因为 macOS 提供了 Keychain 集成:
ssh-add --apple-use-keychain ~/.ssh/id_ed25519把私钥加入 Agent 并存在系统钥匙串里,之后重启也不用重复输入 passphrase。
Linux 桌面环境通常自带 ssh-agent 进程,多数桌面登录后就已经在运行。检查是否在运行:
echo $SSH_AUTH_SOCK如果输出空,手动启动:
eval "$(ssh-agent -s)"5.3 多密钥场景下的 config 文件写法
很多开发者的实际场景是:一个 GitHub 账号,一个 Gitee 账号,可能还有公司内网 GitLab。如果所有平台都用同一对密钥当然没问题,但如果你想分开管理,就要用到~/.ssh/config。
例如本地有两对密钥:~/.ssh/id_ed25519_github和~/.ssh/id_ed25519_gitee。编辑~/.ssh/config(Windows 下是C:\Users\你的用户名\.ssh\config):
Host github.com HostName github.com User git IdentityFile ~/.ssh/id_ed25519_github IdentitiesOnly yes Host gitee.com HostName gitee.com User git IdentityFile ~/.ssh/id_ed25519_gitee IdentitiesOnly yes几个参数的含义:
Host:你在 SSH 命令或 Git remote 里使用的别名。这里直接写github.com,就可以匹配git@github.com:xxx/yyy.git这种地址。HostName:实际连接的主机名,一般和 Host 一致。User:SSH 登录用户名,Git 托管平台固定为git。IdentityFile:指定使用哪把私钥。IdentitiesOnly yes:这条很重要。它告诉 SSH 只使用这里指定的私钥,不要拿 ssh-agent 里所有私钥轮番尝试,能避免不少认证问题。
配置完成后,后续git push会根据 remote 地址自动找到对应的私钥。
5.4 多密钥配置中的常见迷思
有人配置了 config,发现ssh -T git@github.com测试成功,但git remote -v明明显示的是git@github.com,push 时依然报Permission denied (publickey)。这种多半是 config 文件里IdentityFile路径写错了,或者私钥文件的权限不对。
还有人把Host写成了自己的别名,比如Host mygithub,然后 remote 地址也改成了git@mygithub:user/repo.git。这也能工作,但很多人测试ssh -T git@github.com时发现没有走别名规则,误以为配置失败。实际上ssh -T git@mygithub才会走该规则,注意这个区别。
6. 连接报错排查清单:从 Permission denied 到 known_hosts
6.1 典型报错速查表
以下是我在实际排查中遇到最多的几种报错,把症状、原因和解决办法整理成了表格:
| 报错信息 | 可能原因 | 处理方式 |
|---|---|---|
Permission denied (publickey) | 公钥没添加到托管平台,或本地私钥路径不对 | 检查公钥是否已添加;用ssh -vT git@github.com看详细日志 |
Host key verification failed | known_hosts 里记录的主机指纹和服务器实际指纹不一致 | 用ssh-keygen -R github.com清除旧记录后重连 |
Bad owner or permissions on C:\Users\...\.ssh\config | Windows 下 config 文件权限设置了过多用户 | 用 icacls 或 GUI 修正权限,只保留当前用户完全控制 |
git@github.com: Permission denied且-vT显示no mutual signature algorithm | 服务器或客户端不支持 RSA/SHA-1 老算法 | 换 ED25519 密钥,或临时启用ssh-rsa兼容 |
ssh: connect to host github.com port 22: Operation timed out | 网络策略封了 22 端口 | 换 GitHub 的 443 端口的 SSH 服务,或换 HTTPS 方式 |
6.2 Windows 上 Bad owner or permissions 的完整修复流程
这个报错在 Windows 上非常经典。报错长这样:
bad owner or permissions on c:\users\你的用户名\.ssh\config问题出在.ssh目录(通常是config文件)的访问权限被设置得过于开放,OpenSSH 为了安全直接拒绝读取。
修复流程需要打开文件属性面板或用 icacls 命令。先说命令行方式,以管理员身份打开 PowerShell:
icacls "C:\Users\你的用户名\.ssh\config" /inheritance:r icacls "C:\Users\你的用户名\.ssh\config" /grant:r "$($env:USERNAME):F"第一条命令移除继承的所有权限,第二条命令只给当前用户完全控制权。执行完重新测试 SSH 连接,通常就恢复正常。
如果不想用命令,打开资源管理器,找到.ssh下的config文件,右键 → 属性 → 安全 → 高级,先改所有者为自己,然后禁用继承,再添加自己并授予完全控制权限,移除其他所有用户条目。
这个坑的本质是 Windows 文件系统 ACL 权限模型和 Unix 的chmod 600完全不同,所以很多从 mac/Linux 切到 Windows 的开发者会一头雾水。知道原因后就不慌了。
6.3 Linux/Ubuntu 上 SSH 无法连接的排查思路
热词里“ubuntu ssh无法连接”也是高频问题。如果本地密钥配置没问题,但连接 Linux 服务器报错,排查思路应该是:
先确认服务端 SSH 服务是否在运行:
sudo systemctl status ssh没运行就启动并设成开机自启:
sudo systemctl enable --now ssh再检查端口是否被防火墙拦截:
sudo ufw status通常需要放行 22 端口:
sudo ufw allow OpenSSH然后确认服务端~/.ssh/authorized_keys文件里有没有写入你的公钥。文件权限必须是:
chmod 700 ~/.ssh chmod 600 ~/.ssh/authorized_keysauthorized_keys权限过宽是服务端拒绝公钥登录的常见原因。修改后重启 SSH 服务:
sudo systemctl restart ssh如果还是不行,打开服务端 SSH 调试日志,看sudo journalctl -u ssh或/var/log/auth.log,多半能在日志里看到明确原因。
6.4 老系统兼容性:RSA、SHA-1 与 known_hosts 清理
有些老服务器生成的是 RSA 密钥,而新版本 OpenSSH 默认关闭了ssh-rsa这种基于 SHA-1 的签名算法,于是连接时报no mutual signature algorithm。临时解法是在.ssh/config对应 Host 下加两行:
Host old-server HostName 192.168.1.10 User root HostKeyAlgorithms +ssh-rsa PubkeyAcceptedAlgorithms +ssh-rsa但这只是兼容旧机器的过渡手段,新环境建议还是用 ED25519。
known_hosts 文件是 OpenSSH 用来记录服务器指纹的。如果你重装过服务器系统,或服务器换了主机密钥,再连接时就会报Host key verification failed。这时候不用慌,也不是被中间人攻击,绝大多数情况就是服务器指纹变了。清除旧记录:
ssh-keygen -R github.com或者指定清除某端口:
ssh-keygen -R "[github.com]:443"清理后重新连接,按提示输入yes就能继续。
6.5 网络端口被限制时怎么办
某些办公网络会屏蔽 22 端口,导致 SSH 连接超时。GitHub 官方提供了 443 端口的 SSH 服务,配置方法是在~/.ssh/config中:
Host github.com HostName ssh.github.com Port 443 User git然后测试:
ssh -T git@github.com只要 443 端口可访问,就能正常走 SSH 认证。这是纯技术层面的替代方案,适用于所有标准 SSH 场景。
7. 配置完成不等于万事大吉:几个值得养成的习惯
密钥配置好之后,有些细节值得在日常使用中注意。
第一,如果你在使用多台电脑,每一台机器上都要单独生成一对密钥,然后把公钥分别添加到托管平台。不要试图把所有机器的私钥统一成一把,那样一旦某台机器丢了,你必须第一时间到托管平台删除对应公钥,否则风险极大。每台机器维护独立的密钥对,发现哪台机器出问题就单独吊销哪一把,控制风险面。
第二,.ssh目录里最好放一个README或者至少用文件名来区分每把密钥属于哪台机器。VSCode 连接 SSH 远程服务器、批量登录多台服务器这类场景,密钥多了之后如果不做区分,很容易拿错私钥连错机器,浪费时间。
第三,定期检查托管平台上的 SSH Keys 列表,及时删掉已经不再使用的机器。换电脑是家常便饭,但很多人换完电脑就把旧机器的公钥留在平台上一辈子,这些“僵尸公钥”其实是不小的安全隐患。
最后分享一个小技巧:如果平时习惯用 VSCode 远程开发,SSH 密钥配置好后,Remote-SSH扩展可以直接使用本机的~/.ssh/config,你为 Git 配置的多密钥规则同样适用于远程开发连接。也就是说,同一套配置,既管 Git 代码推送,又管远程服务器登录,一劳永逸。这也是我为什么一直强调密钥统一管理的原因——配一次,长期受益,值得认真对待。