Setting up SSH commit signing from scratch Jump to heading
SSH signing arrived in Git 2.34 and removed most of the friction that kept teams away from signed commits. There is no keyring to manage, no web of trust to understand, and most engineers already have ssh-keygen and an agent running. What remains are a handful of settings that are easy to get subtly wrong: signing with the authentication key you use for everything else, forgetting the allowed signers file so local verification always reports an error, or signing commits but not tags. This page walks through a clean setup in the order that avoids those traps. The choice between this and GPG is discussed in the parent topic, GPG vs SSH commit signing.
When to use this approach Jump to heading
- You are setting up signing for the first time, on a machine with Git 2.34 or newer.
- Your forge accepts SSH signing keys β all major hosted forges do.
- You want something the whole team can configure in five minutes without learning GPG.
- You may later want hardware-backed keys; the same steps work with a FIDO2
sk-key, covered in signing Git commits with a YubiKey. - If your organisation already runs GPG with subkeys and a published keyring, follow setting up GPG commit signing with subkeys instead.
Step 1 β Check the Git and OpenSSH versions Jump to heading
SSH signing needs Git 2.34 or newer and an OpenSSH client recent enough to support ssh-keygen -Y sign, which means 8.2 or newer. Older versions fail with confusing messages about unknown options rather than a clear version error.
git --version # 2.34.0 or newer
ssh -V # OpenSSH_8.2 or newer
ssh-keygen -Y sign 2>&1 | head -1 # should print usage, not "unknown option" If either is too old, upgrade before going further; the rest of the configuration silently does nothing on an old Git.
Step 2 β Generate a dedicated signing key Jump to heading
Use a separate key for signing rather than reusing the key you log in to servers with. The two jobs have different risk profiles: an authentication key is loaded into agents, forwarded to jump hosts, and copied between machines; a signing key should do one thing and be revoked independently.
# Ed25519, with a comment that says what the key is for and when it was made
ssh-keygen -t ed25519 -C "git-signing $(whoami)@$(hostname) $(date +%Y-%m)" \
-f ~/.ssh/id_ed25519_signing Give it a passphrase. The agent will cache it, so you type it once per session rather than once per commit.
# Verification: the key exists and has the expected type
ssh-keygen -l -f ~/.ssh/id_ed25519_signing.pub Step 3 β Configure Git to sign with it Jump to heading
Four settings do the work: the format, the key, and automatic signing for commits and tags.
git config --global gpg.format ssh
git config --global user.signingKey ~/.ssh/id_ed25519_signing.pub
git config --global commit.gpgSign true
git config --global tag.gpgSign true user.signingKey points at the public key file; Git hands it to ssh-keygen, which finds the matching private key through the agent or next to the public file. Pointing at the private key also works, but then every signature prompts for the passphrase unless the agent already holds it.
# Load the key into the agent so you are not prompted on every commit
ssh-add ~/.ssh/id_ed25519_signing
ssh-add -l | grep signing Step 4 β Create an allowed signers file for local verification Jump to heading
Git can sign without any further setup, but it cannot verify SSH signatures without a list of trusted keys. Without it, git log --show-signature reports an error on every commit, which teaches people to ignore signature output. Create a personal file containing at least your own key.
mkdir -p ~/.config/git
printf '%s namespaces="git" %s\n' "$(git config user.email)" \
"$(cat ~/.ssh/id_ed25519_signing.pub)" >> ~/.config/git/allowed_signers
git config --global gpg.ssh.allowedSignersFile ~/.config/git/allowed_signers On a team, replace this personal file with a shared one generated from the roster, as described in building an allowed signers file from team membership, so everyone sees each otherβs commits as verified.
# Verification: a fresh commit reports a good signature
git commit --allow-empty -m "signing test"
git log -1 --show-signature
git log -1 --format='%G? %GS' # G your@email Step 5 β Register the key with your forge as a signing key Jump to heading
The forge needs the public key to show commits as verified. On every major forge there is a separate list for signing keys; adding the key only as an authentication key does not work.
# GitHub CLI: add as a signing key (requires the admin:ssh_signing_key scope)
gh auth refresh -s admin:ssh_signing_key
gh ssh-key add ~/.ssh/id_ed25519_signing.pub --type signing --title "laptop $(date +%Y-%m)"
gh api /user/ssh_signing_keys --jq '.[].title' The email on your commits must also be a verified email on the forge account, or the forge shows the signature as unverified even though it is cryptographically valid.
# Verification: the committer email matches a verified address on the account
git config user.email
gh api /user/emails --jq '.[] | select(.verified) | .email' Step 6 β Sign tags and verify them Jump to heading
With tag.gpgSign set, every annotated tag is signed automatically. Lightweight tags cannot be signed because they have no object of their own; the difference is covered in signed tags vs signed commits.
git tag -a v1.4.0 -m "Release 1.4.0" # signed because tag.gpgSign is true
git verify-tag v1.4.0
git tag -v v1.4.0 | tail -2 Validation checklist Jump to heading
Frequently Asked Questions Jump to heading
Can I use the same key on several machines? Jump to heading
You can, but it is better not to. A separate key per device means losing one laptop revokes one key, not your ability to sign everywhere. The trade-offs are covered in using a separate signing key per device.
Why does the forge show my commit as unverified? Jump to heading
Almost always one of two things: the key was added as an authentication key rather than a signing key, or the commitβs email is not a verified address on your account. Check both before suspecting the signature itself.
Do I need to restart anything after changing these settings? Jump to heading
No. Git reads configuration on every command. The only state that persists is the agent; if you regenerate the key, remove the old one with ssh-add -d and add the new one.
Related Jump to heading
- GPG vs SSH Commit Signing β the parent topic and the comparison behind this choice.
- Signing Commits on Windows and WSL β the same setup where the agent lives on a different side of the boundary.
- Keeping SSH Signing Keys in an OS Keychain Agent β protecting the key you just created.
- Migrating a Team from GPG to SSH Commit Signing β rolling this out across a team that already signs.