Skip to content
ExponaBLOG
GIT & GITHUB FOR PROFESSIONAL DEVELOPERS · DAY 049 / 100

Signing commits: Git & GitHub best practices explained

Prove who wrote a commit.

Expona Admin7 min read

Signing commits: Git & GitHub best practices explained

When you push code to a shared repository, how does your team know the commit actually came from you and hasn't been tampered with? Without cryptographic signing, anyone with access to your Git configuration could impersonate you—a risk that grows as projects scale and compliance requirements tighten. In this article, we'll walk through the practical side of commit signing, from choosing between SSH and GPG to enforcing signatures on protected branches, so your team can maintain trust and auditability in your codebase.

Understanding why commit signing matters

Git commits contain author metadata—name and email—but that's just a text field. Someone with local repository access can change it with git config user.name and commit as someone else. For small teams, this might feel like overkill, but in regulated industries like fintech, healthcare, or SaaS with strict audit trails, unsigned commits are a liability. Signing commits cryptographically proves that you—and only you—created and approved that specific commit at that specific time.

GitHub shows a green "Verified" badge next to signed commits, giving your team immediate visual feedback. At scale, this becomes a security control: you can require all commits on critical branches to be signed, making it impossible to merge unverified code.

SSH or GPG signing: choosing the right approach

Git supports two main signing mechanisms: GPG (GNU Privacy Guard) and SSH keys. Both work, but the decision depends on your team's existing infrastructure and comfort level.

GPG signing

GPG is the traditional choice. You generate a GPG key pair, register the public key with GitHub, and Git signs every commit with the private key. The advantage is that GPG is battle-tested and separates your commit-signing key from your Git authentication key. The downside is setup friction: GPG requires additional tools and configuration, especially on Windows, and team adoption often stumbles at the installation step.

To generate a GPG key:

bash
gpg --full-generate-key

Choose RSA 4096-bit, set an expiration (we recommend 2–3 years), and enter your name and email. Then export your public key:

bash
gpg --armor --export YOUR_KEY_ID

Copy the output and add it to GitHub under Settings → SSH and GPG keys → New GPG key.

SSH signing

GitHub introduced SSH commit signing in 2023 as a simpler alternative. Instead of a separate GPG key, you sign commits with your existing SSH key—the same one you use for git push. This dramatically lowers friction: if you're already using SSH authentication, signing is just a configuration change. The trade-off is that your single SSH key now serves dual purposes (authentication and signing), which some teams view as less separable of concerns.

To use SSH signing, tell Git which SSH key to use:

bash
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519

If you're using a different key path or passphrase, adjust accordingly. The key must already be uploaded to GitHub.

Practical comparison

Aspect GPG SSH
Setup complexity Higher (separate tool, key management) Lower (reuses existing SSH key)
Team adoption ease Slower, especially on Windows Faster, especially if SSH already in use
Key separation Better (auth and sign keys separate) Simpler (single key for both)
GitHub integration Full support, verified badge Full support, verified badge

For most teams, SSH signing is the pragmatic choice because it removes friction without sacrificing security guarantees. Start there; graduate to GPG if your compliance framework demands strict key separation.

Enabling commit signing with git config commit.gpgsign true

Once you've chosen GPG or SSH and set up your key, the next step is automation. Manually signing every commit with -S flag gets old fast:

bash
git commit -S -m "Fix authentication bug"

Instead, configure Git to sign all commits by default:

bash
git config --global commit.gpgsign true

This global flag tells Git to sign every commit automatically. If you want to scope it to a single repository (e.g., for a high-security project), use without the --global flag inside that repo:

bash
cd /path/to/repo
git config commit.gpgsign true

Verify your configuration:

bash
git config --list | grep gpgsign

From now on, every git commit will be signed automatically. You'll be prompted for your GPG or SSH passphrase the first time in each session (or not at all if you've configured an SSH agent).

One caveat: some CI/CD systems or pre-commit hooks may struggle with unsigned commits if signing is enforced downstream. Test this in your actual workflow before rolling out globally. We've seen teams enable commit.gpgsign true and then get blocked by their build pipeline because the CI environment wasn't set up to sign. Better to test on a feature branch first.

Verified badges on GitHub

