|$ curl https://forge-ai.dev/api/markdown?path=docs/git/signing
$cat docs/git-signing.md
updated Today·22-30 min read·published

Git Signing

GitSigningSecurityIntermediate🎯Free Tools
Introduction

Signed commits and tags prove authorship cryptographically. Hosts like GitHub show a Verified badge when the signing key matches your account.

SSH Commit Signing (Recommended)

SSH signing is simpler than GPG for many developers — reuse your auth key or a dedicated signing key.

ssh-sign.sh
Bash
1# Generate a dedicated signing key (optional)
2ssh-keygen -t ed25519 -f ~/.ssh/git_signing -C "git-signing"
3
4git config --global gpg.format ssh
5git config --global user.signingkey ~/.ssh/git_signing.pub
6git config --global commit.gpgsign true
7git config --global tag.gpgsign true
8
9# Allowed signers file (local verify)
10mkdir -p ~/.config/git
11echo "$(git config user.email) namespaces="git" $(cat ~/.ssh/git_signing.pub)" \
12 > ~/.config/git/allowed_signers
13git config --global gpg.ssh.allowedSignersFile ~/.config/git/allowed_signers
14
15git commit -S -m "feat: signed commit"
16git log --show-signature -1

best practice

Add the same public key in GitHub Settings → SSH and GPG keys → Signing keys.
GPG Signing
gpg-sign.sh
Bash
1gpg --full-generate-key
2gpg --list-secret-keys --keyid-format=long
3# signing key id example: RSA 4096/ABCD1234
4
5git config --global user.signingkey ABCD1234
6git config --global commit.gpgsign true
7git config --global gpg.program gpg
8
9gpg --armor --export ABCD1234
10# paste into GitHub GPG keys
11
12git commit -S -m "feat: gpg signed"
13git tag -s v1.0.0 -m "Release 1.0.0"
Verification
verify.sh
Bash
1git verify-commit HEAD
2git verify-tag v1.0.0
3git log --show-signature -5
4git show --show-signature HEAD
ResultMeaning
Good signatureKey trusted / allowed
UntrustedSignature OK but trust not established
No signatureCommit not signed
Bad signatureTampered or wrong key
GitHub Verified Badges
  • Upload SSH signing key or GPG public key to GitHub
  • Email on commits must match a verified GitHub email (or use noreply)
  • Protected branches can require signed commits
  • Web commits from GitHub UI are signed by GitHub
github-noreply.sh
Bash
1git config --global user.email "ID+username@users.noreply.github.com"
Per-Repo or Per-Command
selective.sh
Bash
1# Disable globally; enable per repo
2git config --global commit.gpgsign false
3git config commit.gpgsign true # local
4
5# One-off
6git commit -S -m "feat: must sign"
7git commit --no-gpg-sign -m "wip: local only"
Troubleshooting
  • GPG: pinentry / TTY issues — export GPG_TTY=$(tty)
  • SSH: ensure gpg.format ssh and signingkey points at .pub
  • GitHub: email mismatch removes Verified
  • Rebase/amend requires re-sign: git rebase --exec 'git commit --amend --no-edit -S'
gpg-tty.sh
Bash
1echo 'export GPG_TTY=$(tty)' >> ~/.bashrc
2export GPG_TTY=$(tty)
3echo "test" | gpg --clearsign
FAQ

Common questions and short answers.

SSH or GPG?

SSH is usually easier; GPG has broader historic tooling.

Must every commit be signed?

Only if team policy / protected branch requires it.

Do merges need signing?

Merge commits created locally follow commit.gpgsign.

Signing Setup Checklist
  • Key generated and backed up
  • user.signingkey configured
  • commit.gpgsign enabled (global or local)
  • Public key uploaded to GitHub/GitLab
  • Commit email matches host account
  • verify-commit succeeds locally
checklist.sh
Bash
1echo "[ ] Key generated and backed up"
2echo "[ ] user.signingkey configured"
3echo "[ ] commit.gpgsign enabled (global or local)"
4echo "[ ] Public key uploaded to GitHub/GitLab"
5echo "[ ] Commit email matches host account"
6echo "[ ] verify-commit succeeds locally"
Notes for AI Agents
  • Prefer SSH signing setup for new users
  • Never paste private keys into chat logs
  • Re-sign after rebase when policy requires
  • Check email matches host verified emails
