What you will achieve
You will configure Git to sign commits and tags using your existing SSH key, set up the allowed-signers file for verification, and integrate with GitHub or GitLab so signatures display as verified.
Why SSH signing
SSH signing landed in Git 2.34 (November 2021). It reuses the SSH key you already use for pushes - no separate GPG key management, no gpg-agent headaches. Modern Git hosts (GitHub, GitLab, Gitea, Forgejo) verify SSH signatures natively.
Step 1: confirm Git version
git --version
# git version 2.34.0 or later
Step 2: confirm an SSH key exists
ls -la ~/.ssh/id_ed25519*
# If missing:
ssh-keygen -t ed25519 -C "[email protected]"
Step 3: configure Git to sign with SSH
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub
git config --global commit.gpgsign true
git config --global tag.gpgsign true
Now every commit and tag is signed automatically.
Step 4: test
cd /tmp/sign-test
git init
echo "test" > file.txt
git add . && git commit -m "Test signed commit"
git log --show-signature -1
Output includes:
Good "git" signature with ED25519 key SHA256:...
Step 5: configure allowed signers
To verify others' signatures locally, build an allowed_signers file:
mkdir -p ~/.config/git
echo "[email protected] $(cat ~/.ssh/id_ed25519.pub)" \
>> ~/.config/git/allowed_signers
git config --global gpg.ssh.allowedSignersFile ~/.config/git/allowed_signers
Add lines for each collaborator whose signatures you want to verify:
[email protected] ssh-ed25519 AAAA...
[email protected] ssh-ed25519 AAAA...
Step 6: verify a commit
git verify-commit HEAD
git log --show-signature
git verify-tag v1.0.0
Step 7: upload key to your Git host
For GitHub: Settings → SSH and GPG keys → New SSH key → paste your public key, mark it as a "Signing key" (separate from authentication keys, optional but cleaner).
For GitLab: User Settings → SSH Keys → set the usage type to "Signing".
After upload, your commits show "Verified" badges in the host's UI.
Step 8: sign older commits
To retroactively sign an unsigned series:
# Sign the last commit
git commit --amend --no-edit -S
# Sign a range via rebase
git rebase -i <base> --exec 'git commit --amend --no-edit -S'
SHAs change; coordinate before pushing.
Step 9: enforce signed commits
On GitHub or GitLab, branch protection can require signed commits before merge. Combine with required reviews to raise the bar against credential theft.
gh api -X PUT repos/owner/repo/branches/main/protection \
-F required_signatures.enabled=true
Step 10: handle multiple machines
Each laptop typically has its own key. Three approaches:
- Per-machine key, all uploaded to your host as signing keys. Simplest.
- One master key, copied between machines. Convenient, riskier.
- Hardware key (YubiKey, Solo) used across machines. Highest security.
Hardware-backed signing
Generate an SSH key on a YubiKey:
ssh-keygen -t ed25519-sk -O resident -C "[email protected]"
Use the resulting ~/.ssh/id_ed25519_sk.pub as your signing key. Each signature requires a touch of the hardware - a meaningful security upgrade.
Step 11: signing tags
git tag -s v1.0.0 -m "Release 1.0.0"
git tag -v v1.0.0
Signed tags are the foundation of release attestation; tools like cosign and in-toto build supply chains atop them.
Troubleshooting
- "gpg: skipped: No secret key" - means GPG is still configured. Run
git config --global gpg.format ssh. - "Bad signature" on someone else's commit - their key is not in your
allowed_signers. - Host shows "Unverified" despite local verification - public key not uploaded as a signing key on the host.
The result
Every commit and tag you create is cryptographically tied to your SSH key. Your host displays the verified badge. Branch protection can require signatures, raising the cost of stolen credentials. The setup is one afternoon; the security benefit is permanent.