Setup SSH Key on Remote Machine¶
Terminology rule (zh-TW pages)
技術名詞首次出現以「中文 (English original)」格式呈現,例:依賴注入
(dependency injection)。不自創翻譯——若無公認譯名直接保留英文
(如 embedding、tokenizer)。代碼、API 名、CLI flag、套件名、檔名一律不翻。
將本機 SSH 金鑰 (key) 複製到遠端主機 (remote machine)(例如 Raspberry Pi),同時用於 SSH 登入與 GitHub 存取。
先決條件 (Prerequisites)¶
- 可以 SSH 連線到遠端主機(一開始使用密碼驗證)
- 已將公鑰 (public key) 加到你的 GitHub 帳號:https://github.com/settings/keys
0. 建立 SSH 金鑰¶
選擇演算法 (algorithm)¶
| 演算法 | 命令 | 備註 |
|---|---|---|
| Ed25519 | ssh-keygen -t ed25519 |
推薦。快速、安全、金鑰短小。任何環境都能用(OpenSSH >= 6.5)。 |
| Ed25519-SK | ssh-keygen -t ed25519-sk |
綁定硬體(FIDO2 / YubiKey)。簽署時需要實體觸碰。 |
| ECDSA-SK | ssh-keygen -t ecdsa-sk |
給不支援 Ed25519-SK 的舊款 YubiKey 的 FIDO2 備援方案。 |
| RSA (4096) | ssh-keygen -t rsa -b 4096 |
僅供舊系統相容。連線到非常老舊的系統時才使用。 |
| ECDSA | ssh-keygen -t ecdsa -b 521 |
較少見;相較 Ed25519 沒有優勢。 |
TL;DR:除非你需要硬體金鑰(ed25519-sk)或舊系統相容(rsa),不然就用 ed25519。
產生金鑰¶
# 基本(會互動式詢問路徑與 passphrase)
ssh-keygen -t ed25519 -C "your_email@example.com"
# 明確指定檔案路徑與註解
ssh-keygen -t ed25519 -f ~/.ssh/<key> -C "description or email"
# 搭配 YubiKey(需要先把 key 插上)
ssh-keygen -t ed25519-sk -f ~/.ssh/id_ed25519_sk -C "yubikey"
要不要設 passphrase?¶
| 情境 | 設 passphrase? | 為什麼 |
|---|---|---|
| 個人電腦,已啟用全磁碟加密 | 可選 | 磁碟加密已經保護了檔案 |
| 共用 / 多使用者主機 | 設 | 防止其他使用者或程式 (process) 使用你的金鑰 |
| CI/CD、自動化、無人值守 server | 不設 | 沒有人類可以輸入;改用檔案權限 + 密碼管理工具 (secrets manager) |
| 高安全 / 合規場景 | 設 | 縱深防禦;搭配 SSH agent 只需輸入一次 |
提示:日後可以在不重新產生金鑰的情況下變更或移除 passphrase:
建立金鑰之後的管理方式¶
純檔案(最簡單)¶
金鑰以檔案形式放在 ~/.ssh/。你在 ~/.ssh/config 中明確指定它們:
優點:簡單、可攜。缺點:金鑰檔案存在磁碟上、必須複製到每台機器。
SSH agent¶
把金鑰載入 agent,每個會話 (session) 只需輸入一次 passphrase:
# 啟動 agent(此 repo 會自動處理,見 docs/tools/ssh-agent.md)
eval "$(ssh-agent -s)"
# 加入金鑰(若有設 passphrase 會提示輸入)
ssh-add ~/.ssh/<key>
# macOS:把 passphrase 存進 Keychain(重開機後仍有效)
ssh-add --apple-use-keychain ~/.ssh/<key>
Bitwarden SSH Agent¶
把金鑰存放在 Bitwarden 保險庫 (vault) 內;桌面應用程式 (desktop app) 擔任 SSH agent。初次匯入後,磁碟上不再有金鑰檔案。
完整設定見 Bitwarden SSH Agent tutorial。
1Password / 其他密碼管理工具¶
概念與 Bitwarden 類似 -- 應用程式提供 SSH agent socket。詳見你使用之密碼管理工具的官方文件。
YubiKey (FIDO2)¶
私鑰永遠不會離開硬體。-sk 系列金鑰類型(ed25519-sk、ecdsa-sk)會在 ~/.ssh/ 產生 key handle,但實際的簽署運算在 YubiKey 上完成。每次 SSH 操作都需要把 YubiKey 插上。
比較¶
| 方法 | 金鑰是否在磁碟? | 是否可跨機器攜帶? | 是否每個 session 需輸入 passphrase? |
|---|---|---|---|
| 純檔案 | 是 | 手動複製 | 每次都要(或不設 passphrase) |
| SSH agent | 是(已載入記憶體) | 複製金鑰,agent 自動載入 | 每個 session 一次 |
| Bitwarden / 1Password | 否(匯入後就無) | 透過 vault 同步 | 解鎖 vault 一次 |
| YubiKey | 僅有 handle | 攜帶實體金鑰 | 每次操作觸碰一次 |
IdentitiesOnly yes¶
預設情況下,SSH client 會把 agent 內的所有金鑰逐一提供給伺服器 (server),全部試完才會嘗試 IdentityFile。下列情境會出問題:
- agent 內金鑰太多 -- 多數伺服器只允許 5-6 次驗證嘗試。如果你的 agent 持有 8 把以上的金鑰,伺服器會在試到正確金鑰之前就以
Too many authentication failures拒絕你。 - 錯誤金鑰用在錯誤主機 -- 沒有限制時,SSH 可能用非預期的金鑰登入伺服器(例如把私人金鑰用在公司伺服器)。
- 安全 / 稽核 -- 你想保證每台主機使用哪一把金鑰是固定的。
在 SSH 設定中加入 IdentitiesOnly yes,可讓 SSH 只使用 IdentityFile 指定的金鑰,忽略 agent 中的其他金鑰:
# 個別主機
Host github.com
IdentityFile ~/.ssh/<key>
IdentitiesOnly yes
# 或全域套用(金鑰多時建議)
Host *
IdentitiesOnly yes
注意:使用
IdentitiesOnly yes時,每個 host 條目都必須設IdentityFile-- SSH 不會回退去使用 agent 中的金鑰。如果你使用密碼管理工具的 agent(Bitwarden/1Password),通常不會想全域套用,因為 agent 才是金鑰的唯一來源。
| 情境 | IdentitiesOnly |
為什麼 |
|---|---|---|
| 金鑰少、只有單一 agent | 否(預設) | agent 全部試一遍也沒問題 |
| agent 中金鑰很多(5+) | 是 | 防止「too many failures」 |
| 多個 GitHub/GitLab 帳號 | 是 | 強制每台主機使用正確金鑰 |
| 僅用 Bitwarden/1Password agent | 否 | agent 就是身份來源 |
| 檔案金鑰與 agent 金鑰混用 | 各主機分別設 是 | 明確控制 |
1. 啟用 SSH 登入到遠端主機¶
一般金鑰檔案¶
這會把公鑰附加到遠端的 ~/.ssh/authorized_keys。
YubiKey / 硬體安全金鑰¶
如果你的金鑰存放在 YubiKey 上(FIDO2 或 PIV),磁碟上沒有私鑰檔案 -- 只剩 .pub 檔或 agent 知道它。
# 如果你有 .pub 檔(ed25519-sk / ecdsa-sk)
ssh-copy-id -i ~/.ssh/id_ed25519_sk.pub user@remote
# 如果金鑰只存在於 agent(YubiKey PIV、gpg-agent 等)
# ssh-copy-id 會自動使用 agent 中的金鑰
ssh-copy-id user@remote
SSH agent(Bitwarden、1Password、gpg-agent 等)¶
當你的金鑰由 SSH agent 管理,且磁碟上沒有 .pub 檔案時:
# 列出 agent 目前持有的金鑰
ssh-add -L
# ssh-copy-id 會自動取用 agent 的金鑰
ssh-copy-id user@remote
# 或從 agent 將特定金鑰透過管線送出
ssh-add -L | grep "<key comment or type>" | ssh user@remote 'mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys'
注意:請確認
SSH_AUTH_SOCK指向正確的 agent。 此 repo 如何管理SSH_AUTH_SOCK詳見 SSH Agent Fallback。
接著在本機 ~/.ssh/config 加入:
Host rpi
HostName <IP or hostname>
User <user>
IdentityFile ~/.ssh/<key>
IdentitiesOnly yes # 可選:只使用此金鑰,忽略 agent
驗證:ssh rpi 應該能不需密碼直接登入。
2. 將金鑰對複製到遠端¶
scp ~/.ssh/<key> ~/.ssh/<key>.pub rpi:~/.ssh/
ssh rpi 'chmod 600 ~/.ssh/<key> && chmod 644 ~/.ssh/<key>.pub'
3. 在遠端設定 GitHub SSH¶
ssh rpi 'cat >> ~/.ssh/config << EOF
Host github.com
IdentityFile ~/.ssh/<key>
EOF'
ssh rpi 'chmod 600 ~/.ssh/config'
4. 將 Git remote 從 HTTPS 切換為 SSH¶
通用的轉換規則:https://github.com/<user>/<repo>.git → git@github.com:<user>/<repo>.git
5. 驗證¶
# 測試 GitHub SSH 驗證
ssh rpi 'ssh -T git@github.com'
# 預期: "Hi <user>! You've successfully authenticated..."
# 測試 git push
ssh rpi 'cd /path/to/repo && git push'
自動化:ssh-setup-remote¶
此 repo 提供一個 shell function,會以互動式流程帶你走過上述所有步驟:
它會提示你:
- 選擇或建立 SSH 金鑰(演算法、名稱、passphrase)
- ssh-copy-id 公鑰以啟用免密碼登入
- 複製金鑰對到遠端(可選,用於 GitHub 存取)
- 在遠端加入 GitHub SSH 設定(可選)
- 把金鑰接進本機
~/.ssh/config—— 精靈會分辨兩種情況: - 若該別名已經設定過(會遞迴沿著
Include ~/.ssh/config.d/*找到實際所在的檔案),就 直接在既有的Host區塊內就地加入IdentityFile,而非附加重複區塊。若區塊已有IdentityFile,會詢問要取代 / 再加一條 / 略過;用同一把金鑰重跑則為無動作。 - 若是新主機(例如
user@ip),則附加一個全新的別名區塊(寫到~/.ssh/config或~/.ssh/config.d/drop-in),並可選擇加上IdentitiesOnly yes。當寫入 drop-in 但你的~/.ssh/config沒有對應的Include行時,會主動詢問是否補上,讓該設定真正生效。
就地編輯需要
python3;若環境中沒有python3,精靈會退回到僅附加(append-only)的行為。
來源:~/.config/shell/96_ssh_setup.sh。搭配的 dotfiles-windows repo 也提供指令名稱與流程完全相同的
PowerShell 版本。
ProxyJump (跳板) 鏈¶
ssh(1) 會透明地穿過 ProxyJump 主機建立連線,所以如果只對最終目標設定金鑰,跳板 (jump host) 每次連線
都還是會要求密碼——因為跳板從來沒拿到過金鑰。ssh-setup-remote 會先解析目標完整的跳板鏈(遞迴解析,
因為跳板本身也可能有自己的 ProxyJump),然後對每一跳都由外而內重跑一次整套流程:
Host zr-windows
HostName 61.169.38.122
User zhurun
Host zr
HostName 10.168.11.54
User hongxing
ProxyJump zr-windows
$ ssh-setup-remote zr
=== SSH Key Setup for zr ===
ProxyJump chain detected: zr-windows -> zr
Each hop needs its own key on its own authorized_keys — ProxyJump only
forwards TCP, so the jump host never sees your private key.
========================================
[1/2] zr-windows (jump)
========================================
...
========================================
[2/2] zr (target)
========================================
...
跳板只會執行第 1、3 步(安裝金鑰、接進本機設定)——ProxyJump 的驗證是從你的機器對最終目標端對端完成 的,所以跳板本身完全不需要私鑰;第 2 步(複製金鑰對)只會在最終目標執行。已經免密碼的那一跳會被偵測到, 並提供跳過的選項。
私鑰旁邊缺 .pub¶
如果金鑰的公鑰檔不見了(例如從別台機器只複製了一半,或是只從 agent 匯入過),以前會悄悄通過存在性檢查,
直到 ssh-copy-id 內部才失敗,噴出令人困惑的
failed to open ID file '…/key.pub': No such file or directory。精靈現在會在選好金鑰後立刻發現這個情況
並主動提議修復:
公鑰永遠可以從私鑰推導回來,所以這個修復是安全、不會遺失資料的;你也可以自己先手動處理:
ssh-keygen -y -f ~/.ssh/some_key > ~/.ssh/some_key.pub。
疑難排解 (Troubleshooting)¶
精靈顯示「Could not identify the remote OS」然後失敗¶
這幾乎都是連線層的錯誤被誤報成遠端作業系統問題。在 macOS 上最常見的原因是
ControlPath 超過 sockaddr_un 的 104 位元組上限,ssh 會在開 socket 之前就以
255 結束:
現在的 ssh-setup-remote 會限制 socket 路徑長度,並且直接印出 ssh 真正的 stderr
而不是吞掉,所以上面這行會直接顯示出來。要完全排除多工的影響:
完整來龍去脈見 pitfalls/ssh-controlpath-too-long-macos-tmpdir.md。
Remote Host Identification Has Changed¶
連線時若看到 WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!,代表伺服器的 host key 已和 ~/.ssh/known_hosts 內存的紀錄不符。常見於重新安裝作業系統,或同一個 IP 重新佈建 (reprovision) server 之後。
移除舊金鑰後重新連線:
錯誤訊息會告訴你是哪一行(例如 Offending ECDSA key in ~/.ssh/known_hosts:89),但 ssh-keygen -R 會根據 hostname/IP 自動處理移除。
群組 SSH 設定與 dev 整合¶
新機採 create-only 的 SSH 入口,明列 config.d/git/github.conf、
config.d/git/gitlab.conf 與 config.d/common/defaults.conf,選項縮排四格。
優先順序由 Include 清單決定。種子不依賴 dev 已安裝;既有 flat/wildcard
設定保留舊種子,不會自動遷移。新群組種子依入口標記 gating,避免更新時
意外啟用新定義。搬移種子且移除原本精確 Include 後,apply 不會重建舊檔。
使用包含 SSH 管理功能的 dev build 時,可執行 dev ssh format 預覽縮排,
或明確選擇 dev ssh organize 搬移完整 Host 區塊及註解到群組。檢查計畫後
加 --apply 才套用,dev ssh restore <receipt> 預覽復原;新的 dev 寫入
使用 macOS/Linux 安全 backend。dev ssh manage 另外選擇 fleet/Herdr
註冊及原生 profile 動作;安裝 dotfiles 本身不註冊主機或啟動遠端 server。
Television SSH channel 優先使用 dev ssh list --format tsv 的 active aliases;
dev 不可用或版本尚無 machine-management command 時,退回 flat/群組檔案的顯示型掃描。共用 SSH setup helper 追蹤
巢狀 Include,為新選定檔案加入精確 Include,不會順便啟用相鄰的休眠檔案。
原有以目錄為單位的 helper 子命令仍保留供既有呼叫者使用。