👉 这是我自己每次换电脑都要翻一遍的私人备忘录,索性公开成博客。 目标是:从一台空白机器开始,5 分钟内从 Bitwarden 把 SSH 密钥救回来,让 git push 不再每次问我 passphrase。Windows / Linux 双平台并行讲。
📑 这篇会讲什么
先让你大致知道这篇博客的结构,再决定要不要往下读:
- 🔹 心智模型:搞懂 SSH 的「方向」,理清密钥对到底归属谁
- 🔹 准备工作:适用场景 + 前置检查
- 🔹 五步操作:准备私钥 → 启 ssh-agent → 加进 agent → 让 Git 走对 ssh → 公钥贴 GitHub
- 🔹 验证全链路 + 平台特殊情况(Windows / Linux 各自的坑)
- 🔹 Bitwarden 条目模板:怎么存才能未来一键恢复
- 🔹 日常使用 + 排错速查表
- 🔹 压缩流程:5 步速查,回头翻只看这里就够了
- 🔹 最后的心智模型:把所有命令归到一条原则上
🧠 写在最前:先搞懂 SSH 的「方向」
说实话,我自己一开始也以为 SSH 是「服务器之间的通讯协议」,每台机器都生成一对密钥放着就好。后来踩坑多了才意识到——这个模型对了一半,错了另一半。
真正的模型:C/S,不对称
SSH 走的是 客户端-服务器(C/S)模型,两端角色不对等:
- 客户端 = 发起连接的那一方(比如你的笔记本想
git push) - 服务器 = 被连接的那一方(比如 GitHub、云主机、跳板机)
[客户端 = 持私钥的发起方] ──SSH──> [服务器 = 存公钥的接收方]
↑ ↑
私钥永不离开本机 authorized_keys / GitHub 设置页
🎯 一句话口诀
👉 谁发起连接,谁持有私钥;谁被连接,谁存对方的公钥。
🔗 由这个模型推导出的三条铁律
-
🔹 密钥对的归属单位是「身份」,不是「机器类型」
不是「每个服务器」要生成一对,而是「每一台会发起 SSH 连接的机器」要生成一对。笔记本一对、工作站一对、CI 节点一对;同一台机器也可以为不同身份建多对(个人 GitHub + 公司 GitHub)。
-
🔹 私钥从生成到销毁,永远不离开本机
不通过邮件、聊天、云盘、U 盘传播。要在新机器上「用同一个身份」?要么从加密的密码管理器(如 Bitwarden)恢复,要么直接在新机器上生成新密钥对、把新公钥重新挂到目标服务上。
-
🔹 公钥可以随便公开,但必须放在「被连接方」
在 GitHub,是网页里的
Settings → SSH Keys;在云主机,是~/.ssh/authorized_keys。本质都是「服务器的白名单」。
这一节是整篇教程的「为什么」。后面所有命令——为什么要 chmod 600、为什么要 ssh-agent、为什么 Windows 要多一行 core.sshCommand——回过头看都是在保护这个「私钥死守本机」的边界。
🎯 适用场景
这篇文章是写给「这一刻的我」的,你如果命中了下面任一条,也能照抄:
- 重装 Windows / Linux / 拿到新电脑,想最快恢复 SSH 工作环境
- 想让 GitHub / 服务器 SSH 走
ssh-agent+ passphrase 双保险 - 已经在 Bitwarden 存了旧密钥(或准备第一次生成新密钥)
- 在服务器上配 ssh-agent 被 systemd 报错搞烦了,想要一个「不依赖 systemd 也能用」的方案
📋 前置检查
- 🪟 Windows 10 / 11(系统自带 OpenSSH) 或 🐧 Linux(OpenSSH 客户端,绝大多数发行版默认装好)
- Git 已安装
- Bitwarden 已登录(如果走恢复路线)
① 准备私钥(二选一)
🟢 方案 A:从 Bitwarden 恢复旧密钥(换电脑首选)
这是我每次换机走的路。理由很简单:保持同一个身份,GitHub 那边一行不用动。
- 打开 Bitwarden,找到对应条目(比如我自己的是
SSH Key - GitHub (ed25519)) - 复制私钥完整内容(含
-----BEGIN ...-----/-----END ...-----行,少一行就废) - 把私钥写进本机:
🪟 Windows(PowerShell):
New-Item -ItemType Directory -Force $env:USERPROFILE\.ssh | Out-Null
notepad $env:USERPROFILE\.ssh\id_ed25519
🐧 Linux:
mkdir -p ~/.ssh && chmod 700 ~/.ssh
nano ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
❗ Linux 上文件权限必须 600,否则 SSH 会以「权限过开」为由拒绝使用密钥。这是我第一次踩的坑——SSH 宁可让你登不上去,也不愿用一个全世界都能读的私钥。这个「过度保守」的设计其实是好事。
-
- (可选)从私钥反推公钥:
-
路径用默认
~/.ssh/id_ed25519 -
❗ 必须设强 passphrase
没 passphrase 的私钥 = 一个明文凭证。一旦本机被入侵或备份泄露,攻击者直接拿到你的 GitHub 身份。passphrase 给你一个「加密层」缓冲。
-
生成完别忘了把新公钥贴到 GitHub(步骤 ⑤)和新建 Bitwarden 条目(最后的模板章节)
- 你输一次 passphrase,agent 把解密后的密钥握在内存里
- 之后所有需要这个密钥的命令(
git push、ssh user@host),都让 agent 在背后帮忙签名 - 你不用每次都输一遍 passphrase
- 有输出(一个 socket 路径) = 桌面,直接跳到步骤 ③
- 空 = 服务器,往下看
- 输入 passphrase(从 Bitwarden 复制)
- 验证(两平台一致):
- ✅ 私钥必须带 passphrase(即 Bitwarden 密码字段非空)—— 否则 vault 一旦泄露,密钥裸奔
- ✅ passphrase 和私钥存在同一条目(恢复时一一对应,不会错配)
- ❌ 不要把未加密的明文私钥直接存进任何地方
- ✅ 每生成一把新密钥 → 新建条目,不要覆盖旧的(保留历史可追溯)
- ✅ Bitwarden 主密码开 2FA
- 🐧 Linux 特别注意:
~/.ssh权限 700、私钥 600,否则 SSH 拒绝使用 - 🔹 首次配置、agent 里还没任何密钥(也可以靠
AddKeysToAgent yes在第一次ssh host时自动喂入) - 🔹 主动
ssh-add -D清空过 agent - 🔹 把
ssh-agent服务设为 Manual / Disabled 后 - 桌面环境(GNOME / KDE)+ keychain:✅ 完全零操作,开机自动解锁
- 服务器(按 per-SSH-session 配置完成):✅ 每次新 SSH 登录时,第一次用到密钥(
ssh -T或git push)会被问一次 passphrase,之后整个 SSH 会话免输;登出自动清理 - 未配
AddKeysToAgent yes:每次 SSH 登录后手动ssh-add ~/.ssh/id_ed25519 - 🔹
chmod 600是在保护文件层 - 🔹
passphrase是在保护存储层 - 🔹
ssh-agent是在控制内存层的解密窗口 - 🔹
core.sshCommand是在保证调用链没走偏 - 🔹 Bitwarden 是在解决跨设备恢复
🪟 Windows:
ssh-keygen -y -f $env:USERPROFILE\.ssh\id_ed25519 > $env:USERPROFILE\.ssh\id_ed25519.pub
🐧 Linux:
ssh-keygen -y -f ~/.ssh/id_ed25519 > ~/.ssh/id_ed25519.pub
(会要求输 passphrase——这就是 Bitwarden 里存的那个)
🟡 方案 B:生成新密钥(换身份或全新设备)
如果你想给新机器一个独立身份(推荐做法,特别是工作机),那就重新生成。两平台命令一致:
ssh-keygen -t ed25519 -C "备注这个密钥对的信息"
② 启动 ssh-agent(一次性配置)
讲讲 ssh-agent 在这里扮演什么角色。
👉 ssh-agent 是一个常驻进程,负责「在内存里解密私钥并代为签名」。
它存在的意义:
本质是「用进程隔离,换便利性」——比把密钥裸放着安全,比每次手敲 passphrase 省心。
🪟 Windows
管理员 PowerShell:
Set-Service ssh-agent -StartupType Automatic
Start-Service ssh-agent
✅ 之后开机自启,永远不需要再跑。 验证:
Get-Service ssh-agent
看到 Status: Running 即可。
💡 Windows 版 ssh-agent 的关键差异:和 Linux 的进程级 agent 不同,Windows 这版是 系统服务 + DPAPI 持久化。ssh-add(或通过 AddKeysToAgent yes 在 ssh 解密成功后自动喂入)的密钥,会被 DPAPI 用你的 Windows 登录凭证加密后写入注册表 HKCU\Software\OpenSSH\Agent\Keys,重启后服务自启 → 自动从注册表恢复已加载的密钥。一次输 passphrase,长期托管。这是 Microsoft 移植版的特定行为,和 Linux 的「重启即清空」直觉不一样。
🐧 Linux
先判定场景(这一步决定后面的方案)
🔹 桌面 Linux(GNOME / KDE / Ubuntu Desktop / Fedora Workstation)→ 多数情况 ssh-agent 已由桌面环境自动启动并集成 keychain 🔹 服务器 / 容器 / 跳板机(云 ECS、VPS、Docker、纯 SSH 登录的 Linux)→ 没有自动 agent,需要手动配置 判定命令:
echo $SSH_AUTH_SOCK
🟢 桌面 Linux
桌面环境已经在帮你管 agent,通常什么都不用做。
首次 ssh-add 会弹图形界面问 passphrase,勾选「保存到 keychain」之后开机自动解锁。
🟢 服务器 / 非桌面 Linux —— per-SSH-session 推荐方案
❗ 先劝退一条路:不要走 systemctl --user enable --now ssh-agent.service:
🔹 多数发行版(Ubuntu 22.04 / 24.04、Debian 等)打包的 ssh-agent.service unit 缺 [Install] 段,enable 直接报 The unit files have no installation config
🔹 即便手写 unit 绕过,「agent 跟着开机时间常驻」对服务器是反模式 —— 解密后的密钥长期驻留内存,且和「一次 SSH 登录 = 一次身份验证」的信任边界不对齐
我选择的粒度:以「一次 SSH 登录」为生命周期。登入时拉一个 agent,登出时随会话自然清理。
这其实是把前面那条铁律「私钥死守本机」延伸到了内存层面——解密后的私钥也不该比会话活得更久。
① 写入 ~/.bash_profile
cat > ~/.bash_profile <<'EOF'
# Source .bashrc for interactive shells (必须!否则丢 PS1 / alias / history 配置)
[ -f "$HOME/.bashrc" ] && . "$HOME/.bashrc"
# ssh-agent per SSH login session
SSH_ENV="$HOME/.ssh/agent-env"
start_agent() {
ssh-agent -s > "$SSH_ENV"
chmod 600 "$SSH_ENV"
. "$SSH_ENV" > /dev/null
}
if [ -f "$SSH_ENV" ]; then
. "$SSH_ENV" > /dev/null
kill -0 "$SSH_AGENT_PID" 2>/dev/null || start_agent
else
start_agent
fi
EOF
❗ 这是我踩过最阴的坑:Ubuntu 默认没有 .bash_profile,登录 shell 走 .profile 间接 source .bashrc。一旦你自己建了 .bash_profile,bash 只读它、不再读 .profile 也不读 .bashrc。所以第一行必须显式 source .bashrc,否则会丢:
🔹 彩色 PS1(用户名/主机名变成默认白色)
🔹 所有 alias(ll、la 等)
🔹 history 去重、追加等配置
我第一次配完登进服务器,整个 prompt 变白,alias 全没——还以为系统坏了,折腾半小时才反应过来是 .bash_profile 屏蔽了 .bashrc。
② 修改 ~/.bashrc(兜底子 shell / tmux 重连)
找到文件开头的 non-interactive guard:
case $- in
*i*) ;;
*) return;;
esac
紧接着插入一行:
[ -f "$HOME/.ssh/agent-env" ] && . "$HOME/.ssh/agent-env" > /dev/null
🔹 作用:tmux 跨 SSH 重连、或手动 bash 进子 shell 时,能从 agent-env 文件复用同一个 agent
🔹 位置必须在 guard 之后,否则 scp 等非交互式调用也会执行,污染环境
③ 配置 ~/.ssh/config
cat >> ~/.ssh/config <<'EOF'
Host *
AddKeysToAgent yes
IdentityFile ~/.ssh/id_ed25519
EOF
chmod 600 ~/.ssh/config
🔹 AddKeysToAgent yes:首次用到密钥时自动加载进 agent,免手动 ssh-add
🔹 第一次 ssh -T git@github.com 或 git push 输一次 passphrase,整个 SSH 会话内免输
④ 退出并重新 SSH 登录
exit
# 在本地重新 ssh 进来
⚠️ 必须重新登录! 登录脚本只在登录那一刻读一次。在当前 shell source ~/.bash_profile 只能验证语法,不能完整模拟新登录会话。
⑤ 验证
echo $SSH_AUTH_SOCK # 应有值,类似 /tmp/ssh-XXXXXX/agent.1234
ssh-add -l # "The agent has no identities."(agent 活着、还没密钥)
ssh -T git@github.com # 第一次:输 passphrase → Hi <user>!
ssh -T git@github.com # 第二次:直接 Hi <user>! 不再问
⚖️ Linux 三种方案对比
| 方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 桌面 keychain | GNOME/KDE 桌面 | 零配置、图形化解锁、开机持久 | 仅桌面环境 |
| **per-SSH-session(推荐)** | **服务器、容器、跳板机** | 简单、信任边界清晰、无 systemd 依赖 | 每次 SSH 登录输一次 passphrase |
| systemctl --user 常驻 | 极少数:每天反复 SSH 进出几十次、且不用 tmux | 跨会话不重复输密码 | 需手写 unit、`enable-linger`、密钥长期驻留内存 |
③ 把私钥加进 agent
🪟 Windows:
ssh-add $env:USERPROFILE\.ssh\id_ed25519
🐧 Linux:
ssh-add ~/.ssh/id_ed25519
ssh-add -l
能看到指纹即成功。
🔹 Linux 桌面通常用 GNOME Keyring / KDE Wallet 集成,首次 ssh-add 会弹图形界面问 passphrase,可勾选「保存到 keychain」—— 之后开机自动解锁,无需每次 ssh-add。
④ 让 Git 走系统 OpenSSH
🪟 Windows(关键!)
git config --global core.sshCommand "C:/Windows/System32/OpenSSH/ssh.exe"
🔹 没这一步,Git for Windows 会用自带的 MSYS ssh,那是一个独立的 ssh 实现,看不到 Windows 原生 ssh-agent 服务——结果就是 agent 明明在跑、密钥也加载了,git push 还是问你 passphrase。
🔹 这条配置是把 git 的 ssh 指针掰回到 Windows 自家的 OpenSSH 上。
这条我第一次没加,被反复问 passphrase 问到怀疑人生。
🐧 Linux
✅ 不需要这一步。Linux 上 git 直接调用系统 /usr/bin/ssh,天然连接系统 ssh-agent。
⑤ 把公钥贴到 GitHub
这是「把公钥发给服务器」的具体操作——回想开头那条铁律:公钥放在被连接方。GitHub 在这里就是「被连接方」。 复制公钥到剪贴板: 🪟 Windows:
Get-Content $env:USERPROFILE\.ssh\id_ed25519.pub | clip
🐧 Linux(X11):
xclip -sel clip < ~/.ssh/id_ed25519.pub
🐧 Linux(Wayland):
wl-copy < ~/.ssh/id_ed25519.pub
🐧 Linux 兜底(直接显示再手动复制):
cat ~/.ssh/id_ed25519.pub
去 GitHub → Settings → SSH and GPG keys → New SSH key,粘贴保存。
(从 Bitwarden 恢复的旧密钥,如果 GitHub 上已经有,跳过这一步——身份没变,对方白名单也不用动。)
✅ 验证全链路
两平台一致:
ssh -T git@github.com
看到 Hi <你的GitHub用户名>! You've successfully authenticated... = ✅
任意 git 仓库里:
git push
不问 passphrase = 全链路打通。
⚠️ 平台特殊情况
🪟 Windows:Git Bash 的 ssh 不连系统 agent
Git Bash 里直接跑 ssh -T git@github.com 会失败 —— 它用的是 MSYS 自带 ssh,不连 Windows ssh-agent。
🔹 不影响 git push(因为已有 core.sshCommand)
🔹 如果非要在 Git Bash 里测,用完整路径:
/c/Windows/System32/OpenSSH/ssh.exe -T git@github.com
👉 平时测试只用 PowerShell 的 ssh -T 或任何地方的 git push。
🐧 Linux:自动加载密钥
想让 ssh-agent 在首次用到密钥时自动加载(少打一次 ssh-add),编辑 ~/.ssh/config:
Host *
AddKeysToAgent yes
IdentityFile ~/.ssh/id_ed25519
之后第一次 git push 时弹一次 passphrase 输入,之后全免。
📝 Bitwarden 条目模板
建一条 Login 类型条目(不要用 SSH Key 类型——那是给 Bitwarden SSH Agent 功能用的,跟本指南方案不兼容)。
| 字段 | 内容 |
|---|---|
| 名称 | `SSH Key - GitHub (ed25519) - <设备名>` |
| 用户名 | `id_ed25519` |
| 密码 | **passphrase 本体** |
| 附件 / Notes | **私钥完整内容**(含 BEGIN/END 行) |
Notes 字段建议写入(双平台恢复步骤):
生成日期: YYYY-MM-DD
设备: <Windows 笔记本 / Linux 工作站 / ...>
邮箱: <你的邮箱>
算法: ed25519
公钥指纹: <ssh-keygen -lf id_ed25519.pub 的输出>
关联服务: GitHub (<你的 GitHub 邮箱>)
恢复步骤(Windows):
1. 复制 passphrase 字段,先放剪贴板
2. 复制私钥到 C:\Users\<你>\.ssh\id_ed25519
3. PowerShell: ssh-add $env:USERPROFILE\.ssh\id_ed25519
4. git config --global core.sshCommand "C:/Windows/System32/OpenSSH/ssh.exe"
5. ssh -T git@github.com 验证
恢复步骤(Linux):
1. 复制 passphrase 字段,先放剪贴板
2. mkdir -p ~/.ssh && chmod 700 ~/.ssh
3. 把私钥写入 ~/.ssh/id_ed25519,然后 chmod 600 ~/.ssh/id_ed25519
4. ssh-add ~/.ssh/id_ed25519
5. ssh -T git@github.com 验证
❗ 安全要点(跨平台一致)
🔄 日常使用
🪟 Windows
✅ 通常零操作。Windows ssh-agent 服务会把已加载的密钥用 DPAPI 加密后写入注册表 HKCU\Software\OpenSSH\Agent\Keys,开机自启后自动解密回 agent 内存——passphrase 已被「Windows 登录态」托管。
只有以下情况需要手动 ssh-add 一次:
ssh-add $env:USERPROFILE\.ssh\id_ed25519
❗ 安全含义:「能以你的 Windows 用户身份执行代码 = 能用你的私钥签名」。passphrase 在此模式下不再被询问,它实际上等价于你的 Windows 登录态。便利性 ↔ 本机被入侵时的爆炸半径,自行权衡。
🐧 Linux
🧭 排错速查
| 现象 | 平台 | 检查 / 修复 |
|---|---|---|
| `ssh-add` 报 `Error connecting to agent` | 🪟 | `Get-Service ssh-agent` 是否 Running;不在则回到步骤 ② |
| `ssh-add` 报 `Could not open a connection to your authentication agent` | 🐧 | `echo $SSH_AUTH_SOCK` 看是否为空;空则 `eval "$(ssh-agent -s)"` 或启动 systemd user service |
| `Permissions ... are too open` 报错 | 🐧 | `chmod 700 ~/.ssh && chmod 600 ~/.ssh/id_ed25519` |
| `git push` 一直问 passphrase | 🪟 | ① `ssh-add -l` 看 agent 是否持有;② `git config --global --get core.sshCommand` 看是否指向 Windows OpenSSH |
| `git push` 一直问 passphrase | 🐧 | ① `ssh-add -l` 看 agent;② 检查 `~/.ssh/config` 是否有 `IdentitiesOnly yes` 误强制走文件 |
| `ssh -T` 在 PowerShell 通、Git Bash 不通 | 🪟 | 正常现象,用完整路径 `/c/Windows/System32/OpenSSH/ssh.exe -T git@github.com` 或直接 `git push` 测 |
| GitHub 报 `Permission denied (publickey)` | 🪟🐧 | 公钥没贴 / 贴到了别的 GitHub 账号;重新复制贴一次 |
| 重启后 `ssh-add -l` 显示 `no identities` | 🐧 | Linux 非桌面环境的正常现象 —— agent 是进程,重启即清空,重新 `ssh-add` 即可;桌面 Linux 配 keychain 集成可免 |
| 重启后 `ssh-add -l` 显示 `no identities` | 🪟 | **不正常**。Windows ssh-agent 服务应跨重启持久化。检查:① `Get-Service ssh-agent` 是否 Running 且 StartType=Automatic;② `Get-ChildItem "HKCU:\Software\OpenSSH\Agent\Keys"` 是否有子项;都没有则重新 `ssh-add` 一次 |
| `systemctl --user enable ssh-agent.service` 报 `no installation config` | 🐧 | unit 缺 `[Install]` 段是常态。**改走 per-SSH-session 方案**(步骤 ② Linux 章节),别纠缠 systemd |
| 自建 `.bash_profile` 后 SSH 登录用户名/主机名变白、alias 全失效 | 🐧 | `.bash_profile` 覆盖了 `.profile`,导致 `.bashrc` 没被 source。在 `.bash_profile` 第一行加 `[ -f "$HOME/.bashrc" ] && . "$HOME/.bashrc"` |
| 改完 `.bash_profile` 但 agent 没起来(`echo $SSH_AUTH_SOCK` 还是空) | 🐧 | 登录脚本只在 SSH 登录那一刻读一次,必须 `exit` 重新登入;当前 shell `source ~/.bash_profile` 可验证语法 |
🎯 压缩流程(换电脑速查)
如果你只是回来翻速查表,看这一节就够了。
🪟 Windows
👉 5 步走:
① 管理员 PowerShell:Set-Service ssh-agent -StartupType Automatic + Start-Service ssh-agent
② 从 Bitwarden 恢复 id_ed25519 到 ~/.ssh/
③ ssh-add $env:USERPROFILE\.ssh\id_ed25519(输 passphrase)—— 或在 ~/.ssh/config 写 AddKeysToAgent yes,第一次 ssh host 时自动喂入
④ git config --global core.sshCommand "C:/Windows/System32/OpenSSH/ssh.exe"
⑤ ssh -T git@github.com 或 git push 验证
✅ 之后重启不需要重复任何步骤 —— 密钥已被 DPAPI 持久化到注册表,agent 服务自启时自动加载
🐧 Linux 服务器(per-SSH-session)
👉 5 步走:
① 从 Bitwarden 恢复 id_ed25519 到 ~/.ssh/,chmod 700 ~/.ssh + chmod 600 ~/.ssh/id_ed25519
② start_agent 块写进 ~/.bash_profile(第一行必须 source .bashrc 否则丢 PS1 和 alias)
③ ~/.bashrc 的 non-interactive guard 后插入 [ -f "$HOME/.ssh/agent-env" ] && . "$HOME/.ssh/agent-env" > /dev/null
④ ~/.ssh/config 加 Host * + AddKeysToAgent yes + IdentityFile ~/.ssh/id_ed25519
⑤ exit 重新 SSH 登入 → ssh -T git@github.com 输一次 passphrase 验证
🐧 Linux 桌面
👉 3 步走:
① 从 Bitwarden 恢复 id_ed25519 到 ~/.ssh/(chmod 600)
② ssh-add ~/.ssh/id_ed25519(图形界面输 passphrase,勾选保存到 keychain)
③ ssh -T git@github.com 验证
🧭 最后的心智模型
所有这些操作,回头看其实只在反复贯彻同一件事: 👉 谁发起连接,谁持有私钥;谁被连接,谁存对方的公钥。私钥从生成到销毁,永不离开本机内存或加密存储之外的任何介质。
🎯 跨平台决策规则:私钥永远带 passphrase;passphrase 永远存 Bitwarden;日常用系统 ssh-agent;Windows 多一步 core.sshCommand,Linux 多注意文件权限。
下次换电脑,照这页抄,5 分钟搞定。
