← Back to DevBytes

Git Signing Commits with GPG

Understanding GPG and Commit Signing

Git commit signing uses GPG (GNU Privacy Guard) to cryptographically sign your commits, proving that you — and not someone impersonating you — authored the changes. When you sign a commit, Git attaches a digital signature that anyone can verify against your public key. This is distinct from the user.name and user.email configuration, which Git does not cryptographically verify and can be trivially spoofed.

What is GPG?

GPG stands for GNU Privacy Guard. It is an open-source implementation of the OpenPGP standard (RFC 4880), providing public-key cryptography for encryption and signing. In the context of Git, GPG is used exclusively for creating digital signatures that authenticate commits, tags, and, in newer versions, pushes. GPG relies on a keypair: a private key that you keep secret and use to sign, and a public key that you distribute so others can verify your signatures.

Why Signing Commits Matters

🚀 Deploy your AI agent in 10 minutes

Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.

Try it free →

Unsigned commits carry no cryptographic proof of origin. The author name and email displayed in git log are set by the committer's local configuration and are not validated by Git. An attacker who gains access to a repository or a developer's machine can craft commits that appear to come from anyone. Signed commits solve this by providing:

Prerequisites and Installation

Before you can sign commits, you need a GPG implementation installed on your system. Most modern operating systems ship with GPG, but you may need to install or upgrade it manually.

Installing GPG on Linux

# Debian / Ubuntu
sudo apt update && sudo apt install gnupg

# Fedora / RHEL
sudo dnf install gnupg2

# Arch Linux
sudo pacman -S gnupg

Installing GPG on macOS

# Using Homebrew
brew install gnupg

# Alternatively, download GPG Suite from https://gpgtools.org

Installing GPG on Windows

On Windows, the recommended approach is to install Gpg4win, which provides a complete GPG environment including Kleopatra, a graphical key management tool. Download it from https://www.gpg4win.org. Alternatively, if you use WSL2, install GPG inside your Linux distribution as shown above.

Generating a GPG Keypair

If you do not already have a GPG key, generate one with the following command. This interactive wizard will ask you several questions. For Git commit signing, the defaults are usually fine, but pay special attention to key type, size, and expiration.

gpg --full-generate-key

The wizard prompts you to choose:

Non-interactive key generation (advanced)

If you need to script key generation, use --batch mode. The example below creates an RSA 4096-bit signing-only key with a two-year expiration.

gpg --batch --generate-key <

After generation, GPG will print the key ID. Note this ID — you will need it to configure Git.

Listing your keys

To see all keys in your keyring, including the newly generated one:

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

Output looks like:

sec   rsa4096/AAAAAAAAAAAAAAAA 2025-01-01 [SC] [expires: 2027-01-01]
      BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
uid                 [ultimate] Jane Developer <jane@example.com>
ssb   rsa4096/CCCCCCCCCCCCCCCC 2025-01-01 [E] [expires: 2027-01-01]

The long key ID is the alphanumeric string after rsa4096/ on the sec line — here AAAAAAAAAAAAAAAA. You will use this ID to tell Git which key to sign with.

Configuring Git to Sign Commits

Once you have a GPG key, you must tell Git to use it. There are three configuration levels: per-repository, global (for your user account), or system-wide. Global configuration is the most common.

Set your signing key

git config --global user.signingkey AAAAAAAAAAAAAAAA

Replace AAAAAAAAAAAAAAAA with your actual long key ID. If you prefer to use the email address associated with the key, you can use that instead, but the key ID is unambiguous.

Enable commit signing by default

git config --global commit.gpgsign true

With this setting, every git commit will automatically attempt to sign. Without it, you must pass the -S flag to git commit each time.

Enable tag signing by default

git config --global tag.gpgsign true

Tags can also be signed. Signed tags are particularly important for releases, as they mark specific points in history as officially sanctioned.

Optional: Configure GPG program path

If Git cannot find GPG automatically, specify the path explicitly. This is common on Windows or when using GPG2.

# On most Linux/macOS systems with gpg2
git config --global gpg.program gpg2

