SSH Authentication with Yubikey
The GPG SSH section earlier in this series covered using your GPG authentication subkey for SSH connections via the GPG agent. This page covers what changes when that authentication subkey is stored on a Yubikey rather than on disk.
The short version: almost nothing changes from the user’s perspective. SSH still connects the same way. The GPG agent still handles the authentication handshake. The difference is that the agent now delegates the actual cryptographic operation to the Yubikey rather than performing it in software. The private key never touches RAM. Every SSH connection requires the physical card.
Prerequisites
The following must be in place before continuing:
- GPG configured with SSH support enabled in
~/.gnupg/gpg-agent.conf SSH_AUTH_SOCKpointing to the GPG agent socket in~/.bashrc- The authentication subkey moved to the Yubikey as covered in the previous section
scdaemonconfigured and communicating with the card
Verify everything is in order with the Yubikey inserted:
gpg --card-status | grep "Authentication key"
This should show the fingerprint of your authentication subkey rather than [not set].
Verify the SSH agent sees the key:
ssh-add -L
This should output your SSH public key in OpenSSH format. If it returns empty, the agent is not seeing the card. Check that scdaemon is running and the card is detected:
gpg --card-status
If gpg --card-status works but ssh-add -L is empty, the sshcontrol file may need updating. Find the keygrip of the authentication subkey:
gpg --list-keys --with-keygrip $GPGKEY
Find the [A] subkey and add its keygrip to the sshcontrol file:
echo "<keygrip> 0" >> ~/.gnupg/sshcontrol
Reload the agent:
gpgconf --reload gpg-agent
Then check ssh-add -L again.
Distributing the public key to servers
The public key exposed by ssh-add -L is what goes in authorized_keys on remote servers. Copy it using either method:
# Method 1: pipe directly
ssh-add -L | ssh user@server "cat >> ~/.ssh/authorized_keys"
# Method 2: export from GPG and use ssh-copy-id
gpg --export-ssh-key $GPGKEY > /tmp/yubikey_auth.pub
ssh-copy-id -f -i /tmp/yubikey_auth.pub user@server
rm /tmp/yubikey_auth.pub
Verify it is in place:
ssh user@server "cat ~/.ssh/authorized_keys"
Connecting
With the public key on the server and the Yubikey inserted:
ssh user@server
What happens next depends on whether touch confirmation is enabled on the card:
Without touch confirmation: The GPG agent prompts for the card PIN via the Qt pinentry dialog. Enter it and the connection proceeds.
With touch confirmation: The GPG agent prompts for the PIN. After entering it, the Yubikey blinks. Touch it. The connection proceeds. If touch confirmation is enabled and you do not touch the key within the timeout period, the connection fails. Touch it promptly when it blinks.
On subsequent connections within the same session, if the PIN is cached by the agent (controlled by default-cache-ttl in gpg-agent.conf), only the touch is required rather than the PIN again.
Verifying the connection uses the card
To confirm SSH is authenticating via the Yubikey rather than a key file:
ssh -v user@server 2>&1 | grep "Offering"
The output should reference the key fingerprint matching your authentication subkey. You can also watch the Yubikey blink when the connection is made, which is the most direct confirmation that the card is being used.
What happens when the Yubikey is not inserted
Remove the Yubikey and attempt an SSH connection to a server where only the card public key is in authorized_keys:
ssh user@server
The connection will fail. ssh-add -L returns empty when the card is not present. The GPG agent has a stub entry for the authentication subkey but cannot perform operations without the physical card. This is the expected behaviour and the point of the whole exercise.
If you have also added a separate software SSH key (from ~/.ssh/id_ed25519) to the server’s authorized_keys, that key will still work without the Yubikey. Whether to maintain a software fallback key alongside the card key is a decision that depends on your threat model and how critical the access is.
SSH config for Yubikey-only servers
For servers where you want to explicitly use only the GPG agent key and not fall back to key files in ~/.ssh/:
Host server.yourdomain.net
IdentityFile /dev/null
IdentitiesOnly yes
Setting IdentityFile /dev/null and IdentitiesOnly yes tells SSH to use only what the agent offers, ignoring any key files. This is useful for servers where the Yubikey is the only intended authentication method.
Using the key on a different machine
Because the authentication subkey is on the card, you can use it from any machine that has GPG and scdaemon installed and configured. Insert the Yubikey, import your public key on the new machine, update the sshcontrol file with the keygrip, and the card works for SSH authentication immediately.
On a new machine, the quickest setup is:
# Install prerequisites
sudo apt install scdaemon pcscd
# Fetch your public key
gpg --fetch-key https://keys.openpgp.org/vks/v1/by-fingerprint/<your-fingerprint>
# Or fetch directly from the card
gpg --edit-card
gpg/card> fetch
gpg/card> quit
# Set ultimate trust
gpg --edit-key $GPGKEY
gpg> trust
# Choose 5
gpg> quit
# Add the keygrip to sshcontrol
gpg --list-keys --with-keygrip $GPGKEY
echo "<auth-subkey-keygrip> 0" >> ~/.gnupg/sshcontrol
# Point SSH at the GPG agent
export SSH_AUTH_SOCK="$(gpgconf --list-dirs agent-ssh-socket)"
# Verify
ssh-add -L
The public key shown by ssh-add -L will be the same key fingerprint as on the original machine, because the key is on the card rather than tied to the machine.
Agent forwarding with the Yubikey
GPG agent forwarding, as covered in the GPG Remote section, works identically with the Yubikey. The forwarded agent socket allows remote servers to request signing operations, which are handled by the local GPG agent, which delegates to the Yubikey. The card never leaves your desk. The private key never crosses the network.
The touch requirement, if enabled, means you need to physically touch the key even for forwarded operations. If you are running a command on a remote server that triggers an SSH authentication via the forwarded agent, the Yubikey on your local machine will blink and require a touch. This is the correct behaviour.
Troubleshooting
ssh-add -L returns empty with card inserted:
gpgconf --kill scdaemon
gpg --card-status # Forces scdaemon restart
ssh-add -L
Connection fails with “sign_and_send_pubkey: signing failed”: The card may have lost its connection. Remove and reinsert the Yubikey, then retry.
PIN prompt does not appear:
Check that pinentry-qt is installed and set in gpg-agent.conf. Reload the agent after making changes.
Touch prompt blinks but connection times out: The touch timeout on the card may be shorter than expected. Touch it promptly when it blinks. The default timeout is around 15 seconds.
The Yubikey blink is not optional when touch confirmation is enabled. If you walk away mid-connection and miss it, the authentication fails. Build the habit of watching for the blink when connecting to servers.