Skip to content

← Writing

engineering

Signing Commits with SSH and GPG

· Jerwin Arnado ·

Git records an author name and email on every commit — and anyone can set those to anything. git config user.email "[email protected]" and your commits claim to be Linus. Commit signing closes that gap: a cryptographic signature proving the commit genuinely came from a key you control. That’s what the green Verified badge on GitHub means. On a repo where supply-chain trust matters — and after the near-misses the ecosystem has had, it always does — it’s worth the ten-minute setup.

There are two ways to sign: SSH (new, simple, reuses a key you already have) and GPG (older, more features, more ceremony). Start with SSH unless you have a reason not to.

The easy path: SSH signing (Git 2.34+)

You almost certainly already have an SSH key for pushing to GitHub. Modern Git can sign commits with that same key — no new toolchain.

1. Tell Git to use SSH as the signing format and point it at your key:

git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub

2. Turn on automatic signing for every commit:

git config --global commit.gpgsign true

3. Register the key with GitHub so it can verify the badge. On GitHub: Settings → SSH and GPG keys → New SSH key → set the key type to Signing Key (not Authentication), and paste the contents of ~/.ssh/id_ed25519.pub.

That’s it. Your next commit is signed; GitHub shows Verified. Confirm locally:

git log --show-signature -1

What each setting does: gpg.format ssh switches Git’s signing backend from GPG to SSH; user.signingkey names the key; commit.gpgsign true makes signing automatic so you never have to remember the -S flag. To sign a one-off without enabling it globally, use git commit -S instead.

The classic path: GPG signing

GPG is the traditional method — more setup, but supports key expiry, subkeys, and a web of trust if you need those.

1. Generate a key:

gpg --full-generate-key          # choose RSA 4096 or ed25519

2. Find its ID and export the public block:

gpg --list-secret-keys --keyid-format=long
# note the key id after "sec   ed25519/", e.g. 3AA5C34371567BD2
gpg --armor --export 3AA5C34371567BD2

3. Wire it into Git (note gpg.format goes back to the default, or omit it):

git config --global user.signingkey 3AA5C34371567BD2
git config --global commit.gpgsign true
git config --global gpg.format openpgp

4. Register the exported -----BEGIN PGP PUBLIC KEY BLOCK----- on GitHub under Settings → SSH and GPG keys → New GPG key.

Fixing “gpg failed to sign the data”

The single most common error, almost always one of three causes:

error: gpg failed to sign the data
fatal: failed to write commit object

1. Git can’t find the GPG binary or the TTY. GPG needs to prompt for your passphrase and doesn’t know where:

export GPG_TTY=$(tty)
# make it permanent:
echo 'export GPG_TTY=$(tty)' >> ~/.zshrc

2. The wrong gpg program. On macOS with Homebrew, point Git at the installed GPG:

git config --global gpg.program $(which gpg)

3. The key expired or the agent forgot the passphrase. Test signing directly, outside Git, to see the real error:

echo "test" | gpg --clearsign

If that fails, the problem is GPG/agent config, not Git. gpgconf --kill gpg-agent restarts the agent; an expired key needs gpg --edit-key <id>expire.

Caveats and best practices

  • SSH signing is the path of least resistance. If you already push over SSH, you’re one gpg.format ssh away from signed commits with zero new tools. Prefer it.
  • Signing proves identity, not quality. A verified commit is provably yours — it says nothing about whether the code is good. It complements review; it doesn’t replace the commit-message and hook discipline.
  • Back up your keys. Lose the signing key and you can’t produce verified commits until you register a new one. For GPG, export and store the secret key somewhere safe.
  • Enforce it where it counts. GitHub branch protection can require signed commits on main — turn it on for repos where provenance actually matters, not every scratch repo.
  • user.email must match a verified GitHub email or the badge stays grey even with a valid signature.

Conclusion

# SSH signing — the modern default
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub
git config --global commit.gpgsign true
# …then register the key as a "Signing Key" on GitHub

Ten minutes of setup turns every commit into a verifiable claim of authorship. On anything you publish or collaborate on, that green badge is the cheap, honest signal that the history is really yours.