Signed Commits (GPG/SSH)

Complete guide to Git commit signing with GPG and SSH keys. Setup, verification, trust chains, and why signed commits matter for supply chain security.

published: reading time: 11 min read updated: March 31, 2026

Introduction

Every commit in Git records an author name and email — but nothing proves that the person who made the commit actually owns that identity. Anyone can commit as “Linus Torvalds torvalds@linux-foundation.org” in their local repository. The commit hash verifies content integrity, but not authorship.

Signed commits solve this problem. By cryptographically signing each commit with a GPG or SSH key, you create a verifiable chain of trust from your identity to every change in the repository. GitHub, GitLab, and other platforms display “Verified” badges on signed commits, giving you and your team confidence in the provenance of every line of code.

This guide covers both GPG and SSH signing methods, step-by-step setup for every major platform, and the operational practices that make signed commits a practical part of your workflow.

When to Use / When Not to Use

When to sign commits:

  • Open source projects where identity matters
  • Enterprise repositories with compliance requirements
  • Supply chain security for critical software
  • Regulated industries (finance, healthcare, government)
  • Any project where you need non-repudiation

When signing may be optional:

  • Personal projects with single contributors
  • Internal tools with trusted teams
  • Rapid prototyping where overhead outweighs benefit

Core Concepts

Git supports two signing methods:


graph TD
    COMMIT["Commit Object"] -->|signs with| KEY["Cryptographic Key"]
    KEY --> GPG["GPG/PGP Key\n(traditional)"]
    KEY --> SSH["SSH Key\n(modern, simpler)"]

    GPG --> SIG1["GPG Signature\nin commit header"]
    SSH --> SIG2["SSH Signature\nin commit header"]

    SIG1 --> VERIFY1["Verify with GPG\npublic key"]
    SIG2 --> VERIFY2["Verify with SSH\npublic key"]

    VERIFY1 --> TRUST1["Web of Trust\nor explicit trust"]
    VERIFY2 --> ALLOW["Allowed Signers File\nor platform trust"]

Both methods embed a cryptographic signature in the commit object. The signature covers the commit content (tree, parent, author, committer, message), so any tampering invalidates it.

Architecture or Flow Diagram


flowchart LR
    DEV["Developer"] -->|creates| COMMIT["Commit Object"]
    COMMIT -->|signs with| KEY["Private Key\n(GPG or SSH)"]
    KEY -->|produces| SIG["Signature Block\nembedded in commit"]

    SIG -->|pushed to| PLATFORM["GitHub/GitLab"]
    PLATFORM -->|verifies with| PUBKEY["Public Key\nregistered on platform"]
    PUBKEY -->|result| BADGE["Verified Badge\nor Warning"]

    AUDITOR["Auditor"] -->|runs| VERIFY["git verify-commit"]
    VERIFY -->|checks| LOCALKEY["Local Keyring\nor allowed signers"]

Step-by-Step Guide / Deep Dive

GPG Signing Setup

1. Generate a GPG key:


# Generate a new GPG key (interactive)
gpg --full-generate-key

# Or non-interactive for scripting
gpg --batch --gen-key << EOF
%no-protection
Key-Type: RSA
Key-Length: 4096
Subkey-Type: RSA
Subkey-Length: 4096
Name-Real: Your Name
Name-Email: your.email@example.com
Expire-Date: 0
%commit
EOF

2. Configure Git to use it:


# List your GPG keys
gpg --list-secret-keys --keyid-format=long

# Configure Git (use the key ID from above)
git config --global user.signingkey ABC123DEF456
git config --global commit.gpgsign true

# Optional: sign tags too
git config --global tag.gpgsign true

3. Add your public key to GitHub/GitLab:


# Export your public key
gpg --armor --export ABC123DEF456

# Copy the output and add it to:
# GitHub: Settings → SSH and GPG keys → New GPG key
# GitLab: Settings → GPG Keys

SSH Signing Setup (Git 2.34+)

SSH signing is simpler since most developers already have SSH keys:


# Configure Git to use SSH signing
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub
git config --global commit.gpgsign true

# Create an allowed signers file
cat > ~/.gitallowed << EOF
your.email@example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA...
EOF

# Tell Git where to find it
git config --global gpg.ssh.allowedSignersFile ~/.gitallowed

Signing Existing Commits


# Re-sign the last commit
git commit --amend --no-edit -S

# Re-sign multiple commits (interactive rebase)
git rebase -i HEAD~5
# Mark commits with 'edit', then:
git commit --amend --no-edit -S
git rebase --continue