# On Windows with Gpg4win, the path might be:
git config --global gpg.program "C:\\Program Files (x86)\\GnuPG\\bin\\gpg.exe"

# Verify the setting
git config --global --list | grep gpg

Signing Your First Commit

With signing enabled, creating a signed commit works just like any other commit. The -S flag is explicit signing; if you set commit.gpgsign true, you can omit -S.

# Create a change
echo "# My Project" > README.md
git add README.md

# Sign the commit (explicit -S flag)
git commit -S -m "Initial commit with signed signature"

# If commit.gpgsign is true, this also signs:
git commit -m "Another signed commit"

When you run the commit command, GPG may prompt you for your passphrase (unless you are using a GPG agent that caches it). Enter your passphrase to complete the signature.

Signing tags

# Create an annotated, signed tag
git tag -s v1.0.0 -m "Release version 1.0.0"

# Verify the tag signature
git tag -v v1.0.0

Verifying Signed Commits

Verification checks that the commit's signature is cryptographically valid and was made with a key that you trust. To verify a single commit:

git verify-commit HEAD

For signed tags:

git verify-tag v1.0.0

If the signature is valid and the key is trusted, you will see output like:

gpg: Signature made Mon 01 Jan 2025 12:00:00 PM UTC
gpg:                using RSA key AAAAAAAAAAAAAAAAA
gpg: Good signature from "Jane Developer <jane@example.com>" [ultimate]

If the signature cannot be verified because the public key is missing, Git warns:

gpg: Can't check signature: No public key

This means you need to import the signer's public key (covered in the next section).

Viewing signatures in git log

# Show signatures in log output
git log --show-signature

# Compact one-line view with signature verification status
git log --show-signature --oneline

The log will display verification status for each commit: a green checkmark or Good signature for valid signatures, and warnings for missing or invalid ones.

Exchanging Public Keys

For others to verify your signed commits, they must have your public key. Likewise, you need their public keys to verify their commits. Public keys are meant to be distributed widely — there is no security risk in sharing a public key.

Exporting your public key

# Export ASCII-armored public key
gpg --armor --export jane@example.com > jane-public-key.asc

# Export by key ID
gpg --armor --export AAAAAAAAAAAAAAAA > jane-public-key.asc

The --armor flag produces text output suitable for email, pastebins, and key servers. The resulting .asc file looks like a block of text starting with -----BEGIN PGP PUBLIC KEY BLOCK-----.

Distributing your public key

There are several ways to share your public key:

  • Key servers: Upload to a public keyserver like keys.openpgp.org or keyserver.ubuntu.com.
  • GitHub / GitLab: Add your public key in your account settings. Both platforms will then show a "Verified" badge on commits signed with that key.
  • Project repository: Some projects store public keys of maintainers inside the repository in a .gpg or keys/ directory.
  • Personal website: Publish your key and fingerprint on your personal site so people can fetch and verify it out of band.

Uploading to a keyserver

# Upload your public key to keys.openpgp.org
gpg --keyserver keys.openpgp.org --send-keys AAAAAAAAAAAAAAAA

# Upload to Ubuntu keyserver
gpg --keyserver keyserver.ubuntu.com --send-keys AAAAAAAAAAAAAAAA

Importing someone else's public key

To verify commits from another developer, import their public key into your keyring and assign trust.

# Import from a keyserver
gpg --keyserver keys.openpgp.org --recv-keys THEIR_KEY_ID

# Import from a local .asc file
gpg --import their-public-key.asc

# List keys to confirm import
gpg --list-keys --keyid-format LONG

Signing and trusting keys

Importing a key does not automatically trust it. GPG uses a trust model where you explicitly decide how much you trust a key. To mark a key as trusted for signature verification:

# Edit the key to assign trust
gpg --edit-key THEIR_KEY_ID
# At the gpg> prompt, type:
gpg> trust
# Choose trust level (1-5). For full trust, choose 5.
gpg> 5
gpg> save

Alternatively, you can sign their key with your own key to certify that you have verified their identity. This builds a web of trust.

gpg --sign-key THEIR_KEY_ID

