Forwarding your GPG agent over SSH

If you keep your GPG keys on a smartcard (like a Yubikey as I do), you can use them on a remote host over SSH without ever copying a private key off the card. The signing and decryption happen on your local computer, driven by the remote; and the keys never leave the hardware. This is GPG agent forwarding, and it’s very useful for signing commits or decrypting files on a remote computer.

The problem is that almost every guide, including GnuPG’s own example tells you to forward the agent’s restricted (called extra) socket. On recent GnuPG (2.4.x) that silently breaks signing and decryption. Here’s the setup that actually works.

Forwarding locally

gpg is just a front-end; the secret-key operations live in gpg-agent, which talks to your card over a Unix socket. Forward your local agent’s socket onto the remote and the remote’s gpg talks to your agent (and your card). If configured, the PIN prompt (and any touch) happen locally.

Add this to your ~/.ssh/config:

Host devbox
  # Host <yourhost>
  RemoteForward /run/user/<your remote UID>/gnupg/S.gpg-agent /run/user/<your local UID>/gnupg/S.gpg-agent

The order is RemoteForward <remote-socket> <local-socket>. Find the exact paths with gpgconf --list-dirs agent-socket on each machine – it’s usually /run/user/<uid>/gnupg/..., and mind you that the uid can differ between hosts.

Use the full socket, not the “extra” one

This is the part people get wrong. gpg-agent listens on two sockets:

The problem is that recent gpg discovers which secret keys exist by asking the agent HAVEKEY --list, and the restricted socket answers Forbidden. gpg has no per-key fallback, so it decides there’s no secret key and refuses to work:

gpg: skipped "0xDEADBEEF": No secret key
gpg: signing failed: No secret key

To make it more confusing, encryption still works — that’s a public-key operation that never touches the agent. Only signing and decryption break. The fix is to forward the full S.gpg-agent, exactly as shown above, and not S.gpg-agent.extra. There’s a real trade-off to this; see the security note at the end.

On the remote:

1. Let sshd replace a stale socket

By default sshd refuses to bind the forwarded socket if a file already exists at that path. Allow it to clean up first, via /etc/ssh/sshd_config (or better, just add it to a file under /etc/ssh/sshd_config.d/):

StreamLocalBindUnlink yes

Then systemctl reload sshd.

2. Import your public key

Forwarding carries only the private side. The remote’s gpg still needs your public key to know what it’s signing with:

gpg --export <your-key-id> | ssh <yourhost> gpg --import

Don’t bother testing with gpg --card-status — just sign or decrypt something (echo test | gpg --clearsign). Your YubiKey should blink at home and the PIN prompt should appear on your desktop.

3. Make the socket directory exist at boot

The first time you connect after the remote reboots, you may hit this:

Warning: remote port forwarding failed for listen path /run/user/<UID>/gnupg/S.gpg-agent

sshd binds the forwarded socket during session setup, but /run/user/<uid>/gnupg doesn’t exist yet — nothing has created it, and sshd won’t mkdir a forward’s parent. Normally that directory is created lazily by the first gpg command, which is too late for this first connection.

Fix it with a user-level tmpfiles rule on the remote. Create ~/.config/user-tmpfiles.d/gnupg.conf with:

d %t/gnupg 0700 - - -

%t expands to /run/user/<uid>.

That’s it.

A security note

The full socket gives the remote host unrestricted access to your entire agent for as long as you’re connected — every key it holds, not just the card — including key-management commands. And by default GnuPG caches your card PIN for ten minutes, so during that window anything running as you on the remote can sign or decrypt with no prompt at all. You’re better be doing this to hosts you trust.

One thing worth doing to increase the security is adding a touch requirement to the card, if available. On a YubiKey, gpg --card-editadminuif 1 on (signing) and uif 2 on (decryption) makes every operation need a physical tap - so even with the full socket and a cached PIN, a compromised remote can’t use your keys without you. Use on, not permanent.

With touch enabled, the trade-off becomes pretty comfortable: your keys stay on the card, they work on the remote, and nothing happens without your finger on the YubiKey.