# Sign all commits on a branch
git rebase main --exec 'git commit --amend --no-edit -S'

Verification


# Verify the last commit
git log --show-signature -1

# Verify a specific commit
git verify-commit <sha>

# Show signature status in log
git log --format="%H %G? %GS" -10
# %G? = signature status (G=good, B=bad, U=untrusted, N=no signature)
# %GS = signer information

Production Failure Scenarios + Mitigations

ScenarioSymptomsMitigation
Expired GPG key”error: gpg failed to sign the data”Extend key expiry: gpg --edit-key <id> expire
Missing key on platformCommits show “Unverified” badgeUpload public key to GitHub/GitLab
SSH signing not supported”error: unsupported value for gpg.format”Upgrade Git to 2.34+
Key rotationOld commits show as unverifiedKeep old public keys; upload new ones
CI/CD signingAutomated commits unsignedUse bot keys or disable signing for CI

Trade-offs

AspectGPGSSH
Setup complexityHigher (key generation, keyring)Lower (reuse existing SSH key)
Platform supportUniversal (GitHub, GitLab, Bitbucket)Git 2.34+, GitHub, GitLab 15.0+
Key managementGPG keyring, web of trustSSH keys, allowed signers file
User familiarityLess common among developersWidely understood
Expiry handlingBuilt-in expiry datesNo expiry (manage manually)

Implementation Snippets


# Enable signing for all commits
git config --global commit.gpgsign true

# Sign a single commit
git commit -S -m "Signed commit"

# Sign a tag
git tag -s v1.0 -m "Release 1.0"

# Verify all commits in a range
git log --show-signature v1.0..v2.0

# Check if commits are signed
git log --format="%H %G?" main

# Configure GUI tools to sign
git config --global gpg.program $(which gpg)

# Use specific GPG home directory
git config --global gpg.minTrustLevel ultimate

Observability Checklist

  • Monitor: Percentage of signed commits in repository
  • Verify: Platform shows “Verified” badge on recent commits
  • Track: Key expiry dates (set calendar reminders)
  • Audit: Run git log --show-signature on release branches
  • Alert: Unsigned commits on protected branches

Security/Compliance Notes

  • Signed commits provide non-repudiation — the signer cannot deny authorship
  • GPG keys should be stored securely (consider hardware tokens like YubiKey)
  • SSH keys for signing should be separate from authentication keys
  • Rotate keys periodically and update platforms
  • See Git Secrets Management for comprehensive security

Common Pitfalls / Anti-Patterns

  • Signing with expired keys — platforms reject them
  • Not backing up GPG keys — losing your key means losing your identity
  • Using weak algorithms — prefer RSA 4096 or Ed25519
  • Signing in CI/CD without key management — exposes private keys
  • Assuming signed = reviewed — signing proves identity, not code quality

Quick Recap Checklist

  • GPG signing requires key generation and platform registration
  • SSH signing (Git 2.34+) reuses existing SSH keys
  • Configure commit.gpgsign true for automatic signing
  • Verify with git log --show-signature
  • Platforms display “Verified” badges for recognized keys
  • Keep keys secure and backed up
  • Rotate keys before expiry

Interview Q&A

What does a signed commit prove?