Integrating with GitHub and GitLab

Both GitHub and GitLab recognize GPG-signed commits and display verification badges in the UI. This adds a visual indicator of trustworthiness.

GitHub setup

  1. Export your public key as described above.
  2. Go to Settings → SSH and GPG keys in your GitHub profile.
  3. Click New GPG key and paste the entire ASCII-armored block, including the BEGIN and END lines.
  4. Ensure the email address on the GPG key matches your verified GitHub email. If they differ, GitHub will show "Unverified" even if the signature is cryptographically valid.

GitLab setup

  1. Export your public key.
  2. Navigate to Preferences → GPG Keys (/-/profile/gpg_keys) in your GitLab profile.
  3. Paste the ASCII-armored key and save.
  4. Like GitHub, GitLab requires the email on the key to match a verified email on your account.

Configuring Git for verified emails

To ensure the email in your commits matches the email in your GPG key and your platform account:

git config --global user.email "jane@example.com"

This email must exactly match what is in your GPG key UID and what is verified on GitHub/GitLab.

Handling Passphrases and GPG Agents

Typing a passphrase for every commit quickly becomes tedious. GPG agents cache passphrases in memory for a configurable duration, so you only need to enter it periodically.

Configuring the GPG agent cache

Edit ~/.gnupg/gpg-agent.conf to adjust cache settings:

# Cache passphrases for 8 hours (28800 seconds)
default-cache-ttl 28800

# Maximum cache lifetime (even if used) — 24 hours
max-cache-ttl 86400

# Use the pinentry program appropriate for your environment
pinentry-program /usr/bin/pinentry-gtk-2  # GUI
# or
pinentry-program /usr/bin/pinentry-tty    # Terminal

After editing, reload the agent:

gpg-connect-agent reloadagent /bye

macOS Keychain integration

On macOS, you can store your GPG passphrase in the system keychain. Install pinentry-mac and configure:

brew install pinentry-mac
echo "pinentry-program /usr/local/bin/pinentry-mac" >> ~/.gnupg/gpg-agent.conf
gpg-connect-agent reloadagent /bye

Signing Existing Commits (Rewriting History)

If you have a branch with unsigned commits and want to sign them retroactively, you can use interactive rebase. Warning: This rewrites history and changes commit SHAs. Do not do this on branches shared with others unless you coordinate the change.

# Sign the last 5 commits
git rebase --signoff -i HEAD~5
# In the editor, change 'pick' to 'edit' for commits you want to sign,
# then for each commit run:
git commit --amend -S --no-edit
git rebase --continue

A more automated approach uses --exec:

# Re-sign the last N commits using exec
git rebase --exec 'git commit --amend -S --no-edit' -i HEAD~5

Best Practices for Commit Signing

  • Use a dedicated signing subkey: Generate a primary key for certification and a separate subkey for daily signing. This limits exposure and allows you to revoke the subkey independently if compromised.
  • Protect your private key: Use a strong passphrase, store the key on an encrypted drive, and never share it. Consider hardware tokens like YubiKey for storing GPG keys securely.
  • Set an expiration date: Keys without expiration cannot be revoked cleanly if lost. An expiration date forces renewal, which is a good periodic security check.
  • Publish your public key broadly: Upload to keyservers and add it to your GitHub/GitLab profile. The more places your public key lives, the harder it is for an attacker to substitute a fake one without detection.
  • Match your email exactly: The email in your GPG key UID must match your git config user.email exactly. Inconsistencies cause verification failures or "Unverified" badges on platforms.
  • Sign tags for releases: Tags mark release points. A signed tag is a strong assertion that a particular commit represents an official release.
  • Enable signing by default: Set commit.gpgsign true globally so you never accidentally push unsigned commits.
  • Verify signatures before merging: Integrate signature verification into CI/CD pipelines or pre-merge checks to block unsigned commits from entering protected branches.
  • Revoke compromised keys immediately: If your private key is exposed, generate a revocation certificate ahead of time and store it securely offline, or revoke the key immediately and distribute the revocation.

Creating a revocation certificate (proactive)