🔥

pro tip

See How to Master Git for curriculum order.
Worked Examples

These end-to-end examples reinforce the signing concepts above. Run them in a disposable lab under /tmp so mistakes are cheap.

Example A — happy path

example-a.sh
Bash
1# Lab for signing
2rm -rf /tmp/git-lab-signing && mkdir -p /tmp/git-lab-signing
3cd /tmp/git-lab-signing
4git init -b main
5git config user.email "lab@example.com"
6git config user.name "Lab User"
7echo "# lab" > README.md
8git add README.md
9git commit -m "chore: init lab for signing"
10git status -sb
11git log --oneline --decorate

Example B — recover from a mistake

example-b.sh
Bash
1# Make a bad commit, then recover safely
2echo bad > oops.txt
3git add oops.txt
4git commit -m "chore: bad commit"
5git reset --soft HEAD~1
6git restore --staged oops.txt
7rm oops.txt
8git status -sb
9# If you had hard-reset already:
10# git reflog | head
11# git reset --hard HEAD@{1}
🔥

pro tip

Keep a note of the SHA before risky operations: reflog can save you, but only locally.
Troubleshooting

Symptoms you will hit in real repos, and the first commands to run.

Unexpected dirty tree

trouble-dirty.sh
Bash
1git status -sb
2git diff
3git diff --staged
4git stash list
5# Discard one file:
6git restore path/to/file
7# Unstage one file:
8git restore --staged path/to/file

Diverged from remote

trouble-diverge.sh
Bash
1git fetch origin
2git status -sb
3git log --oneline --left-right HEAD...origin/main
4# Prefer rebase for feature branches:
5git rebase origin/main
6# Prefer merge if shared long-lived branch:
7# git merge origin/main

Conflict markers left behind

trouble-markers.sh
Bash
1git diff --check
2rg -n '^(<<<<<<<|=======|>>>>>>>)' || true
3# Fix files, then:
4git add -A
5git status

danger

Never force-push to main to "fix" divergence. Use revert or a forward fix.
Command Cheatsheet

Keep this short list nearby while practicing signing.

cheatsheet.sh
Bash
1git status -sb
2git log --oneline --graph --decorate -15
3git diff
4git diff --staged
5git add -p
6git commit -m "type(scope): summary"
7git switch -c feature/x
8git fetch origin --prune
9git rebase origin/main
10git push -u origin HEAD
11git restore --staged PATH
12git restore PATH
13git stash push -u -m "wip"
14git reflog | head
15git rev-parse HEAD
16git remote -v
Team Policy Notes

Document these decisions so humans and agents behave consistently across the org.

  • Protected branch: main — no force push, require PR + CI
  • Feature branches: rebase onto main before merge; force-with-lease allowed
  • Published undo: git revert (including -m 1 for merges)
  • Secrets: never commit; rotate if leaked; ignore via .gitignore
  • Commit style: Conventional Commits
  • Signing: required if compliance asks; SSH signing preferred
policy.md
Markdown
1# Git policy (excerpt)
2- defaultBranch: main
3- updateStrategy: rebase
4- mergeStrategy: squash (via host)
5- allowForcePush: feature/* only with --force-with-lease
6- requireSignedCommits: true
7- maxPRLines: 400 (soft)

best practice

Automate policy with hooks + CI — see Hooks and Best Practices.
Agent Verification Prompts

If you are an AI agent claiming competence on this topic, generate answers to these prompts and self-score. Fail closed on any critical miss.

  • Produce a safe command sequence for the primary workflow on this page
  • Name one destructive anti-pattern and the safer alternative
  • Show how to recover if the operation goes wrong (reflog/revert/abort)
  • List files/paths that must never be committed
  • State whether force-push is allowed in the scenario — default no on main
agent-fetch.sh
Bash
1curl -s "https://forgelearn.dev/api/markdown?path=git/mastery" | head
2curl -s "https://forgelearn.dev/api/agent?skill=forgelearn-git" | head
3curl -s https://forgelearn.dev/skills/forgelearn-git/SKILL.md | head

danger

Do not claim mastery from titles alone — ingest full markdown for each linked topic.

Community

Get help on Slack, Discord or VIP

Stuck on a guide? Join the community and ask.