Setting Up Git and SSH Commit Signing for New Hires
A new hire's first pull request is blocked with Commits must have verified signatures, the commit list shows Unverified next to every commit, and git log --show-signature prints error: gpg.ssh.allowedSignersFile needs to be configured and exist for ssh signature verification. Signed commits are increasingly required by branch protection, and SSH-based signing (Git 2.34+) makes them easy — once the handful of settings line up. This page sets up Git identity, one SSH key for both authentication and signing, and local verification, as part of workstation provisioning and team dotfiles.
SSH signing avoids GPG entirely: no key servers, no expiry surprises, no agent quirks. The same key that pushes the commit signs it.
Diagnostic
Check the identity, signing configuration and what the hosting service knows about this machine's key:
#!/usr/bin/env bash
set -euo pipefail
git --version
git config --get user.email || echo "user.email unset"
git config --get gpg.format || echo "gpg.format unset (defaults to openpgp)"
git config --get user.signingkey || echo "user.signingkey unset"
git config --get commit.gpgsign || echo "commit.gpgsign unset"
gh api user/emails --jq '.[] | "\(.email) verified=\(.verified)"' 2>/dev/null || true
gh api user/ssh_signing_keys --jq '.[].title' 2>/dev/null || echo "no signing keys registered"
git log -1 --show-signature 2>&1 | head -3
Expected bad output:
git version 2.46.0
[email protected]
gpg.format unset (defaults to openpgp)
user.signingkey unset
commit.gpgsign unset
[email protected] verified=true
no signing keys registered
commit 3f2a91c (HEAD -> first-change)
The commit email does not match any verified address on the account, nothing is configured for signing, and no signing key is registered — three independent reasons for Unverified.
Root cause
A hosting service marks a commit verified only when four things line up: the commit has a signature; the signing key is registered on the account as a signing key (GitHub keeps authentication and signing keys in separate lists, even if they are the same key); the commit's author email is a verified email on that account; and the signature validates against the registered key. New hires commonly miss one: they register the SSH key only for authentication, commit with a personal email that is not on the work account, or never set commit.gpgsign. Locally, git log --show-signature needs an allowed_signers file that maps emails to public keys, because unlike GPG there is no key server for SSH keys — without it, Git cannot verify even correct signatures and prints a configuration error.
Resolution
- Set the identity to the work account's verified email:
#!/usr/bin/env bash
set -euo pipefail
git config --global user.name "Jane Doe"
git config --global user.email "[email protected]"
- Create one Ed25519 key (or use a hardware-backed or password-manager key) and register it twice — for authentication and for signing:
#!/usr/bin/env bash
set -euo pipefail
key="$HOME/.ssh/id_ed25519"
[ -f "$key" ] || ssh-keygen -t ed25519 -C "$(git config --get user.email)" -f "$key"
gh auth login --git-protocol ssh --web --scopes admin:ssh_signing_key
gh ssh-key add "$key.pub" --type authentication --title "$(hostname) auth"
gh ssh-key add "$key.pub" --type signing --title "$(hostname) signing"
- Configure Git to sign with it and verify locally with an allowed signers file:
#!/usr/bin/env bash
set -euo pipefail
key="$HOME/.ssh/id_ed25519.pub"
git config --global gpg.format ssh
git config --global user.signingkey "$key"
git config --global commit.gpgsign true
git config --global tag.gpgsign true
mkdir -p "$HOME/.config/git"
echo "$(git config --get user.email) namespaces=\"git\" $(cat "$key")" > "$HOME/.config/git/allowed_signers"
git config --global gpg.ssh.allowedSignersFile "$HOME/.config/git/allowed_signers"
- Re-sign any unverified commits on the branch before pushing again:
#!/usr/bin/env bash
set -euo pipefail
git rebase --exec 'git commit --amend --no-edit --reset-author -S' origin/main
git push --force-with-lease
--reset-author also rewrites the author email to the one now configured, fixing commits made with the personal address.
Expected output
$ git log -1 --show-signature
commit 9b7c1e2 (HEAD -> first-change)
Good "git" signature for [email protected] with ED25519 key SHA256:x3k9…Qm
Author: Jane Doe <[email protected]>
$ ssh -T [email protected]
Hi jdoe-acme! You've successfully authenticated, but GitHub does not provide shell access.
Git verifies the signature locally, authentication works with the same key, and after pushing, every commit on the pull request shows Verified.
Adding teammates' keys to a shared allowed_signers file — generated from the hosting service's API for everyone in the organisation — extends local verification to the whole history, so git log --show-signature confirms who signed each commit without opening a browser. That file changes as people join and leave, so generate it in CI or in the bootstrap script rather than committing a hand-maintained copy.
Prevention
Put signing configuration in team dotfiles so every machine gets it on day one; see managing team dotfiles with chezmoi.
Check signing in the doctor script by making a throwaway signed commit in a temporary repository and verifying it:
#!/usr/bin/env bash
set -euo pipefail
tmp=$(mktemp -d); cd "$tmp"; git init -q
git commit -q --allow-empty -m probe
git log -1 --show-signature 2>&1 | grep -q 'Good "git" signature' && echo "signing ok" || { echo "commit signing not working"; exit 1; }
rm -rf "$tmp"
- Enable "vigilant mode" on developer accounts so unsigned commits attributed to them are flagged, which catches misconfigured machines quickly.
Platform caveats
macOS: add
UseKeychain yesandAddKeysToAgent yesunderHost *in~/.ssh/configso the key's passphrase is stored in the keychain and the agent survives reboots.
WSL2: Windows Git and WSL Git have separate configurations and keys. Configure signing in the one that makes commits — usually WSL Git when working in the Linux filesystem.
1Password SSH agent: set
gpg.ssh.programto 1Password'sop-ssh-signbinary and pointuser.signingkeyat the public key; the private key never touches disk.
Apple Silicon (ARM64): nothing specific; Ed25519 keys and the Homebrew or system
sshwork identically.
Rollback
Turn signing off without removing keys, or remove the signing key registration:
#!/usr/bin/env bash
set -euo pipefail
git config --global --unset commit.gpgsign
git config --global --unset tag.gpgsign
gh ssh-key list | awk '/signing/ {print $NF}'
Frequently Asked Questions
Why does GitHub show Unverified even though I signed the commit?
Usually the key is registered only for authentication, not signing, or the commit email is not a verified email on your account. Register the same public key again with --type signing and make sure user.email matches a verified address.
Can one SSH key be used for both pushing and signing?
Yes. Register the same public key twice, once as an authentication key and once as a signing key. Some organisations prefer separate keys; the setup is identical with a second key file.
What is allowed_signers for?
Git needs to know which public keys are trusted for which emails to verify SSH signatures locally, since there is no key server. The file maps emails to keys; without it, git log --show-signature cannot verify anything.
Should we use GPG instead?
GPG works and is required by some policies, but it adds key servers, expiry management and agent configuration. SSH signing reuses a key developers already have and is supported by GitHub and GitLab.