
September 11, 2026
12 min read
By Kokil Thapa | Last reviewed: September 2026
Unsigned Git commits are easy to forge. Anyone who knows your email can push code under your name without touching your account. When you sign your Git commits with GPG or SSH, Git attaches a cryptographic proof that only your private key could produce. Platforms like GitHub and GitLab display a green Verified badge. Supply-chain audits and CI pipelines can reject unsigned work before it merges. This guide walks through both methods on Linux and macOS, with copy-paste commands you can run today on a Ubuntu development or deploy server.
user.signingkey, enable commit.gpgsign true, and upload the public key to your Git host. SSH signing is simpler; GPG remains the default on older tooling.Why should you sign your Git commits with GPG or SSH?
Commit metadata is plain text. The author name and email in git log carry no built-in proof of identity. A disgruntled contractor, a compromised laptop, or a misconfigured CI job can all push commits that look like they came from you.
Signed commits change that. Git computes a hash of the commit object and encrypts it with your private key. Anyone with your public key can verify the signature locally or on the remote. On GitHub, verified commits show a green badge next to the SHA.
In my experience working on production Laravel applications, signed commits matter most on long-lived client repos. A law-firm portal or eCommerce codebase may pass through three developers over five years. Signing makes authorship auditable without trusting email headers alone.
Signed tags protect release points the same way. A tag signature proves the person who cut v2.4.1 actually controlled the signing key. That pairs well with conventional commits and semantic versioning workflows where automated changelogs depend on trustworthy history.
How do GPG and SSH commit signing compare?
Git supports two signing backends. GPG has been the standard for over a decade. SSH commit signing arrived in Git 2.34 and uses the same Ed25519 keys many developers already use for authentication.
For most new setups in 2026, SSH signing wins on simplicity. You skip keyservers, pinentry popups, and GPG agent quirks. GPG still makes sense when your organisation mandates OpenPGP or you need cross-tool signature compatibility.
| Criteria | GPG Signing | SSH Signing |
|---|---|---|
| Minimum Git version | Any recent Git | Git 2.34+ |
| Key generation | gpg --full-generate-key | ssh-keygen -t ed25519 |
| Agent overhead | GPG agent + pinentry | ssh-agent (often already running) |
| GitHub support | Upload GPG public key | Upload SSH key as signing key |
| CI/CD fit | Export secret key carefully | Same SSH key patterns as deploy keys |
| Best for | Legacy teams, OpenPGP policy | New projects, solo developers |
Both methods produce the same Verified badge on GitHub when configured correctly. Pick one per machine and stay consistent. Mixing GPG on your laptop and SSH on CI is fine as long as each identity maps to the right public key on the remote.
How do you sign Git commits with an SSH key?
SSH signing reuses the key format you already know from SSH key-only authentication. Git 2.34 introduced gpg.format ssh. The workflow below takes about five minutes on Ubuntu 22 or 24.
Generate a dedicated signing key
Do not reuse your authentication key for signing. Generate a separate Ed25519 key so a compromised CI token cannot both push and sign.
ssh-keygen -t ed25519 -C "signing@yourdomain.com" -f ~/.ssh/id_ed25519_signing
chmod 600 ~/.ssh/id_ed25519_signing
chmod 644 ~/.ssh/id_ed25519_signing.pub Add the private key to your agent. On Linux with systemd, ssh-add ~/.ssh/id_ed25519_signing persists until reboot unless you configure keychain integration.
Configure Git for SSH signing
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519_signing.pub
git config --global commit.gpgsign true
git config --global tag.gpgsign true The signing key must point to the .pub file path or the key fingerprint string. Git reads the public key to embed the signer identity in the signature block.
Upload the public key to GitHub
- Copy the public key:
cat ~/.ssh/id_ed25519_signing.pub - Open GitHub → Settings → SSH and GPG keys → New SSH key
- Select Signing Key as the key type
- Paste and save
GitLab follows the same pattern under User Settings → SSH Keys, checking the signing option. After upload, create a test commit and push. The commit page should show Verified within seconds.
Verify locally before pushing
git commit -S -m "test: verify SSH signing"
git log --show-signature -1 Good output includes Good "git" signature with your signing key fingerprint. Bad output usually means the public key is not on the remote or user.signingkey points to the wrong file.
How do you sign Git commits with GPG?
GPG signing remains the path for teams that already distribute public keys through keyservers or internal PKI. The official Git documentation on signing your work still centres on GPG as the default format.
Install GnuPG and generate a key
sudo apt update && sudo apt install -y gnupg
gpg --full-generate-key Choose RSA 4096 or Ed25519 if your GnuPG version supports it. Use the same email address as your GitHub account. Set an expiry date — one to two years is reasonable. You can extend it later with gpg --edit-key.
Export the key ID and configure Git
gpg --list-secret-keys --keyid-format=long
git config --global user.signingkey YOUR_LONG_KEY_ID
git config --global commit.gpgsign true
git config --global tag.gpgsign true Tell Git which program signs commits. This line prevents "gpg: signing failed" errors on headless servers:
git config --global gpg.program gpg Upload your GPG public key to GitHub
gpg --armor --export YOUR_LONG_KEY_ID Copy the armoured block including BEGIN PGP PUBLIC KEY BLOCK. Paste it under GitHub Settings → SSH and GPG keys → New GPG key. The email on the key must match a verified email on your account.
Fix common GPG agent problems on Linux
Headless SSH sessions often fail because pinentry cannot open a GUI. Add this to ~/.gnupg/gpg-agent.conf:
default-cache-ttl 34560000
max-cache-ttl 34560000 Reload the agent with gpgconf --kill gpg-agent. For WSL or remote tmux sessions, install pinentry-curses so the passphrase prompt renders in the terminal. I've encountered this during production deployments on shared EC2 boxes where Deployer runs Git operations over SSH.
How do you enforce signed commits in CI and on GitHub?
Local signing is voluntary until the remote rejects unsigned pushes. GitHub organisation owners can require signed commits under Rulesets or branch protection. GitLab offers a similar push rule.
GitHub branch protection
Navigate to Settings → Rules → Rulesets → New ruleset. Target your default branch. Enable Require signed commits. Unsigned pushes return a 403 with a clear error message.
Pair this with Git hooks that automate checks before commit and push. A local pre-commit hook cannot replace server-side enforcement, but it catches missing signatures before CI wastes a runner minute.
Verify signatures in a CI pipeline
GitLab CI and GitHub Actions can audit the last N commits on a merge request. A shell one-liner checks each commit in a range:
git merge-base origin/main HEAD | xargs -I{} git log {}..HEAD --pretty=format:"%H" | while read sha; do
git verify-commit "$sha" 2>&1 | grep -q "Good signature" || exit 1
done For SSH-signed commits, ensure the CI runner has the signer's public key in allowed_signers. Create ~/.ssh/allowed_signers:
signing@yourdomain.com ssh-ed25519 AAAAC3Nza... comment Then set:
git config gpg.ssh.allowedSignersFile ~/.ssh/allowed_signers This file format is documented in the GitHub commit signature verification guide. Without it, git verify-commit returns "No principal matched" even when GitHub shows Verified.
Signing commits from CI bots
Automated release bots need their own signing key stored as a CI secret. Never embed a human developer's private key in a pipeline variable. Generate a bot-specific SSH or GPG key, upload the public half to GitHub as a signing key, and restrict the private half to protected branch contexts.
On projects I've maintained with Deployer 7 and GitLab CI, bot-signed release tags integrate cleanly with semantic-release. The tag signature proves the pipeline — not a random shell session — cut the release. See also sign commits with GPG and SSH for supply chain trust for a broader security framing.
What are the common mistakes when you sign Git commits with GPG or SSH?
Most signing failures come from configuration drift, not cryptography. The list below covers the issues I see most often on client repos and sister-site deploy pipelines.
- Email mismatch. The GPG key email or SSH key comment must match a verified GitHub email. Unverified addresses produce Unverified commits even with a valid signature.
- Reusing auth keys for signing. Keep authentication and signing keys separate. Rotation becomes painful when one key serves both roles.
- Forgetting
commit.gpgsign true. Without global or per-repo config, you must pass-Son every commit. One forgotten flag ships an unsigned merge. - Missing allowed_signers for SSH. Local verification fails silently in scripts unless you maintain the allowed signers file.
- Expired GPG keys. Set a calendar reminder before expiry. An expired key still verifies old commits but cannot sign new ones.
- Rebase without re-signing. Interactive rebase rewrites commit hashes. Git re-signs automatically if signing is enabled, but old signatures on rewritten SHAs disappear from the log.
Store backup key material offline. Export GPG private keys with gpg --export-secret-keys --armor KEYID. For SSH, back up ~/.ssh/id_ed25519_signing to encrypted storage. A lost signing key does not lock you out of the repo, but your old commits may show as signed by an unknown key after account migration.
Use a strong passphrase on your signing key even in automated workflows where the CI secret store provides the second factor. Treat signing keys with the same care as production database credentials. Run periodic scans with secrets scanning in Git and CI with Gitleaks to catch accidental private-key commits.
For teams managing multiple deploy keys across servers, document which machine holds which signing identity. A shared dotfiles repo tracked in Git can version your global Git config while keeping private keys out of the tree.
Key Takeaways
- Enable
commit.gpgsign trueglobally so every commit is signed by default, not only when you remember-S. - Prefer SSH signing on Git 2.34+ for simpler agent management; use GPG when policy or tooling demands OpenPGP.
- Upload the public key to GitHub or GitLab as a signing key, not just an authentication key.
- Enforce signed commits with GitHub Rulesets or GitLab push rules — local hooks alone are not enough.
- Maintain
allowed_signersfor SSH if CI pipelines rungit verify-commit. - Back up private signing keys securely and rotate them on the same schedule as deploy credentials.
People Also Ask
Does GitHub show verified commits for both GPG and SSH?
Yes. GitHub verifies both GPG and SSH signatures since SSH signing support launched. The badge reads Verified when the signature matches a key registered to your account and the commit email is verified.
Can I sign commits in GitHub Codespaces or VS Code?
Yes. Configure signing in your dotfiles or Codespaces secrets the same way as a local machine. VS Code respects global Git config, so once commit.gpgsign is true, GUI commits are signed automatically.
Do signed commits slow down my workflow?
The overhead is negligible — typically under 200 milliseconds per commit when your agent caches the passphrase. SSH signing through an active ssh-agent adds virtually no perceptible delay during normal development.
What happens to signatures after a rebase or squash merge?
Rebase creates new commit objects with new hashes. Git re-signs them if signing is enabled. Squash merges produce one new signed commit; individual branch signatures do not carry into the squashed result.
Make every commit verifiable on your next project
When you sign your Git commits with GPG or SSH, you add a low-cost layer of supply-chain accountability that scales from solo freelance work to multi-developer agency teams. SSH signing on Git 2.34+ takes minutes to configure. GPG remains the right call when compliance requires it. Either way, turn on global signing, upload your public key, and enforce verification on the default branch.
If you want help hardening Git workflows, CI pipelines, or server access on a production codebase, review the Linux system administration service or browse the Adventure Third Pole Trek portfolio project where signed commits protect a live Laravel booking platform. For passphrase generation on new keys, use the password generator tool. Ready to audit your team's setup? Contact us and we can wire signing into your existing support and maintenance workflow.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

