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
What protects the key, and whenA key without a passphrase is readable by any process running as you, at all times. A passphrase-protected key held in a keychain agent is encrypted on disk and only usable through the agent while you are logged in, which closes the copy-the-file attack.Bare key fileEncrypted key + keychain agentat rest on diskplaintextencryptedcopied by malwareusable anywhereuseless without passphraseprompts per commitnonenone after loginafter logoutstill readablelockedthe keychain gives you the convenience people dropped the passphrase for

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.

Signing through a password-manager agentGit calls ssh-keygen with the public key. ssh-keygen asks the agent socket to sign. The password manager asks the user to approve, signs inside the vault, and returns the signature, so the private key never touches the filesystem.gitssh-keygenagent socketvault-Y sign with key:: pubkeysign requestapprove?signed in vaultsignatureembedded in commitno private key file exists anywhere on the machine
# 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.

Layers between malware and your signing keyA passphrase stops the key file being useful if copied. A keychain agent keeps the decrypted key out of reach of ordinary file reads. Locking on idle shrinks the time an attacker already on the machine could ask the agent to sign.each layer narrows the attackPassphrase on the keya copied file is uselessKeychain-backed agentno prompt, no bare keyLock on idle or screen lockagent stops signingHardware keyapproval needs a toucha hardware key is the only layer that stops a process already running as you

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.