Sign Git commits and tags with GnuPG or SSH keys

Updated November 2026

A signed Git commit or tag proves that someone holding your key created it. Git can sign with an OpenPGP key through GnuPG (the default) or, since Git 2.34, with an SSH key. Set user.signingkey, turn on commit.gpgsign, and upload the same public key to GitHub or GitLab to get the “Verified” badge.

The examples were tested in November 2026 with Git 2.47, GnuPG 2.4.7 and OpenSSH 10.0 on Debian 13. The newest Git release as of November 2026 is 2.56.0, and the settings below are the same there.

What signing does and does not prove

Git records an author name and email in each commit, but anyone can type any name. A signature adds proof that the commit object, including its tree and parent commits, was signed by a specific key. It does not prove the code is good, and it says nothing about commits that are not signed. Signing is most useful for release tags and for projects that check signatures before merging.

Sign with your OpenPGP key

You need a key pair with signing capability (create one). Find its fingerprint:

gpg --list-secret-keys --keyid-format=long

Tell Git which key to use, and sign everything by default:

git config --global user.signingkey 47C8795201EE2CC8465E9A646DB662AD4C80E1C2
git config --global commit.gpgsign true
git config --global tag.gpgSign true

Git passes user.signingkey unchanged to gpg’s --local-user, so a fingerprint, long key ID or email all work; the full fingerprint is the most precise. The email in your Git configuration (user.email) should be one of the user IDs on the key, or the hosting sites will not show the commit as verified.

Without the defaults, sign one commit with -S and one tag with -s:

git commit -S -m "Fix the parser"
git tag -s v1.2.0 -m "Release 1.2.0"

If gpg cannot ask for your passphrase in a terminal session, set GPG_TTY in your shell start-up file. Both GitHub’s documentation and the gpg-agent manual give the same line:

export GPG_TTY=$(tty)

With commit.gpgsign turned on, a long rebase signs every rewritten commit again; the Git manual suggests using an agent so you do not type the passphrase each time (gpg-agent does this for you). Keys on a YubiKey or other OpenPGP card work the same way: Git calls gpg, gpg asks the card.

Check a signature

git log --show-signature -1
git verify-commit HEAD
git verify-tag v1.2.0

A good signature looks like this:

gpg: Signature made Wed 04 Nov 2026 02:02:43 AM EST
gpg:                using EDDSA key 47C8795201EE2CC8465E9A646DB662AD4C80E1C2
gpg: Good signature from "Test User <test@example.org>" [ultimate]

git verify-commit exits with status 0 for a good signature, so you can use it in scripts. For a quick overview of many commits use the %G? format:

git log --format='%h %G? %GK %GS' -10
Values of %G? in git log
CodeMeaning
GGood (valid) signature
UGood signature, unknown validity (you have not certified or trusted the key)
XGood signature that has expired
YGood signature made by an expired key
RGood signature made by a revoked key
BBad signature
ECannot be checked, for example the key is missing
NNo signature

To check other people’s commits you need their public keys in your keyring: fetch them by fingerprint from a keyserver or by email via WKD. git merge --verify-signatures aborts a merge unless the tip commit of the merged branch is signed with a valid key; by default that requires a key with at least marginal trust (adjust with gpg.minTrustLevel).

Sign with an SSH key

Git 2.34 added SSH signing. Its release notes warn that it does not work with ssh-keygen from OpenSSH 8.7, so use OpenSSH 8.8 or later. If you already have an SSH key, this is the quickest setup:

git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub
git config --global commit.gpgsign true
git config --global tag.gpgSign true

user.signingkey can point to the private key or, when the key is loaded in ssh-agent, to the public key. Git calls ssh-keygen to sign.

SSH has no web of trust, so Git needs a list of keys you accept before it can verify anything. Without it, git log --show-signature prints “gpg.ssh.allowedSignersFile needs to be configured and exist for ssh signature verification”. Create the file:

echo "$(git config user.email) namespaces=\"git\" $(cat ~/.ssh/id_ed25519.pub)" >> ~/.ssh/allowed_signers
git config --global gpg.ssh.allowedSignersFile ~/.ssh/allowed_signers

Each line holds the principals (email addresses), options and the public key, as described under “ALLOWED SIGNERS” in ssh-keygen(1). The namespaces="git" option limits that key to Git signatures. Add a line for each colleague whose commits you check. A verified commit then shows:

Good "git" signature for test@example.org with ED25519 key SHA256:+IBb4Ux59J1LGihbXkzBJPBE8mY5iLH+KB9ZreK5vmc

Git treats a key listed in the allowed signers file as fully trusted; a key that is not listed has undefined trust, and git verify-commit fails.

OpenPGP or SSH?

GitHub: the Verified badge

  1. Export your public key: gpg --armor --export FPR (or copy your .pub SSH key).
  2. On GitHub, open Settings → SSH and GPG keys (in the “Access” section of the sidebar) and click New GPG key, or for SSH click New SSH key and add it as a signing key. A key you already uploaded for SSH login must be uploaded again as a signing key.
  3. Make sure the email in the key is a verified email on your GitHub account and matches your commit email. GitHub says the key “must be associated with a verified email that matches your committer identity”. To keep your address private, you can use your GitHub-provided no-reply address in both places.

GitHub lists RSA, ElGamal, DSA, ECDH, ECDSA and EdDSA as supported GPG key algorithms. It shows Verified when the signature checks out and Unverified when a commit is signed but cannot be verified; unsigned commits show no status by default. With the optional vigilant mode turned on, your unsigned commits are also marked Unverified, and a commit whose author differs from the committer can be shown as Partially verified.

GitHub records verification when the commit is pushed. Its documentation says previously verified commits keep their status even if signing keys are later rotated or revoked.

GitLab: the Verified badge

  1. In GitLab, select your avatar, then Edit profile → Access → GPG keys, then Add new key, and paste the exported public key. For SSH signing, add the SSH key with usage type “Authentication & Signing” or “Signing”.
  2. GitLab requires that one email in the GPG key matches a verified email of your GitLab account, and that the committer email matches it too. It ignores emails from revoked user IDs. For SSH signing, the committer email must match a verified email on your account.

GitLab supports GPG keys, SSH keys and X.509 certificates. Its SSH documentation draws a useful line: revoking a key in GitLab marks earlier commits signed with it as Unverified, while deleting the key leaves earlier commits as they were. New commits stay Unverified until you add a key again.

Common problems

“gpg failed to sign the data” and “No secret key”
user.signingkey names a key that is not in your secret keyring (check gpg -K), or the key has expired. Extend it with gpg --quick-set-expire (how) and upload the updated public key to the hosting site.
No passphrase prompt in a terminal
Set export GPG_TTY=$(tty) and open a new shell.
Signed locally, Unverified on the site
Usually the commit email is not on the key, is not verified on the account, or the key on the site is an older copy without your newer subkey. Upload the current public key again.
Git uses the wrong gpg program
Set it explicitly: git config --global gpg.program gpg (or the full path, for example on Windows with Gpg4win).

Sources