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:
- Authenticity: Recipients can verify that a commit was truly created by the holder of the corresponding private key.
- Integrity: Any tampering with the commit after signing invalidates the signature, making manipulation detectable.
- Trust: Open-source projects and enterprises can build trust chains around known public keys, ensuring only authorized developers push code.
- Compliance: Many regulated industries and supply-chain security frameworks (like SLSA) require signed commits as part of a secure software development lifecycle.
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:
- Key kind: Choose
(1) RSA and RSA (default)or(4) RSA (sign only)if you only need signing. For broader use, the default RSA with encryption capability is fine. - Key size: 4096 bits is the current recommended minimum for long-term use. Type
4096when prompted. - Expiration: Set an expiration date (e.g.,
2yfor two years). You can extend the expiration later, and an expiration date limits damage if a key is lost and not revoked. - Real name: Use your full name as it will appear in your commits.
- Email address: Use the exact email address you use in your
git config user.email. You can add multiple email addresses later. - Passphrase: Choose a strong passphrase. This protects your private key if it is ever stolen.
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.orgorkeyserver.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
.gpgorkeys/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
- Export your public key as described above.
- Go to Settings → SSH and GPG keys in your GitHub profile.
- Click New GPG key and paste the entire ASCII-armored block, including the BEGIN and END lines.
- 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
- Export your public key.
- Navigate to Preferences → GPG Keys (
/-/profile/gpg_keys) in your GitLab profile. - Paste the ASCII-armored key and save.
- 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.emailexactly. 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 trueglobally 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.