Git Commit Signed With Wrong GPG Key or Signature Failed
A failed or wrong GPG signature on a commit almost always traces back to a mismatch between the key Git is configured to sign with and the key actually available (and correctly associated with your Git identity) in your local GPG setup — the signing mechanism itself rarely fails without a clear underlying configuration reason.
The Problem
Committing with signing enabled fails outright, or succeeds but shows as unverified/wrong key when checked later:
error: gpg failed to sign the data
fatal: failed to write commit object
gpg: signing failed: No secret key
Or the commit appears to sign successfully, but GitHub/GitLab shows it as "Unverified" rather than the expected "Verified" badge.
Why It Happens
Several distinct configuration mismatches produce signing failures or incorrect signatures:
- Git is configured to sign with a specific key ID that no longer exists in your local GPG keyring — commonly after regenerating keys, switching machines, or an expired key being removed
- The GPG key exists locally but its associated email address doesn't match your Git commit email exactly, causing the signature to technically succeed locally but appear unverified on the hosting platform, since the platform checks that the signing key's email matches the commit author's email
- The GPG agent can't locate the secret key material at all, often due to a keyring path issue after switching machines or operating systems, or a permissions problem on the GPG home directory
- An expired GPG key still configured as the signing key, which GPG correctly refuses to use for new signatures once past its expiration date
The Fix
First, confirm which key Git is actually configured to use for signing:
git config --get user.signingkey
Compare this key ID against what's actually available in your GPG keyring:
gpg --list-secret-keys --keyid-format=long
If the configured key ID doesn't appear in this list at all, that's the direct cause — either update the Git configuration to reference a key you actually have, or import the correct key if it exists elsewhere but isn't currently in your local keyring:
git config --global user.signingkey <correct-key-id>
Confirm the GPG key's associated email exactly matches your Git commit email, since a mismatch here is a very common cause of a signature that technically works but shows as unverified on GitHub or GitLab:
gpg --list-secret-keys --keyid-format=long
git config --get user.email
Both should reference the exact same email address — if they don't, either update your Git email to match the GPG key's associated email, or add the Git email as an additional identity on the GPG key itself:
gpg --edit-key <key-id>
gpg> adduid
gpg> save
Check whether the key has expired, since GPG won't use an expired key for new signatures:
gpg --list-keys --keyid-format=long
Look at the expiration date shown for the relevant key. If it's expired, either extend its expiration (if you still control the key) or generate a new one and update both your Git configuration and the public key uploaded to your Git hosting platform.
If the GPG agent seems unable to find the key even though it's listed correctly, restart it, since a stale agent process is a common source of intermittent signing failures that don't match what the keyring itself actually shows:
gpgconf --kill gpg-agent
gpg-agent --daemon
Test signing in isolation, outside of Git entirely, to confirm the underlying GPG setup works correctly before assuming the issue is Git-specific:
echo "test" | gpg --clearsign
Still Not Working?
If GPG signing works correctly when tested standalone but still fails specifically through Git, check whether Git is configured to use the correct GPG program path, particularly relevant on macOS or systems with multiple GPG installations (like both a Homebrew-installed and a system-installed version) where Git might be pointed at a different binary than the one you're testing manually:
git config --get gpg.program
which gpg
If these point to different binaries, explicitly set Git to use the same one you confirmed works in your standalone test:
git config --global gpg.program $(which gpg)