OpenSSH Client Keys
This page covers client-side SSH keys on a macOS workstation (Apple Silicon or Intel) for Git over SSH with GitHub and GitLab. OpenSSH is preinstalled. Optional CLIs: gh (GitHub) and glab (GitLab) for uploading keys. The same commands largely apply on Linux and WSL — see Platform Notes (Linux and WSL).
For everyday Git workflows, see Git Essentials. For commit signing with SSH keys (different from login keys), see Compliance → SSH signing.
Choose Your Path
Section titled “Choose Your Path”Read Private Keys and Key Types first.
| Goal | Sections to follow |
|---|---|
| Create and register a new key | Create → Add to GitHub/GitLab → ssh-agent → Multi-host config → HTTPS → SSH → Test |
| Compare or identify existing keys | What you are comparing → List → Fingerprint → Compare → Worked example → Match to provider → Agent / verbose |
| Multiple accounts (GitHub + GitLab) | Complete Multi-host config before Test Git Over SSH |
Private Keys and Key Types
Section titled “Private Keys and Key Types”Never commit, paste, or share private keys — files in ~/.ssh/ without a .pub suffix. Treat them like passwords.
| Key type | Scope | This page |
|---|---|---|
| Account SSH key | Your user on GitHub or GitLab; clone and push to repos you can access | Covered below |
| Deploy key | Single repository (read-only or read-write) | Out of scope — configure per repo in provider settings |
Create a New Key Pair
Section titled “Create a New Key Pair”Prefer ED25519 (small, modern):
Use a passphrase on laptops. You will enter it once per session (or store in Keychain via ssh-add below).
Resulting files:
| File | Role |
|---|---|
~/.ssh/id_ed25519_git | Private key — never share |
~/.ssh/id_ed25519_git.pub | Public key — upload to GitHub/GitLab |
Permissions (if ssh-keygen did not set them):
chmod 700 ~/.sshchmod 600 ~/.ssh/id_ed25519_gitchmod 644 ~/.ssh/id_ed25519_git.pubFile already exists: ssh-keygen prompts to overwrite. Prefer a new -f name (for example id_ed25519_gitlab_work) instead of clobbering a key still in use.
RSA (legacy hosts only): if a system requires RSA, ssh-keygen -t rsa -b 4096 -C "[email protected]" -f ~/.ssh/id_rsa_git. Default to ED25519 for GitHub and GitLab.
Add the Public Key to GitHub and GitLab
Section titled “Add the Public Key to GitHub and GitLab”Copy the public key on macOS:
pbcopy < ~/.ssh/id_ed25519_git.pubAdd the key to each provider you use. The same .pub file may be registered on both GitHub and GitLab.
After upload, confirm the provider UI fingerprint matches your local key.
| Provider | Web UI | CLI (optional) |
|---|---|---|
| GitHub | Settings → SSH and GPG keys → New SSH key | gh ssh-key add ~/.ssh/id_ed25519_git.pub --title "MacBook" |
| GitLab (GitLab.com) | Avatar → Edit profile → Access → SSH keys → Add new key | glab ssh-key add ~/.ssh/id_ed25519_git.pub -t "MacBook" |
Self-managed GitLab: use your instance hostname in HostName (see Multiple Git Hosts) and test with ssh -T [email protected].
Verify locally:
ssh-keygen -l -f ~/.ssh/id_ed25519_git.pubCompare the SHA256 fingerprint to the value shown in the provider UI.
Load the Key in ssh-agent (macOS)
Section titled “Load the Key in ssh-agent (macOS)”ssh-add --apple-use-keychain ~/.ssh/id_ed25519_gitOn older macOS releases, ssh-add -K was the equivalent flag for Keychain.
Optional ~/.ssh/config stanza so keys load into the agent and Keychain on use — without pinning one global key:
Host * AddKeysToAgent yes UseKeychain yesDo not set a global IdentityFile under Host * when you use separate keys for GitHub, GitLab, personal, or work accounts.
Confirm the key is loaded:
ssh-add -lMultiple Git Hosts with ~/.ssh/config
Section titled “Multiple Git Hosts with ~/.ssh/config”Configure before ssh -T when you use host aliases. Map each alias to one private key with IdentitiesOnly yes so SSH does not offer every loaded key (avoids Too many authentication failures).
Example for GitHub and GitLab.com:
Host github-personal HostName github.com User git IdentityFile ~/.ssh/id_ed25519_github_personal IdentitiesOnly yes
Host gitlab-work HostName gitlab.com User git IdentityFile ~/.ssh/id_ed25519_gitlab_work IdentitiesOnly yesClone and push URLs:
git clone git@github-personal:org/repo.gitgit clone git@gitlab-work:group/repo.gitThe Host name is arbitrary; it must match the host segment in the Git URL.
Switch an Existing Repo from HTTPS to SSH
Section titled “Switch an Existing Repo from HTTPS to SSH”git remote -vGitHub (default host or alias):
# orgit remote set-url origin git@github-personal:org/repo.gitGitLab (default host or alias):
# orgit remote set-url origin git@gitlab-work:group/repo.gitSee Git Essentials for clone and branch workflows.
Test Git Over SSH
Section titled “Test Git Over SSH”Default hostnames:
With ~/.ssh/config aliases:
ssh -T git@github-personalssh -T git@gitlab-workSuccess messages differ by provider (for example GitHub’s “Hi username!” or GitLab’s welcome text). A first connect may prompt to trust the host key — expected.
If you see Permission denied (publickey), see Troubleshooting.
What You Are Comparing
Section titled “What You Are Comparing”You are matching:
- Local:
~/.ssh/*.pubfiles on disk - Remote: keys registered on your GitHub or GitLab account
Fingerprints must match; the comment at the end of a .pub line is only a label.
List Keys on Disk
Section titled “List Keys on Disk”ls -la ~/.ssh| Suffix | Meaning |
|---|---|
.pub | Public key — safe to upload |
| (none) | Private key — never share |
.pub absent on paired file | Private half of a key pair |
Fingerprint and Key Type
Section titled “Fingerprint and Key Type”ssh-keygen -l -f ~/.ssh/id_ed25519_git.pubLegacy UIs may show MD5 fingerprints:
ssh-keygen -l -E md5 -f ~/.ssh/id_ed25519_git.pub| Field | Example meaning |
|---|---|
| Algorithm | ED25519, RSA, ECDSA |
| Bit length | 256, 3072, … |
| Fingerprint | SHA256:… — use to match provider UI |
| Comment | Email or machine name — not cryptographically binding |
Compare Two Local Keys
Section titled “Compare Two Local Keys”ssh-keygen -l -f ~/.ssh/id_ed25519_github_personal.pubssh-keygen -l -f ~/.ssh/id_ed25519_gitlab_work.pubOr diff fingerprint lines:
diff <(ssh-keygen -l -f ~/.ssh/key_a.pub) <(ssh-keygen -l -f ~/.ssh/key_b.pub)Different SHA256 fingerprints mean different key pairs — not duplicates, even if comments look similar.
Worked Example: Two Keys on One Account
Section titled “Worked Example: Two Keys on One Account”Scenario: You have id_ed25519_old.pub and id_ed25519_new.pub on disk. GitHub lists two SSH keys titled “MacBook” and “MacBook 2026”.
- Fingerprint each local file:
ssh-keygen -l -f ~/.ssh/id_ed25519_old.pubssh-keygen -l -f ~/.ssh/id_ed25519_new.pub- Open GitHub → Settings → SSH and GPG keys (or GitLab → Edit profile → Access → SSH keys).
- Match each UI entry’s fingerprint to one local output.
- Note algorithm and created date in the UI (or
staton macOS):
stat -f "%Sm %N" -t "%Y-%m-%d" ~/.ssh/id_ed25519_old.pub ~/.ssh/id_ed25519_new.pubYou now know which file corresponds to which registered key — without re-uploading.
Match a Local .pub to a Registered Key
Section titled “Match a Local .pub to a Registered Key”Use this when keys already exist on the provider and you need to identify one file on disk. Distinct from adding a new key.
Provider UI:
| Provider | Where to read fingerprints |
|---|---|
| GitHub | Settings → SSH and GPG keys |
| GitLab | Edit profile → Access → SSH keys |
From the terminal:
ssh-keygen -lf ~/.ssh/example.pubOptional listing (authenticated CLI):
gh api user/keys --jq '.[] | "\(.title) \(.key)"'
glab ssh-key listInspect Keys in ssh-agent and Verbose Offers
Section titled “Inspect Keys in ssh-agent and Verbose Offers”Loaded keys:
ssh-add -lssh-add -LWhich key SSH offers (GitHub):
GitLab:
Use the same grep with your Host alias when using ~/.ssh/config.
Replace or Remove an Old Key
Section titled “Replace or Remove an Old Key”- Confirm nothing still uses the old key (remotes, CI, other machines).
- Delete the key in the provider UI (GitHub or GitLab SSH keys page).
- Optionally remove the local pair:
rm ~/.ssh/id_ed25519_old ~/.ssh/id_ed25519_old.pub
This is not a full key-rotation runbook — only cleanup when you know the key is retired.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Things to check |
|---|---|
Permission denied (publickey) | Public key on the account? Correct IdentityFile in ~/.ssh/config? ssh-add -l shows the key? |
| Wrong key offered | IdentitiesOnly yes; correct Host alias; verbose test |
Too many authentication failures | Too many keys in agent; use per-host IdentityFile + IdentitiesOnly yes |
| Fingerprint mismatch | Re-copy .pub; wrong file pasted in UI |
| HTTPS remote still used | Switch remote to SSH — git remote -v should show git@ |
Authentication vs Commit Signing
Section titled “Authentication vs Commit Signing”| Purpose | Config | Docs |
|---|---|---|
| Authentication | Push and clone over SSH ([email protected], [email protected]) | This page |
| Commit signing | git config --global gpg.format ssh and user.signingkey pointing at a .pub | Compliance |
The same key file can sometimes be used for both on GitLab (glab ssh-key add --usage-type auth); signing policy is separate from “can I push?”
Platform Notes (Linux and WSL)
Section titled “Platform Notes (Linux and WSL)”| Task | macOS | Linux / WSL |
|---|---|---|
| Copy public key | pbcopy < file.pub | xclip -selection clipboard < file.pub or wl-copy < file.pub |
| File dates | stat -f "%Sm" -t "%Y-%m-%d" file | stat -c "%y" file |
| ssh-agent | Keychain via ssh-add --apple-use-keychain | eval "$(ssh-agent -s)" or user systemd unit |
| Config path | ~/.ssh/config | Same under Linux home in WSL |
WSL stores keys on the Linux side (~/.ssh inside the distro), not the Windows profile, unless you deliberately share them.
Summary
Section titled “Summary”Path A — new key: ssh-keygen → add public key to GitHub and/or GitLab → ssh-add --apple-use-keychain → per-host ~/.ssh/config → ssh -T → optionally switch remote to SSH.
Path B — existing keys: fingerprint local .pub files → match provider UI → set Host alias and IdentityFile → verbose ssh -vT if the wrong key is offered.
For server-side SSH (authorized_keys, sshd), see Linux and security material elsewhere in the library — not covered here.