# Generate a revocation certificate and store it securely offline
gpg --armor --gen-revoke AAAAAAAAAAAAAAAA > revocation-certificate.asc

Store revocation-certificate.asc in a safe, offline location (e.g., encrypted USB drive, printout in a safe). If your key is compromised, import and publish this certificate to revoke the key.

Using hardware tokens (YubiKey example)

Hardware tokens store your GPG private key on a physical device, making it impossible for malware to steal the key. To move your GPG key to a YubiKey:

# Install required tools
sudo apt install pcscd scdaemon

# Move the key to the YubiKey
gpg --edit-key AAAAAAAAAAAAAAAA
gpg> keytocard
# Follow prompts to select the signing slot
gpg> save

Once moved, the private key lives on the YubiKey and GPG operations require physical presence (touching the YubiKey). This is the gold standard for commit signing security.

Enforcing Signed Commits in Teams

For organizations and open-source projects, you can enforce that all commits pushed to certain branches are signed. This is typically done server-side on platforms like GitHub and GitLab, or via pre-receive hooks on self-hosted Git servers.

GitHub branch protection

In your repository's Settings → Branches → Branch protection rules, enable Require signed commits. This blocks any push containing unsigned commits to the protected branch.

GitLab push rules

In GitLab, navigate to Settings → Repository → Push Rules and enable Require commits to be signed. This is available in GitLab Premium and Ultimate tiers, or at the project level for all tiers via the API.

Custom pre-receive hook

On a self-hosted Git server, you can write a pre-receive hook that inspects incoming commits and rejects unsigned ones. A basic example:

#!/bin/bash
# pre-receive hook to reject unsigned commits
while read oldrev newrev refname; do
  # Get all commits in the push
  for commit in $(git rev-list "$oldrev".."$newrev"); do
    # Check signature
    sig_status=$(git verify-commit "$commit" 2>&1)
    if ! echo "$sig_status" | grep -q "Good signature"; then
      echo "Commit $commit is not signed or has an invalid signature. Push rejected."
      exit 1
    fi
  done
done
exit 0

Deploy this script as hooks/pre-receive in your bare server repository.

Troubleshooting Common Issues

Git cannot find GPG

Error: gpg: gpg failed to sign the data or error: gpg failed to sign the data. This often means Git cannot locate the GPG binary or the GPG agent is not running.

# Verify GPG is installed and in PATH
which gpg
gpg --version

# Set explicit GPG path in Git
git config --global gpg.program /usr/bin/gpg

# Ensure GPG agent is running
gpg-agent --daemon

No secret key found

Error: gpg: signing failed: No secret key. This means the key ID you configured does not exist or is not a private key in your keyring.

# List your secret keys to verify the ID
gpg --list-secret-keys --keyid-format LONG

# Ensure user.signingkey matches exactly

Inappropriate pinentry or no passphrase prompt

If you are never prompted for a passphrase, the pinentry program may be misconfigured. Check ~/.gnupg/gpg-agent.conf and ensure the pinentry-program setting points to a valid binary. On headless servers, use pinentry-tty.

Platform shows "Unverified" despite valid signature

This almost always means the email in your GPG key UID does not match the verified email on your GitHub/GitLab account. Add the matching email to your key:

gpg --edit-key AAAAAAAAAAAAAAAA
gpg> adduid
# Enter the exact email from your platform account
gpg> save

Then update the key on the platform and re-upload to keyservers.

Conclusion

Signing commits with GPG transforms Git's trust model from implicit to explicit, cryptographically binding your identity to every commit you create. It is a foundational practice in modern software supply chain security, required by frameworks like SLSA and increasingly demanded by open-source communities and enterprises alike. The workflow — generate a key, configure Git, sign commits, and distribute your public key — is straightforward to set up and quickly becomes second nature. By combining signed commits with hardware-backed keys, branch protection rules, and CI/CD verification, you build a robust chain of trust that protects your codebase from impersonation and tampering. Start signing your commits today — the cost is minimal, and the security benefit is substantial.

🚀 Need a reliable AI agent for your project?

Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.

Get Started — $23.99/mo
← Back to all articles