Signed commits and verification
Expert
Why this matters
The author of a commit is whatever user.name and user.email say. Anyone can set them to anything, so on its own the author field is a label, not a claim. Signing turns it into one.
The problem, in one command
$ git -c user.name="Ana Lopez" -c user.email="ana@northwind-trails.example" commit -m "docs: something"That commit is now attributed to Ana, from anyone's machine. Push access is required to publish it, which is the real control, but within a repository the author field proves nothing by itself.
SSH signing
Signing used to mean GPG and was correspondingly rare. SSH signing, available since Git 2.34, uses the key you already have (lesson 8.2) and takes five minutes.
-
Tell Git to sign with SSH and which key to use:
Terminal $ git config --global gpg.format ssh $ git config --global user.signingkey ~/.ssh/id_ed25519.pub $ git config --global commit.gpgsign true -
Commit as usual. Nothing changes visibly.
-
To verify locally, Git needs to know which keys to trust, in an allowed signers file:
Terminal $ git config --global gpg.ssh.allowedSignersFile ~/.ssh/allowed_signersEach line is an email and a public key:
~/.ssh/allowed_signers ana@northwind-trails.example ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA… -
Upload the same public key to the platform as a signing key, which is a separate list from authentication keys.
Without step 3, verification fails with a precise message:
error: gpg.ssh.allowedSignersFile needs to be configured and exist for ssh signature verificationWith it:
$ git log --show-signature -1commit c19cb2a6a00205763ed6f713aa4ccb86568c5888
Good "git" signature for ana@northwind-trails.example with ED25519 key SHA256:oXkrCC4GFMEX55s1y3ga0TEiIENchQ3qo6OHN8SSsMo
Author: You <you@example.com>
Date: Sat Sep 5 09:00:00 2026 +0200
docs: a signed commitA compact form for scripts and for scanning a log:
$ git log -1 --format='%h %G? %GS'c19cb2a G ana@northwind-trails.example%G? is G for a good signature, B for bad, U for good but untrusted, N for none.
GPG, briefly
GPG signing still exists and some organizations require it. The shape is the same: generate a key, git config --global user.signingkey <key id>, gpg.format openpgp (the default), and upload the public key to the platform. The differences are practical: GPG keys need managing, expiring and distributing, and the agent setup is fiddlier. If you have a choice, choose SSH.
What Verified means on the platform
Both platforms show a Verified badge on a signed commit, and both mean the same narrow thing:
This commit carries a valid signature from a key registered to an account, and the commit's author email matches a verified email on that account.
What it does not mean:
- That the change is correct, reviewed, or safe.
- That the person intended to publish it, if their key was stolen.
- Anything at all about the commits around it.
An unverified commit is not suspicious by default either: most people do not sign. The badge is useful precisely in projects where everyone signs, because then an unsigned commit stands out.
Both platforms can enforce it: GitLab through a push rule requiring signed commits, GitHub through a ruleset's Require signed commits. That is when signing stops being personal hygiene and becomes a project rule.
Signing tags
$ git tag -s v1.3.0 -m "Release 1.3.0"
$ git tag -v v1.3.0A signed tag is the stronger and older practice: it says "this exact state is the release", which matters more than any individual commit for anyone consuming your releases (lesson 13.6).
How to do it
$ git config --global gpg.format ssh
$ git config --global user.signingkey ~/.ssh/id_ed25519.pub
$ git config --global commit.gpgsign true
$ git log --show-signature -1With includeIf, work commits can be signed with a work key and personal ones with another (lesson 18.2).
git.enableCommitSigning in settings makes VS Code sign commits it creates; the key configuration is Git's, not the editor's.
Settings → Version Control → Git → Configure GPG Key covers GPG; SSH signing is configured in ~/.gitconfig and IntelliJ honours it.
Upload the public key under Edit profile → SSH Keys with the usage type Signing, or GPG Keys for GPG. A push rule can require signed commits on protected branches (lesson 9.5).
Settings → SSH and GPG keys, adding the key as a signing key. A ruleset's Require signed commits enforces it (lesson 10.5).
Common mistakes
- Expecting the author field to prove identity. It is configuration.
- Forgetting the allowed signers file, then reporting that verification is broken.
- Uploading the key as an authentication key only. Signing is a separate usage.
- A signing email that is not verified on the account, which shows as unverified.
- Treating Verified as a quality signal. It is an identity signal, and a narrow one.
- Requiring signing without warning the team, which blocks everyone's push at once.
Try it yourself
Goal: sign a commit and verify it locally.
- Configure SSH signing with the three
git configcommands above. - Make a commit and run
git log --show-signature -1. Read the error about the allowed signers file. - Create the allowed signers file with your email and public key, and set
gpg.ssh.allowedSignersFile. - Run the same command again and read the
Good "git" signatureline. - Run
git log -3 --format='%h %G? %GS'and note which of your recent commits are signed.
Expected result: one commit that verifies locally, and a compact way to see the signing status of a whole log.
Show solution
Step 2's error is worth meeting deliberately, because it is the most common confusion with SSH signing: the commit was signed, and the failure is that Git does not know which keys to trust. Signing and verifying are separate configurations, and only the first is needed for the platform's badge.