skip to content

How do you configure Git to sign every commit and tag automatically?

level: middleimportance: must knowfreq 42%

answer

  1. One setting names the key
  2. Two booleans turn it on by default
  3. A fourth setting chooses the backend
  4. Creating differs from verifying
  5. Lightweight tags have no object to sign

basics

~20 s

In Git, set user.signingKey to your key, commit.gpgSign and tag.gpgSign to true, and gpg.format to ssh if you are signing with an SSH key rather than the default OpenPGP. Individual commands can still opt out with --no-gpg-sign.

solid answer

~40 s

Three settings do it. `user.signingKey` names the key: an OpenPGP key id, or with `gpg.format = ssh`, the path to an SSH public key. `commit.gpgSign = true` makes `git commit` sign without `-S`, and `tag.gpgSign = true` does the same for annotated tags. Without those booleans you sign per invocation with `git commit -S` and `git tag -s`. If you are signing with SSH you also want `gpg.ssh.allowedSignersFile` pointing at a list of trusted principals, otherwise you can create signatures but not verify anyone's — including your own. Escape hatches exist: `--no-gpg-sign` on a single command, and `gpg.program` if the signing binary is not on the default path. Expect to run an agent so you are not retyping a passphrase on every commit.

code

ini · 10 lines
ini
[user]
    signingKey = ~/.ssh/id_ed25519.pub
[gpg]
    format = ssh
[gpg "ssh"]
    allowedSignersFile = ~/.config/git/allowed_signers
[commit]
    gpgSign = true
[tag]
    gpgSign = true

go deeper

for a junior

Recall the three settings by name — user.signingKey, commit.gpgSign, tag.gpgSign — and the per-command -S and git tag -s equivalents.

for a middle

Explain that the signature lives inside the commit or tag object, that gpg.format picks the backend, and that verification needs a trust list that signing does not.

for a senior

Cover the operational edges: agents and passphrase prompts during rebases, keys that expire mid-sprint, --no-gpg-sign escape hatches, and why signing config cannot be shipped inside the repository.

for a principal

Own the identity question — which key represents which principal, how it reaches every developer and build machine, and what the team does when that key must change.

## What signing attaches to Git can sign two kinds of object. A **signed commit** carries the signature in a header of the commit object itself, alongside the tree, parents, author, committer and message. A **signed tag** is an annotated tag object whose payload is signed the same way. Both are ordinary Git objects, so the signature is content-addressed along with everything else: change any byte and you have a different object and an invalid signature. Lightweight tags cannot be signed at all — they are just a ref pointing at a commit, with no object of their own to hold a signature. That is why release processes that care about provenance use annotated, signed tags. ## The configuration **`user.signingKey`** identifies which key to use. With the default OpenPGP backend it is a key id or fingerprint. With SSH signing it is the path to a public key file (for example `~/.ssh/id_ed25519.pub`), or a literal key given in the `key::ssh-ed25519 AAAA...` form. **`gpg.format`** selects the backend: `openpgp` (the default), `ssh`, or `x509`. SSH signing is the low-friction option for teams that already have SSH keys, and it needs a version of `ssh-keygen` that supports the `-Y sign` and `-Y verify` subcommands. **`commit.gpgSign`** set to true signs every commit `git commit` creates, so you stop typing `-S`. **`tag.gpgSign`** set to true does the same for annotated tags. Note the asymmetry in the porcelain: `git tag -a` creates an annotated tag *without* a signature, `git tag -s` creates a signed one, and `git tag -u <keyid>` signs with a specific key. **`gpg.program`** points at the signing binary if the default is wrong for your machine. **`gpg.ssh.allowedSignersFile`** is not needed to *create* SSH signatures but is required to *verify* them; without it, verification reports that it has no way to check the key. ## Doing it per command Everything above has a per-invocation equivalent: `git commit -S`, `git commit -S<keyid>`, `git tag -s`, `git tag -u <keyid>`. Going the other way, `--no-gpg-sign` disables signing for one command even when the config says to sign — useful for a throwaway commit, or for a scripted operation running where no key is available. The same options exist on the commands that create commits indirectly: `git merge -S`, `git cherry-pick -S`, `git rebase -S`. That matters because a rebase can create many commits at once, which with signing enabled means many signing operations — the standard advice is to run an agent that caches the passphrase, or the operation becomes an interrogation. ## Where to put the settings Signing identity is usually a per-machine, per-identity concern rather than a per-repository one, so the settings normally live in the user's global config. Work and personal identities that use different keys are the classic reason people split their config per directory. What you cannot do is ship signing configuration inside the repository for others to inherit — `.git/config` is not cloned, and a key path is meaningless on someone else's machine. ## Verifying that it took effect After configuring, make a commit and check it: `git log --show-signature -1` prints the verification result, and `git cat-file -p HEAD` shows the raw commit object with its signature header, which is the most convincing demonstration that the signature is part of the object rather than metadata bolted on beside it. `git log --pretty="%h %G? %GS"` gives a compact one-line-per-commit view of signature status and signer across a range. ## Common failure modes A signing key that has expired, a passphrase prompt that cannot reach a terminal (the classic failure inside an editor or a script), a `user.email` that does not match any identity on the key, or `gpg.format = ssh` with `user.signingKey` pointing at a private key path when the public key is expected. Each produces a commit that fails to be created rather than one that is silently unsigned, which is the behaviour you want. ## Interview framing Name the three settings precisely, distinguish creating signatures from verifying them (the allowed-signers file is only needed for the second), mention that lightweight tags cannot be signed, and note the agent/passphrase practicality that anyone who has actually turned this on has hit.

  • Why can a lightweight tag not be signed?
    A lightweight tag is only a ref file containing an object id — there is no tag object to hold a signature. Signing requires an annotated tag, created with `git tag -s` (or `-a` plus `tag.gpgSign`), which produces a real tag object carrying the tagger, message and signature.
  • What extra setup does gpg.format=ssh need compared with OpenPGP signing?
    Creating signatures needs only a key and an `ssh-keygen` that supports `-Y sign`. Verification additionally needs `gpg.ssh.allowedSignersFile` — a file mapping principals to public keys with the `git` namespace — because SSH has no keyserver or web of trust to consult, so the trust list has to be supplied explicitly.
  • You enabled commit.gpgSign and now a long rebase asks for your passphrase repeatedly. What do you do?
    Run an agent that caches the passphrase for the session, since each replayed commit is a separate signing operation. If a particular batch genuinely should not be signed, `--no-gpg-sign` disables it for that invocation, and you can re-sign the final result afterwards.

saying these in an interview costs you the question

  • Claims signing is enabled by a repository file everyone clones
  • Thinks git tag -a produces a signed tag
  • Believes an allowed-signers file is needed to create signatures
  • Confuses the signing key with the SSH key used to push
  • Says commits are signed automatically once a key exists

context