Git identity is self-declared. user.email is a config value, not a credential — anyone can set it to your address, commit, and push wherever they have write access. GitHub renders your avatar on the result. Nothing in the process verifies that you wrote anything.

Commit signing fixes that, and since Git 2.34 you can do it with the SSH key you already push with. No GPG keyring, no key servers, no expiry dance.

Setup

Four config lines:

[user]
    signingkey = ~/.ssh/id_ed25519.pub
[gpg]
    format = ssh
[commit]
    gpgsign = true

Note signingkey points at the public key. Git derives the private half from it — and if you use an agent, it never touches the file at all.

Then upload the same public key to GitHub a second time, as a signing key (Settings → SSH and GPG keys → New SSH key → key type: signing). Authentication keys and signing keys are separate slots even when the key material is identical; uploading to one does not populate the other. With gh installed:

gh ssh-key add ~/.ssh/id_ed25519.pub --type signing --title "signing-key"

That needs the admin:ssh_signing_key scope, which the default gh login does not include — gh auth refresh -h github.com -s admin:ssh_signing_key first if it 404s.

Verifying locally

GitHub verifies signatures server-side against the key you uploaded. For git log --show-signature to work on your own machine, Git needs to know which keys to trust, via an allowed-signers file:

echo "you@example.com $(cat ~/.ssh/id_ed25519.pub)" \
  > ~/.config/git/allowed_signers
git config --global gpg.ssh.allowedSignersFile ~/.config/git/allowed_signers

The first field is an email, and it must match the committer email on the commits you want verified — a mismatch produces “No principal matched” even though the signature itself is fine. Then:

$ git log -1 --show-signature
Good "git" signature for you@example.com with ED25519 key SHA256:TbzyV5...

What this does and doesn’t get you

It proves a commit was made by someone holding your private key. That is a real improvement over an unauthenticated email string, and it is what GitHub’s Verified badge attests to.

It does not prove the commit is good, and it does not protect a key sitting readable on a laptop that gets compromised. If you want the private half to be genuinely unextractable, generate the key on a hardware token:

ssh-keygen -t ed25519-sk -C "signing key"

Same config, same GitHub upload — the key now lives on the token and signing requires a physical touch. Buy two and register both, because a hardware key you lose without a backup is a lockout, not an inconvenience.

One caveat about history

Signing is not retroactive. Commits made before you turned it on stay unsigned, and rewriting history to sign them changes every hash from that point forward. Not worth it on a shared branch. Turn it on, and let the verified history start from today.