Skip to content

Signed commits in Git

Anyone can configure a name and email in Git and make a commit that appears to come from someone else. This is a risk when identity traceability matters, especially in:

  • Open source projects with many collaborators.
  • Repositories where contributions count toward legal or compliance certification.
  • Teams that need to prove who introduced a change.

Git solves this with cryptographic signatures: each commit can come with a mathematical proof that it was generated by the person it claims to be from. GitHub (and GitLab) validate that signature and display the "Verified" badge next to the commit.

flowchart LR
    Dev[Your PC] -->|git commit -S| Commit[Signed commit]
    Commit -->|hash + signature| GitHub
    GitHub -->|verifies signature with your public key| Badge[Verified badge]

1. Choose a signing tool

There are two modern ways to sign commits:

Tool Algorithm Support Notes
GnuPG (GPG) RSA, Ed25519, ECC GitHub, GitLab, Bitbucket, Gitea Classic standard. Requires installing GPG.
SSH signing Ed25519, RSA GitHub (since 2022), GitLab Reuses your existing SSH key. Simpler.

In this guide we use GPG because it's the most universal method. If you already have SSH configured, you can check out the official GitHub guide on SSH signing as an alternative.

2. Install GnuPG

  1. Download Gpg4Win.
  2. In the installer, only check "GnuPG" (the other tools are optional and add noise to the menu).
  3. After installing, open Git Bash and verify:
gpg --version

With Homebrew:

brew install gnupg

Verify:

gpg --version

Debian/Ubuntu:

sudo apt install gpg

Fedora:

sudo dnf install gnupg

Verify:

gpg --version

3. Generate the GPG key

Generate a new key with the Ed25519 algorithm (recommended) or RSA 4096 (wide compatibility):

gpg --quick-generate-key "Your Name <your.email@example.com>" ed25519 default 0

The final 0 means the key never expires. If you want it to expire, replace it with a duration like 1y (one year) or 2y.

gpg --full-generate-key

The wizard asks you:

  1. Type: (1) RSA and RSA (Enter).
  2. Size: 4096.
  3. Expiration: 0 (never expires) or your preference.
  4. Confirm with O (OK).
  5. Name and surname.
  6. Email (the same one as on GitHub).
  7. Optional comment.
  8. Passphrase (recommended).

After a few seconds, GPG confirms with a message like:

gpg: key 5F2A8B3C9D4E1F6A marked as ultimately trusted
gpg: revocation certificate stored as '/home/user/.gnupg/openpgp-revocs.d/5F2A8B3C9D4E1F6A.rev'

Keep the revocation certificate

The .rev file serves to revoke the key if it's compromised. Store it in a safe place (not on the same machine).

4. Get the key ID

For Git and GitHub to identify your key, you need the long ID:

gpg --list-secret-keys --keyid-format LONG

You'll see something like:

sec   ed25519/5F2A8B3C9D4E1F6A 2026-01-15 [SC]
      1234567890ABCDEF1234567890ABCDEF12345678
uid                   [ultimate] Your Name <your.email@example.com>
ssb   ed25519/A1B2C3D4E5F6G7H8 2026-01-15 [E]

The ID you need is the one that appears after ed25519/ in the sec line: in this case, 5F2A8B3C9D4E1F6A.

5. Export the public key in ASCII format

To upload it to GitHub:

gpg --armor --export 5F2A8B3C9D4E1F6A

Copy everything in the output, including the lines:

-----BEGIN PGP PUBLIC KEY BLOCK-----
...
-----END PGP PUBLIC KEY BLOCK-----

The armor command

Without --armor, GPG exports the key in binary. GitHub needs the ASCII format (base64) to paste it in a web form.

6. Upload the key to GitHub

  1. Go to github.com/settings/gpg/new.
  2. Title: a descriptive name (e.g. "GPG MacBook personal").
  3. Key: paste the block you copied.
  4. Click Add GPG key.

If the key's email matches a verified email of your GitHub account, commits signed with that key will be displayed as Verified.

Multiple emails

If you use multiple emails (work + personal), add each one to the key when generating it, or generate separate keys and upload each one. For a signed commit to display as verified, the Git user.email must match an email on the key AND be verified on GitHub.