Once you've signed commits, GitHub automatically verifies them against your registered key. Every signed commit displays a green "Verified" badge next to the author name in the commit history. This visual signal serves two purposes:

  1. Immediate trust feedback: Your team can scan a branch and see at a glance which commits are cryptographically verified.
  2. Audit trail: In pull requests and commit logs, the badges document that these changes came from verified authors.

You'll see the badge in several places: the commit detail page, blame view, and pull request list. If a commit is unsigned or signed with an unregistered key, no badge appears—which should raise questions.

To confirm your keys are properly registered:

  1. Go to your GitHub profile Settings → SSH and GPG keys.
  2. Verify both your SSH key (if using SSH signing) and/or your GPG key are listed.
  3. Push a signed commit and refresh the repository page—you should see the "Verified" badge.

If badges aren't appearing, the most common culprit is a mismatch between your local Git config (user.email) and the email registered on GitHub. Git uses the email in the commit to match it against your registered keys. Ensure they align:

bash
git config user.email
# Should match your primary GitHub email

Requiring signed commits on protected branches

Signing is voluntary by default—a team can adopt it gradually. But for critical branches (main, production, release), you want to enforce it. GitHub's branch protection rules allow you to require signed commits before merging pull requests.

Setting up the requirement

  1. Go to your repository Settings → Branches.
  2. Click Add rule and select the branch to protect (e.g., main).
  3. Under "Require status checks to pass before merging," check Require branches to be up to date before merging.
  4. Scroll down and check Require commits to be signed.
  5. Click Create or Save changes.

Now, any pull request containing unsigned commits cannot be merged, even if all other checks pass. The UI clearly shows which commits lack signatures, and developers must sign them locally before pushing.

Handling edge cases

Once you enforce signing on a branch, you'll encounter edge cases:

  • Merge commits from GitHub UI: GitHub-generated merge commits (e.g., via the web UI) are automatically signed by GitHub's own key and count as verified.
  • Rebase and force-push: If a developer rebases and force-pushes, all rebased commits must be re-signed locally. This is intentional—it forces deliberate action.
  • Squash merges: When you squash commits in a PR, the new commit created by GitHub is automatically signed if you're merging to a branch that requires signatures.
  • Hotfixes and emergencies: There's no "bypass" for unsigned commits. If you truly need to merge an unsigned commit, temporarily disable the requirement, merge, and re-enable it. This should be rare and audited.

In a recent e-commerce project, we required signed commits on main but not on feature branches. This balanced security with developer experience: engineers could work freely locally, but any merge to production had to pass the signature gate. After two weeks, adoption was nearly 100% because the friction was isolated to one workflow step.

Common mistakes

Forgetting to register your key with GitHub. You sign commits locally, but if your public key isn't registered in GitHub, the platform has no way to verify them. Always double-check Settings after setting up signing.

Mismatched email addresses. Your Git config email must match the email on your GitHub account. If you commit with user.email = dev@company.com but GitHub knows you as dev@personal.com, verification fails. Run git config user.email to verify.

Enabling signing globally without testing locally. Not all CI/CD systems are set up to sign commits. If your continuous integration pushes commits (e.g., dependency updates), and signing is required, the pipeline breaks. Always test on a branch before enforcing globally.

Losing your SSH/GPG passphrase. If you forget the passphrase, you can't use the key anymore. Keep passphrases in a password manager. If using SSH agents, configure them to cache credentials so you're not re-entering passphrases constantly.

Mixing SSH and GPG configuration. If you set gpg.format ssh but then point user.signingkey to a GPG key file, Git gets confused. Pick one approach and stick with it—or document clearly if you're using different keys for different repositories.

Key takeaways

  • Commit signing proves authorship and prevents impersonation; it's especially critical in regulated industries and large teams.
  • SSH signing is simpler to adopt than GPG for most teams because it reuses existing infrastructure; GPG offers better key separation if your compliance framework demands it.
  • Configure git config --global commit.gpgsign true to sign all commits automatically after setting up your key.
  • GitHub displays green "Verified" badges on signed commits; ensure your local Git email matches your registered GitHub email for verification to work.
  • Use GitHub branch protection rules to require signed commits on critical branches, enforcing a security gate for production merges.
  • Test signing in your actual CI/CD and build pipelines before rolling it out globally; some systems need configuration to handle signing correctly.
  • Adoption friction is real; if possible, require signing only on main/production branches and let feature branches be unsigned during the transition period.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles