Skip to content
Codeloom
Git

Signing Git Commits with GPG and SSH Keys

Set up GPG and SSH commit signing for Git. Verify commit authenticity, configure GitHub verified badges, and enforce signed commits across your team.

·8 min read · By Codeloom
Intermediate 13 min read

What you'll learn

  • Why commit signing matters for security and trust
  • Setting up GPG key generation and Git configuration
  • Using SSH keys for simpler commit signing
  • Adding your key to GitHub for verified badges
  • Enforcing signed commits across a team

Prerequisites

  • Basic Git workflow (commit, push)
  • Command line familiarity
  • A GitHub, GitLab, or Bitbucket account

Why Sign Your Commits

Git identifies authors by name and email, both of which are self-reported. Anyone can configure Git to use your name and email:

git config user.name "Linus Torvalds"
git config user.email "torvalds@linux-foundation.org"
git commit -m "totally legit commit"

This commit shows up in git log as if Linus wrote it. There is no built-in verification. Commit signing solves this by cryptographically proving that you — the holder of the private key — authored the commit. Signed commits display a “Verified” badge on GitHub, GitLab, and Bitbucket.

abc1234  Verified   feat: add payment processing    Alice (signed with GPG)
def5678             fix: update dependencies        Bob   (unsigned)
ghi9012  Verified   feat: add user dashboard        Carol (signed with SSH)
Signed vs unsigned commits on GitHub

Option 1: GPG Signing

GPG (GNU Privacy Guard) is the traditional method for commit signing. It uses public-key cryptography: you sign with your private key, and anyone can verify with your public key.

Step 1: Install GPG

# macOS
brew install gnupg

# Ubuntu/Debian
sudo apt install gnupg

# Windows (comes with Git for Windows, or install Gpg4win)
winget install GnuPG.GnuPG

Step 2: Generate a GPG key

gpg --full-generate-key

When prompted:

  • Key type: RSA and RSA (default)
  • Key size: 4096 bits
  • Expiration: Choose based on your security policy (1-2 years is common, or 0 for no expiry)
  • Name and email: Must match your Git config and GitHub account email
gpg: key 3AA5C34371567BD2 marked as ultimately trusted
public and secret key created and signed.

pub   rsa4096 2026-07-08 [SC]
      AB12CD34EF56GH78IJ90KL12MN34OP56QR78ST90
uid                      Alice Developer <alice@example.com>
sub   rsa4096 2026-07-08 [E]

Step 3: Get your GPG key ID

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

Output:

sec   rsa4096/3AA5C34371567BD2 2026-07-08 [SC]
      AB12CD34EF56GH78IJ90KL12MN34OP56QR78ST90
uid                 [ultimate] Alice Developer <alice@example.com>
ssb   rsa4096/1234567890ABCDEF 2026-07-08 [E]

Your key ID is the part after rsa4096/: 3AA5C34371567BD2.

Step 4: Configure Git to use your GPG key

# Set the signing key
git config --global user.signingkey 3AA5C34371567BD2

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

# Enable signing for all tags
git config --global tag.gpgsign true

Step 5: Export your public key for GitHub

gpg --armor --export 3AA5C34371567BD2

This outputs your public key in ASCII format. Copy the entire output including the -----BEGIN PGP PUBLIC KEY BLOCK----- and -----END PGP PUBLIC KEY BLOCK----- lines.

Step 6: Add the key to GitHub

  1. Go to Settings > SSH and GPG keys > New GPG key.
  2. Paste your public key.
  3. Click Add GPG key.

Making a signed commit

If you enabled commit.gpgsign, all commits are signed automatically:

git commit -m "feat: add payment processing"

Or sign a specific commit manually:

git commit -S -m "feat: add payment processing"

Verifying a signed commit

git log --show-signature -1

Output:

commit abc1234def5678 (HEAD -> main)
gpg: Signature made Tue Jul  8 10:30:00 2026 CDT
gpg:                using RSA key AB12CD34EF56GH78IJ90KL12MN34OP56QR78ST90
gpg: Good signature from "Alice Developer <alice@example.com>" [ultimate]
Author: Alice Developer <alice@example.com>
Date:   Tue Jul 8 10:30:00 2026 -0500

    feat: add payment processing

Troubleshooting GPG

“gpg failed to sign the data”:

This usually means the GPG agent cannot prompt for your passphrase. Fix it by setting the GPG_TTY variable:

# Add to ~/.bashrc or ~/.zshrc
export GPG_TTY=$(tty)

On macOS, install and configure pinentry-mac:

brew install pinentry-mac
echo "pinentry-program $(which pinentry-mac)" >> ~/.gnupg/gpg-agent.conf
gpgconf --kill gpg-agent

Key expired:

# Edit the key to extend expiration
gpg --edit-key 3AA5C34371567BD2
gpg> expire
# Follow prompts to set new expiration
gpg> save

Option 2: SSH Signing (Simpler)

Since Git 2.34, you can sign commits with SSH keys. This is simpler than GPG because most developers already have SSH keys for pushing to GitHub.

Step 1: Check your Git version

git --version
# Must be 2.34 or later

Step 2: Use an existing SSH key or generate one

# Generate a new Ed25519 key (recommended)
ssh-keygen -t ed25519 -C "alice@example.com"

# Or use an existing key at ~/.ssh/id_ed25519.pub

Step 3: Configure Git for SSH signing

# Tell Git to use SSH for signing
git config --global gpg.format ssh

# Point to your SSH key
git config --global user.signingkey ~/.ssh/id_ed25519.pub

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

# Enable signing for all tags
git config --global tag.gpgsign true

Step 4: Add the key to GitHub as a signing key

  1. Go to Settings > SSH and GPG keys > New SSH key.
  2. Set Key type to Signing Key (not Authentication Key).
  3. Paste the contents of ~/.ssh/id_ed25519.pub.
  4. Click Add SSH key.

You can use the same SSH key for both authentication and signing, but you need to add it twice: once as an Authentication Key and once as a Signing Key.

Step 5: Set up local verification

To verify SSH signatures locally, create an allowed signers file:

# Create the allowed signers file
echo "alice@example.com $(cat ~/.ssh/id_ed25519.pub)" > ~/.ssh/allowed_signers

# Tell Git about it
git config --global gpg.ssh.allowedSignersFile ~/.ssh/allowed_signers

Add your team members’ public keys to the file:

alice@example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... alice@example.com
bob@example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... bob@example.com

Verifying SSH-signed commits

git log --show-signature -1

Output:

commit abc1234def5678 (HEAD -> main)
Good "git" signature for alice@example.com with ED25519 key SHA256:abc123...
Author: Alice Developer <alice@example.com>
Date:   Tue Jul 8 10:30:00 2026 -0500

    feat: add payment processing

Signing Tags

Signed tags provide verified release points:

# GPG-signed tag
git tag -s v2.4.0 -m "Release 2.4.0"

# Verify a signed tag
git tag -v v2.4.0

With SSH signing configured, tags are signed with your SSH key automatically:

git tag -s v2.4.0 -m "Release 2.4.0"

GPG vs SSH Signing: Which to Choose

FactorGPGSSH
Setup complexityHigher (key generation, agent config)Lower (reuse existing SSH key)
Key managementRich (subkeys, expiry, revocation)Simple (one key file)
Web of trustSupported (key signing parties)Not supported
Git version requiredAny2.34+
GitHub supportFullFull
Best forSecurity-sensitive projects, open sourceMost teams and individual developers

Recommendation: Use SSH signing unless you need GPG’s advanced key management features. SSH signing is simpler to set up, simpler to maintain, and uses keys developers already have.

Enforcing Signed Commits

Branch protection on GitHub

  1. Go to Settings > Branches > Branch protection rules.
  2. Select the branch (e.g., main).
  3. Enable Require signed commits.

Now all commits pushed to the protected branch must have a verified signature.

Pre-receive hook (self-hosted Git)

For self-hosted Git servers, use a pre-receive hook:

#!/bin/bash
# pre-receive hook: reject unsigned commits

while read oldrev newrev refname; do
    if [ "$oldrev" = "0000000000000000000000000000000000000000" ]; then
        commits=$(git rev-list "$newrev")
    else
        commits=$(git rev-list "$oldrev..$newrev")
    fi

    for commit in $commits; do
        sig=$(git verify-commit "$commit" 2>&1)
        if [ $? -ne 0 ]; then
            echo "ERROR: Commit $commit is not signed."
            echo "Please sign your commits with: git commit -S"
            exit 1
        fi
    done
done

Team onboarding script

Create a script that sets up signing for new team members:

#!/bin/bash
# setup-signing.sh

echo "Setting up SSH commit signing..."

# Check Git version
GIT_VERSION=$(git --version | awk '{print $3}')
echo "Git version: $GIT_VERSION"

# Configure SSH signing
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

echo ""
echo "Done! Your commits will now be signed with your SSH key."
echo ""
echo "Next steps:"
echo "1. Go to https://github.com/settings/keys"
echo "2. Click 'New SSH key'"
echo "3. Set type to 'Signing Key'"
echo "4. Paste your public key:"
echo ""
cat ~/.ssh/id_ed25519.pub

Verifying Commits in CI

Add signature verification to your CI pipeline:

# GitHub Actions
- name: Verify commit signatures
  run: |
    git log --format='%H %G?' origin/main..HEAD | while read hash status; do
      if [ "$status" != "G" ] && [ "$status" != "E" ]; then
        echo "Unsigned or unverified commit: $hash"
        exit 1
      fi
    done

The %G? format shows signature status:

  • G — Good signature
  • B — Bad signature
  • U — Good signature with unknown validity
  • E — Signature cannot be checked (e.g., expired key)
  • N — No signature

Wrap-Up

Commit signing adds a layer of trust to your Git history. It proves that commits come from who they claim to come from, not just someone who configured the right name and email. SSH signing is the easiest path forward for most teams: reuse your existing SSH key, add a few Git config lines, and upload the key to GitHub as a signing key. Enable “Require signed commits” on your protected branches, and every commit in your main history is verified. Start with SSH signing today — it takes five minutes and the security benefit is immediate.