A signed commit proves authorship (the holder of the private key created it) and integrity (the content hasn't been modified since signing). It does NOT prove code quality, review status, or that the author name/email match the key owner — those are separate concerns.

Why would you choose SSH signing over GPG?

SSH signing is simpler to set up — most developers already have SSH keys for authentication. It avoids GPG's complexity (keyring management, web of trust). However, GPG has broader platform support and built-in key expiry. Choose SSH for simplicity, GPG for compatibility.

Can you sign commits after they've been pushed?

Yes, but it rewrites history. Use git rebase -i with exec git commit --amend --no-edit -S to sign existing commits. This changes commit hashes, so you'll need to force-push. For shared branches, coordinate with your team to avoid conflicts.

How does GitHub verify GPG signatures?

GitHub stores your public GPG key when you add it in settings. When you push signed commits, GitHub extracts the signature from the commit object and verifies it against your stored public key. If it matches, GitHub displays a "Verified" badge. If the key isn't registered, the commit shows "Unverified."

Commit Signing Flow (Clean Architecture)


graph TD
    DEV["Developer"] -->|1. writes| COMMIT["Commit Object"]
    DEV -->|2. signs with| PRIVKEY["Private Key\nGPG or SSH"]
    PRIVKEY -->|3. produces| SIG["Signature\nembedded in commit"]

    SIG -->|4. pushed to| PLATFORM["GitHub / GitLab"]
    PLATFORM -->|5. verifies with| PUBKEY["Public Key\nregistered on platform"]
    PUBKEY -->|6. result| BADGE["Verified Badge"]

    VERIFY["Local Verification"] -->|git verify-commit| RESULT["Good / Bad / Untrusted"]
    RESULT -->|checks against| KEYRING["Local Keyring\nor allowed signers"]

Production Failure: Key Lifecycle Issues

Scenario: Expired GPG key breaking CI and verification


# Symptoms
$ git commit -S -m "Add feature"
error: gpg failed to sign the data
fatal: failed to write commit object

$ git log --show-signature -1
# Shows "Expired key" warning

# Root cause: GPG key has an expiry date that has passed

# Recovery steps:

# 1. Check key expiry
gpg --list-keys --keyid-format long
# Look for [expires: 2024-01-01]

# 2. Extend key expiry
gpg --edit-key YOUR_KEY_ID
> expire
# Set new expiry date (e.g., 2y for 2 years)
> save

# 3. Re-export and update platform
gpg --armor --export YOUR_KEY_ID
# Update on GitHub/GitLab with new public key

# 4. Re-sign affected commits (if any failed)
git commit --amend --no-edit -S

# === Key Revocation Scenario ===
# If key is compromised:

# 1. Generate revocation certificate (do this when creating key!)
gpg --gen-revoke YOUR_KEY_ID > revocation.crt

# 2. Import revocation
gpg --import revocation.crt

# 3. Upload revoked key to keyserver
gpg --keyserver keys.openpgp.org --send-keys YOUR_KEY_ID

# 4. Generate new key and update all platforms
# 5. Old signed commits will show "revoked" status

Trade-offs: GPG vs SSH vs X.509 Signing

AspectGPGSSHX.509
Setup complexityHigh (keyring, web of trust)Low (reuse existing SSH key)Very high (certificate authority)
Git version requiredAny2.34+2.34+
Platform supportUniversal (GitHub, GitLab, Bitbucket)GitHub, GitLab 15.0+Limited (enterprise GitLab)
Key managementGPG keyring, subkeysSSH keys, allowed_signers filePKI, certificate lifecycle
Expiry handlingBuilt-in expiry datesNo expiry (manual management)Certificate validity period
Hardware supportYubiKey, smart cardsYubiKey, SSH agentSmart cards, HSM
User familiarityLow (most devs don’t know GPG)High (every dev has SSH keys)Low (enterprise IT only)
Best forOpen source, maximum compatibilityModern teams, simplicityEnterprise, compliance

Security/Compliance: Key Management Best Practices

Key Generation:

  • Use Ed25519 for SSH (or RSA 4096 minimum)
  • Use RSA 4096 or Ed25519 for GPG
  • Never use DSA (deprecated and weak)

Key Storage:

  • Store private keys on hardware tokens (YubiKey) when possible
  • Never store private keys in cloud sync (Dropbox, iCloud)
  • Use separate keys for signing vs authentication

Key Rotation:

  • Rotate GPG keys every 1-2 years
  • Rotate SSH signing keys every 6-12 months
  • Keep old public keys registered on platforms (old commits still need verification)

Backup and Recovery:

  • Export and encrypt GPG secret key: gpg --export-secret-keys --armor > backup.asc
  • Store backup in encrypted offline storage (not cloud)
  • Generate revocation certificate immediately after key creation
  • Document key IDs and expiry dates in team wiki

Compliance Notes:

  • Signed commits satisfy code provenance requirements in SOC2, ISO 27001
  • For SBOM and supply chain compliance, combine with SLSA framework
  • Key signing ceremonies may be required for regulated environments
  • Maintain an audit trail of key rotations and revocations

Resources

Category

Related Posts

Git Secrets Management and Pre-commit Hooks

Preventing secrets from entering repositories using pre-commit hooks, secret scanning tools, and automated detection. Protect API keys, tokens, and credentials from accidental commits.

#git #version-control #secrets

Removing Sensitive Data from Git History

Using git filter-repo, BFG Repo-Cleaner, and git filter-branch to scrub secrets, passwords, and credentials from Git history. Step-by-step remediation guide.

#git #version-control #secrets

Centralized vs Distributed VCS: Architecture, Trade-offs, and When to Use Each

Compare centralized (SVN, CVS) vs distributed (Git, Mercurial) version control systems — their architectures, trade-offs, and when to use each approach.

#git #version-control #svn