Fix 'gpg failed to sign the data'
git commit fails with 'error: gpg failed to sign the data' — how do I fix commit signing?
If you’re seeing this error
error: gpg failed to sign the datafatal: failed to write commit objectgpg: signing failed: Inappropriate ioctl for devicegpg: signing failed: No secret keyYou’re in the right place — the fix is below.
Short answer
Test the signing outside Git first: echo test | gpg --clearsign. 'Inappropriate ioctl for device' means the passphrase prompt has nowhere to draw — add export GPG_TTY=$(tty) to your shell profile. 'No secret key' means user.signingkey does not match a key you hold. If you just want to commit right now, git commit --no-gpg-sign gets you through.
export GPG_TTY=$(tty)
echo "test" | gpg --clearsign # does signing work at all?
git config --global user.signingkey <KEYID>
The three checks that resolve nearly every signing failure
Does this match your situation?
- 'error: gpg failed to sign the data' followed by 'fatal: failed to write commit object'.
- 'gpg: signing failed: Inappropriate ioctl for device'
- 'gpg: signing failed: No secret key'
- Signing works in the terminal but fails from VS Code, an IDE or a cron job.
- It worked for months and stopped — often exactly a year after you made the key.
Step-by-step fix
Test GPG on its own
Git adds nothing to the signing process — it shells out to gpg and reports whatever comes back. Testing directly tells you in one line whether this is a Git problem or a GPG problem, and it almost never is a Git problem.
step 1echo "test" | gpg --clearsign gpg --list-secret-keys --keyid-format=longNo output from --list-secret-keys means you have no private key on this machine — that is the whole bug.
'Inappropriate ioctl for device': give the prompt a terminal
GPG needs to ask for your passphrase, and pinentry has to know which terminal to draw on. Without GPG_TTY it has nowhere to write the prompt and fails immediately. This is the single most common cause on Linux and macOS.
step 2export GPG_TTY=$(tty) echo 'export GPG_TTY=$(tty)' >> ~/.bashrc # or ~/.zshrc gpgconf --kill gpg-agent # restart the agent'No secret key': point Git at a key you actually hold
user.signingkey must name a key in your own keyring. Copying a key ID from an old dotfile, or setting it globally and then generating a different key, produces this exactly. Take the long ID from the sec line.
step 3gpg --list-secret-keys --keyid-format=long # sec ed25519/3AA5C34371567BD2 -> the ID is 3AA5C34371567BD2 git config --global user.signingkey 3AA5C34371567BD2 git config --global commit.gpgsign trueThe email on the key must match user.email, or hosts will show the commit as unverified even once signing succeeds.
Check whether the key simply expired
GPG keys carry an expiry date and one or two years is a common default, which is why signing stops working out of nowhere long after you set it up. Extending the expiry keeps the same key and the same fingerprint.
step 4gpg --list-keys --keyid-format=long # look for [expired] gpg --edit-key <KEYID> # then: expire -> choose a period -> saveAfter extending it, export the public key again and re-upload it to GitHub or GitLab so the new expiry is recorded there too.
Tell Git which gpg binary to use
Several systems ship both gpg and gpg2, and package managers move them around. If the wrong one is on Git's path it may have no access to your keyring at all.
step 5which gpg gpg2 git config --global gpg.program "$(which gpg)"Show the Windows / PowerShell version
Windows / PowerShell(Get-Command gpg).Source git config --global gpg.program "C:/Program Files (x86)/GnuPG/bin/gpg.exe"Or switch to SSH signing and skip GPG entirely
Git 2.34 can sign with the SSH key you already use to push. There is no keyring, no agent and no expiry, and GitHub and GitLab both verify it. For most people this is the better answer than debugging GPG.
step 6git config --global gpg.format ssh git config --global user.signingkey ~/.ssh/id_ed25519.pub git config --global commit.gpgsign trueUpload the same public key to your host a second time, as a signing key rather than an authentication key.
Commit now, fix it afterwards
Signing is a policy, not a requirement. If you are mid-task, turn it off for one commit or for this repository and come back to it.
step 7git commit --no-gpg-sign -m "Fix login redirect" git config commit.gpgsign false # this repository only
Why this works
Git does not implement signing. commit.gpgsign tells it to hand the commit object to an external program — gpg, or an SSH signer since 2.34 — and store the returned signature in the commit header. Every part of this failure therefore happens outside Git: the agent that holds your key, the pinentry program that asks for the passphrase, the terminal it needs to draw that prompt on, and the expiry date recorded in the key itself. Git only sees a non-zero exit code and refuses to write a commit it cannot sign, which is why 'gpg failed to sign the data' is followed by 'failed to write commit object'. Reproducing it with echo test | gpg --clearsign takes Git out of the picture in one line, and that is nearly always where the answer is.
If that didn’t work
- Run GIT_TRACE=1 git commit -m test to see the exact gpg invocation Git is making.
- In an IDE or a headless environment, set pinentry-mode loopback in ~/.gnupg/gpg.conf so no GUI prompt is needed.
- On macOS, brew reinstall --cask gpg-suite repairs a pinentry that stopped working after an OS update.
- In CI, import the key with --batch and use a passphrase-free subkey rather than an interactive agent.
How to stop it happening again
- Put export GPG_TTY=$(tty) in your shell profile the day you set signing up.
- Set a calendar reminder before your key expires, or create it with no expiry and rely on revocation.
- Keep user.email and the key's UID identical, or hosts will not mark commits verified.
- Consider SSH signing for new setups — one key, no agent, nothing to expire.
Commands used in this guide
git configRead and write Git settings for one repo, your user, or the whole machine.
git commitRecord everything currently staged as a permanent snapshot.
git tagMark a specific commit as a release.
git logBrowse commit history with as much or as little detail as you want.
git commit --amendReplace the most recent commit instead of stacking another one on top.
Still stuck?
Search the full command reference and every other rescue guide — there are 76 of them, covering everything from detached HEAD to force-push disasters.
Browse all guides →Frequently asked questions
What causes 'error: gpg failed to sign the data'?
GPG returned a non-zero exit code, so Git refused to write the commit. The usual causes are a missing GPG_TTY so the passphrase prompt has no terminal, a user.signingkey that names a key you do not hold, an expired key, or Git calling the wrong gpg binary.
What does 'Inappropriate ioctl for device' mean when signing?
Pinentry could not find a terminal to display the passphrase prompt on. Add export GPG_TTY=$(tty) to your shell profile and restart the agent with gpgconf --kill gpg-agent. It is the most common cause of this error on Linux and macOS.
How do I commit without signing, just this once?
git commit --no-gpg-sign -m "message". To disable it for one repository, run git config commit.gpgsign false — your global setting is untouched, so other projects keep signing.
Why did commit signing stop working after a year?
Your GPG key almost certainly expired — one or two years is a common default. Run gpg --list-keys and look for [expired], then extend it with gpg --edit-key <id> and the expire command. Re-upload the public key afterwards so your host records the new date.
Can I sign commits without GPG?
Yes. Git 2.34 and later can sign with an SSH key: git config --global gpg.format ssh and point user.signingkey at your public key file. There is no keyring, no agent and no expiry, and both GitHub and GitLab verify SSH-signed commits.
Related rescue guides
git commit opened a text editor I can't get out of — how do I save and exit?
You are in Vim. Press Esc, type :wq and press Enter to save and finish the commit, or :q! to abandon it. For a log or diff that has taken over the terminal you are in the pager instead — press q. Set git config --global core.editor "code --wait" (or nano) so it never happens again.
Read the fix →Always fixableMy commits show the wrong name or email — how do I fix them?
Set the correct identity with git config, then fix the last commit with git commit --amend --reset-author. For a range of commits use git rebase with an exec step, or git filter-repo --mailmap to rewrite the whole history at once.
Read the fix →Always fixablegit clone or push fails with Permission denied (publickey) — how do I fix my SSH key?
The server didn't accept any key your SSH client offered. Either you have no key, the key isn't loaded into the agent, or its public half was never added to your account. Run ssh -T git@github.com to see exactly which keys are being tried.
Read the fix →Always fixableMy pre-commit hook (or Husky) isn't running — why does Git ignore it?
Nine times out of ten the hook file is not executable, is misnamed, or sits in a folder Git isn't reading. Run git config core.hooksPath to see where Git actually looks, check the file is named exactly pre-commit with no extension, then chmod +x it. A stray global core.hooksPath silently overrides every repository on the machine — including Husky's.
Read the fix →