7. Configure Git to use the key

Tell Git which key to use for signing:

git config --global user.signingkey 5F2A8B3C9D4E1F6A
git config --global commit.gpgsign true    # sign ALL commits
git config --global tag.gpgsign true       # sign tags too

With commit.gpgsign true, you don't need to remember to type -S on each commit; Git signs automatically.

If gpg is not in your PATH

On Windows with Git Bash, GPG is installed in C:\Program Files (x86)\GnuPG\bin\gpg.exe. If Git can't find it, configure the path manually:

git config --global gpg.program "C:/Program Files (x86)/GnuPG/bin/gpg.exe"

8. Make signed commits

If you enabled commit.gpgsign true, all your commits are signed automatically. If you disabled it:

git commit -S -m "Signed confirmation with GPG"

For merges:

git merge --verify-signatures -S feature/login

--verify-signatures rejects the merge if any of the incoming commits are not signed. Useful when the repository requires mandatory signing (you'll see this in the branch protection rules).

9. Verify signatures locally

To inspect a commit's signature:

git log --show-signature -1

You'll see:

commit 8a4f9c2b3e1d7a6f...
gpg: Signature made [date]
gpg:                using EDDSA key 5F2A8B3C9D4E1F6A
gpg: Good signature from "Your Name <your.email@example.com>"

If the signature is invalid, GPG shows BAD signature. In that case, don't trust the commit: it may have been tampered with.

10. Rotate a compromised key

If your private key is exposed (laptop stolen, backup leaked), revoke the key immediately:

  1. Import the revocation certificate:
gpg --import /path/to/certificate.rev
  1. Publish the revocation on a public key server:
gpg --keyserver hkps://keys.openpgp.org --send-keys 5F2A8B3C9D4E1F6A
  1. Remove the key from GitHub (Settings → SSH and GPG keys).

  2. Generate a new key and repeat the upload process.

Preventive expiration

An alternative to the revocation certificate is to generate keys with expiration (e.g. 1 year) and renew them before they expire. It's discipline, but it reduces the window of exposure.

11. Storing GPG credentials (passphrase cache)

Every time you sign a commit, GPG asks for your passphrase to unlock the private key. Typing it on every commit quickly becomes tedious. The fix is gpg-agent, a background process (analogous to ssh-agent) that caches the unlocked passphrase for a configurable amount of time.

Configuring the cache TTL

The default TTL is usually 10 hours (3600 seconds × 10) after the last time you used the key. To change it, edit ~/.gnupg/gpg-agent.conf:

default-cache-ttl 3600        # 1 hour since last use
max-cache-ttl 86400           # absolute cap of 24 hours

After editing, reload the agent:

gpgconf --reload gpg-agent

Pinentry: the window that asks for the passphrase

gpg-agent delegates passphrase capture to a program called pinentry. The behavior differs per system:

By default it uses pinentry-tty (reads the passphrase from the terminal). If you prefer a GUI, install pinentry-gnome3 (GNOME) or pinentry-qt (KDE) and select it in gpg-agent.conf:

pinentry-program /usr/bin/pinentry-gnome3

GPG Suite installs pinentry-mac, a native window with a "Save in Keychain" option so the passphrase is not asked again until the TTL expires.

Gpg4win ships pinentry-qt or pinentry-w32 (selectable from gpg-agent.conf). The passphrase is cached by gpg-agent until the configured TTL.

gpg-agent is not the same as Git Credential Manager

gpg-agent caches the passphrase of your GPG key for signing commits. It does not cache HTTPS credentials. For HTTPS authentication use Git Credential Manager (GCM), which handles user and token but does not sign commits. They are independent systems.

If VS Code can't find gpg

On Windows with Git Bash, GPG is installed at C:\Program Files (x86)\GnuPG\bin\gpg.exe. If signing a commit from VS Code fails with gpg: failed to start..., set the path manually:

git config --global gpg.program "C:/Program Files (x86)/GnuPG/bin/gpg.exe"

Next step

With cryptographic signing covered, you can return to Git and GitHub Fundamentals to review the Pull Requests workflow, or jump to the Practice section for full exercises.