Keeping SSH signing keys in an OS keychain agent Jump to heading
The weakest part of most SSH signing setups is not the algorithm; it is the file. A private key in ~/.ssh without a passphrase can be copied by any process running as you, from a malicious packageβs install script to a misconfigured backup job. People drop the passphrase because typing it on every commit is unbearable. A keychain-backed agent removes that trade-off: the key stays encrypted at rest, the passphrase is unlocked once with your login, and Git signs through the agent without ever reading the key file. This page sets that up on macOS, Linux and with password-manager agents, as part of protecting and rotating signing keys.
When to use this approach Jump to heading
- Your SSH signing key currently has no passphrase, or you type it many times a day.
- You want the key encrypted on disk but signing to be prompt-free during a work session.
- You use a password manager that can act as an SSH agent, and would like the key to live there instead of on disk.
- Hardware keys are not practical for your team yet; if they are, see signing Git commits with a YubiKey, which is stronger still.
Step 1 β Give the key a passphrase if it has none Jump to heading
An agent protects a key in memory during a session. The passphrase protects it on disk the rest of the time. Without the passphrase, the agent adds convenience but no security.
# Add or change the passphrase on an existing key, in place
ssh-keygen -p -f ~/.ssh/id_ed25519_signing
# Confirm it is now encrypted: this should prompt rather than print the public key
ssh-keygen -y -f ~/.ssh/id_ed25519_signing >/dev/null Step 2 β macOS: store the passphrase in the login Keychain Jump to heading
The OpenSSH bundled with macOS can store key passphrases in the login Keychain and load keys automatically. Two ssh_config options switch it on.
# ~/.ssh/config
Host *
UseKeychain yes
AddKeysToAgent yes # Add the key once, storing its passphrase in the Keychain
ssh-add --apple-use-keychain ~/.ssh/id_ed25519_signing
ssh-add -l | grep signing After a reboot, the first SSH operation loads the key from the Keychain without a prompt. Git signing through ssh-keygen -Y sign uses the same agent, so commits sign silently.
# Verification: after a fresh login, a commit signs without a prompt
git commit --allow-empty -m "keychain test" && git log -1 --format='%G?' Step 3 β Linux: use the desktop secret service Jump to heading
GNOME Keyring and KDE Wallet both provide an SSH agent that unlocks with your login password. On GNOME the agent is part of gcr; enable its socket and point SSH_AUTH_SOCK at it.
systemctl --user enable --now gcr-ssh-agent.socket
# In your shell profile
export SSH_AUTH_SOCK="$XDG_RUNTIME_DIR/gcr/ssh"
ssh-add ~/.ssh/id_ed25519_signing # first time: offers to remember the passphrase On a headless machine with no secret service, use plain ssh-agent with a lifetime, so the decrypted key does not sit in memory indefinitely.
eval "$(ssh-agent -s)"
ssh-add -t 8h ~/.ssh/id_ed25519_signing # key drops out of the agent after 8 hours Step 4 β Or let a password manager be the agent Jump to heading
Several password managers can act as SSH agents. The private key lives in the vault, not on disk; the manager exposes an agent socket and asks for approval when a program wants to sign. For Git, point SSH_AUTH_SOCK at the managerβs socket and set user.signingKey to the public key, since there is no private key file.
# ~/.ssh/config β route all agent requests to the manager's socket
Host *
IdentityAgent ~/.password-manager/agent.sock # Git needs only the public key text; the agent finds the private half
git config --global user.signingKey "key::ssh-ed25519 AAAAC3Nz... git-signing"
git config --global gpg.format ssh The key:: prefix tells Git the value is a literal public key rather than a path.
# Verification: no private key file is needed for signing
ls ~/.ssh/id_ed25519_signing 2>/dev/null || echo "no key file on disk"
git commit --allow-empty -m "vault signing test" && git log -1 --format='%G?' Step 5 β Lock the agent when you step away Jump to heading
An unlocked agent will sign for any process running as you. Locking it, or letting keys expire from it, limits the window in which malware already running on the machine can use the key.
ssh-add -x # lock the agent with a temporary password
ssh-add -X # unlock
ssh-add -D # or remove all keys from the agent Most keychain agents lock automatically with the screen. Check that yours does by locking the screen and trying to sign over SSH from another session.
Validation checklist Jump to heading
Frequently Asked Questions Jump to heading
Does the agent protect against malware already running as me? Jump to heading
Partly. Malware cannot copy a usable key, but while the agent is unlocked it can ask the agent to sign arbitrary data. Approval prompts in password-manager agents and touch requirements on hardware keys are what close that gap.
Should the signing key and the authentication key live in the same agent? Jump to heading
They can. The agent simply holds keys; which one Git uses is decided by user.signingKey. Keeping them in one agent is convenient, and the separation that matters β distinct keys with distinct registrations β is preserved.
What happens in CI, where there is no keychain? Jump to heading
CI uses a different model entirely: short-lived keys injected as secrets or keyless signing. See protecting CI signing keys with environment secrets.
Related Jump to heading
- Protecting & Rotating Signing Keys β the parent topic.
- Using a Separate Signing Key per Device β one key per agent, so a lost laptop is one revocation.
- Setting Up SSH Commit Signing from Scratch β creating the key this page protects.
- Backing Up Signing Keys Without Creating a Leak β whether you should back up a signing key at all.