👉 这是我自己每次换电脑都要翻一遍的私人备忘录,索性公开成博客。 目标是:从一台空白机器开始,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 宁可让你登不上去,也不愿用一个全世界都能读的私钥。这个「过度保守」的设计其实是好事。

    - (可选)从私钥反推公钥:

    🪟 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/id_ed25519

    • 必须设强 passphrase

      没 passphrase 的私钥 = 一个明文凭证。一旦本机被入侵或备份泄露,攻击者直接拿到你的 GitHub 身份。passphrase 给你一个「加密层」缓冲。

    • 生成完别忘了把新公钥贴到 GitHub(步骤 ⑤)和新建 Bitwarden 条目(最后的模板章节)


    ② 启动 ssh-agent(一次性配置)

    讲讲 ssh-agent 在这里扮演什么角色。 👉 ssh-agent 是一个常驻进程,负责「在内存里解密私钥并代为签名」。 它存在的意义:

    • 你输一次 passphrase,agent 把解密后的密钥握在内存里
    • 之后所有需要这个密钥的命令(git pushssh user@host),都让 agent 在背后帮忙签名
    • 你不用每次都输一遍 passphrase

    本质是「用进程隔离,换便利性」——比把密钥裸放着安全,比每次手敲 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 yesssh 解密成功后自动喂入)的密钥,会被 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
    • 有输出(一个 socket 路径) = 桌面,直接跳到步骤 ③
    • 空 = 服务器,往下看

    🟢 桌面 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(llla 等) 🔹 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.comgit 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 &lt;user&gt;!
    ssh -T git@github.com   # 第二次:直接 Hi &lt;user&gt;! 不再问

    ⚖️ Linux 三种方案对比

    方案适用场景优点缺点
    桌面 keychainGNOME/KDE 桌面零配置、图形化解锁、开机持久仅桌面环境
    **per-SSH-session(推荐)****服务器、容器、跳板机**简单、信任边界清晰、无 systemd 依赖每次 SSH 登录输一次 passphrase
    systemctl --user 常驻极少数:每天反复 SSH 进出几十次、且不用 tmux跨会话不重复输密码需手写 unit、`enable-linger`、密钥长期驻留内存
    🎯 **我的选择规则**:桌面用 keychain,服务器用 per-SSH-session,systemctl 路线只在极端高频场景才考虑。

    ③ 把私钥加进 agent

    🪟 Windows

    ssh-add $env:USERPROFILE\.ssh\id_ed25519

    🐧 Linux

    ssh-add ~/.ssh/id_ed25519
    • 输入 passphrase(从 Bitwarden 复制)
    • 验证(两平台一致):
    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 &lt;你的GitHub用户名&gt;! 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
    设备: &lt;Windows 笔记本 / Linux 工作站 / ...&gt;
    邮箱: &lt;你的邮箱&gt;
    算法: ed25519
    公钥指纹: &lt;ssh-keygen -lf id_ed25519.pub 的输出&gt;
    关联服务: GitHub (&lt;你的 GitHub 邮箱&gt;)
    
    恢复步骤(Windows):
    1. 复制 passphrase 字段,先放剪贴板
    2. 复制私钥到 C:\Users\&lt;你&gt;\.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 验证

    ❗ 安全要点(跨平台一致)

    • ✅ 私钥必须带 passphrase(即 Bitwarden 密码字段非空)—— 否则 vault 一旦泄露,密钥裸奔
    • ✅ passphrase 和私钥存在同一条目(恢复时一一对应,不会错配)
    • ❌ 不要把未加密的明文私钥直接存进任何地方
    • ✅ 每生成一把新密钥 → 新建条目,不要覆盖旧的(保留历史可追溯)
    • ✅ Bitwarden 主密码开 2FA
    • 🐧 Linux 特别注意:~/.ssh 权限 700、私钥 600,否则 SSH 拒绝使用

    🔄 日常使用

    🪟 Windows

    通常零操作。Windows ssh-agent 服务会把已加载的密钥用 DPAPI 加密后写入注册表 HKCU\Software\OpenSSH\Agent\Keys,开机自启后自动解密回 agent 内存——passphrase 已被「Windows 登录态」托管。 只有以下情况需要手动 ssh-add 一次:

    • 🔹 首次配置、agent 里还没任何密钥(也可以靠 AddKeysToAgent yes 在第一次 ssh host 时自动喂入)
    • 🔹 主动 ssh-add -D 清空过 agent
    • 🔹 把 ssh-agent 服务设为 Manual / Disabled 后
    ssh-add $env:USERPROFILE\.ssh\id_ed25519

    安全含义:「能以你的 Windows 用户身份执行代码 = 能用你的私钥签名」。passphrase 在此模式下不再被询问,它实际上等价于你的 Windows 登录态。便利性 ↔ 本机被入侵时的爆炸半径,自行权衡。

    🐧 Linux

    • 桌面环境(GNOME / KDE)+ keychain:✅ 完全零操作,开机自动解锁
    • 服务器(按 per-SSH-session 配置完成):✅ 每次新 SSH 登录时,第一次用到密钥(ssh -Tgit push)会被问一次 passphrase,之后整个 SSH 会话免输;登出自动清理
    • 未配 AddKeysToAgent yes:每次 SSH 登录后手动 ssh-add ~/.ssh/id_ed25519

    🧭 排错速查

    现象平台检查 / 修复
    `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/configAddKeysToAgent yes,第一次 ssh host 时自动喂入 ④ git config --global core.sshCommand "C:/Windows/System32/OpenSSH/ssh.exe"ssh -T git@github.comgit push 验证 ✅ 之后重启不需要重复任何步骤 —— 密钥已被 DPAPI 持久化到注册表,agent 服务自启时自动加载

    🐧 Linux 服务器(per-SSH-session)

    👉 5 步走: ① 从 Bitwarden 恢复 id_ed25519~/.ssh/chmod 700 ~/.ssh + chmod 600 ~/.ssh/id_ed25519start_agent 块写进 ~/.bash_profile第一行必须 source .bashrc 否则丢 PS1 和 alias) ③ ~/.bashrc 的 non-interactive guard 后插入 [ -f "$HOME/.ssh/agent-env" ] && . "$HOME/.ssh/agent-env" > /dev/null~/.ssh/configHost * + AddKeysToAgent yes + IdentityFile ~/.ssh/id_ed25519exit 重新 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 验证


    🧭 最后的心智模型

    所有这些操作,回头看其实只在反复贯彻同一件事: 👉 谁发起连接,谁持有私钥;谁被连接,谁存对方的公钥。私钥从生成到销毁,永不离开本机内存或加密存储之外的任何介质。

    • 🔹 chmod 600 是在保护文件层
    • 🔹 passphrase 是在保护存储层
    • 🔹 ssh-agent 是在控制内存层的解密窗口
    • 🔹 core.sshCommand 是在保证调用链没走偏
    • 🔹 Bitwarden 是在解决跨设备恢复

    🎯 跨平台决策规则:私钥永远带 passphrase;passphrase 永远存 Bitwarden;日常用系统 ssh-agent;Windows 多一步 core.sshCommand,Linux 多注意文件权限。 下次换电脑,照这页抄,5 分钟搞定。