Skip to content

SSH Authentication with Ledger

Use your Ledger hardware wallet for secure SSH authentication.



SSH authentication with Ledger provides hardware-backed security for SSH connections:

  • Private key on device - Key never leaves Ledger
  • Physical confirmation - Button press required for each auth
  • Multiple agents - Two options: GPG agent or ledger-agent
  • Platform support - Works with GitHub, GitLab, servers

┌─────────────────────────────────────────────────────────────┐
│ SSH Authentication Flow │
├─────────────────────────────────────────────────────────────┤
│ │
│ ssh github.com │
│ │ │
│ ▼ │
│ SSH Client (looks for key) │
│ │ │
│ ▼ │
│ SSH Agent (ledger-agent or gpg-agent) │
│ │ │
│ ▼ │
│ Ledger Device │
│ │ │
│ ▼ │
│ [User presses button to confirm] │
│ │ │
│ ▼ │
│ Signature returned → SSH connection established │
│ │
└─────────────────────────────────────────────────────────────┘

Ledger supports:

  • ECDSA (NIST P-256) - Default, widely supported
  • Ed25519 - Modern, faster (via ledger-agent)
  • NIST P-256 with SSH - Via GPG agent

  • Ledger Nano S device
  • SSH/GPG Agent app installed on Ledger
  • ledger-agent package installed (included in hardware-security profile)
  • Device initialized and unlocked

See Ledger Setup Guide for initial setup.


Your configuration uses GPG agent with SSH support enabled:

Already configured in nix/profiles/hardware-security.nix:

services.gpg-agent = {
enable = true;
enableSshSupport = true;
pinentry.package = pkgs.pinentry_mac;
};
home.sessionVariables = {
SSH_AUTH_SOCK = "$(gpgconf --list-dirs agent-ssh-socket)";
};

Verify it’s working:

/Users/you/.gnupg-ledger/S.gpg-agent.ssh
# Check SSH_AUTH_SOCK is set
echo $SSH_AUTH_SOCK
# List SSH keys from agent
ssh-add -L

Alternative method using ledger-agent directly:

Start the agent:

Terminal window
# Start in background
ledger-agent -d ssh://ledger@localhost &
# Set SSH auth socket
export SSH_AUTH_SOCK="${HOME}/.ledger-agent/ssh-agent.sock"

Or use launchd service (already configured):

The hardware-security profile includes:

launchd.agents.ledger-ssh-agent = {
enable = true;
config = {
ProgramArguments = [
"${pkgs.ledger-agent}/bin/ledger-agent"
"-d"
"ssh://ledger@localhost"
];
RunAtLoad = true;
};
};

Terminal window
# Using GPG agent (Option 1)
ssh-add -L
# Using ledger-agent (Option 2)
ledger-agent ssh://ledger@localhost &
ssh-add -L

Output example:

ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTY... ledger
Terminal window
# Copy to clipboard (macOS)
ssh-add -L | pbcopy
# Save to file
ssh-add -L > ~/.ssh/ledger_ssh.pub
Terminal window
# Test GitHub
ssh -T git@github.com
# Ledger will display "SSH Auth" - press button to confirm
# Expected output:
# Hi username! You've successfully authenticated...
Terminal window
# Connect to server
ssh user@server.com
# Ledger prompts for confirmation
# Press button to authenticate

  1. Copy your public key:

    Terminal window
    ssh-add -L | pbcopy
  2. Add to GitHub:

    • Go to Settings → SSH and GPG keys
    • Click “New SSH key”
    • Title: Ledger SSH Key
    • Key: Paste your public key
    • Click “Add SSH key”
  3. Verify:

    Terminal window
    ssh -T git@github.com
  1. Copy public key (same as above)
  2. Go to Preferences → SSH Keys
  3. Paste key and save
Terminal window
# Copy public key to server
ssh-copy-id -i <(ssh-add -L) user@server.com
# Or manually append to authorized_keys
ssh-add -L | ssh user@server.com 'cat >> ~/.ssh/authorized_keys'
Terminal window
# SSH with verbose output
ssh -v user@server.com
# Look for:
# debug1: Offering public key: ecdsa-sha2-nistp256 ... ledger
# debug1: Server accepts key: ...
# [Ledger prompts for confirmation]

Problem: ssh-add -L shows “Could not open a connection to your authentication agent”

Solution:

Terminal window
# For GPG agent
export SSH_AUTH_SOCK=$(gpgconf --list-dirs agent-ssh-socket)
# For ledger-agent
export SSH_AUTH_SOCK="${HOME}/.ledger-agent/ssh-agent.sock"
# Add to ~/.zshrc to persist
echo 'export SSH_AUTH_SOCK=$(gpgconf --list-dirs agent-ssh-socket)' >> ~/.zshrc

Problem: ssh-add -L shows “The agent has no identities”

Solution:

Terminal window
# Ensure Ledger is:
# 1. Connected via USB
# 2. Unlocked (PIN entered)
# 3. SSH/GPG Agent app is open
# 4. Screen shows "ready"
# Restart agent
killall gpg-agent ledger-agent
gpgconf --launch gpg-agent
# Or start ledger-agent
ledger-agent -d ssh://ledger@localhost &
# Check again
ssh-add -L

Problem: SSH hangs waiting for Ledger

Checklist:

  • Ledger connected and unlocked
  • SSH/GPG Agent app is open on device
  • Device screen shows “SSH/GPG Agent is ready”
  • Agent process running: pgrep -f ledger

Test:

Terminal window
# Check agent status
pgrep -f "ledger-agent\|gpg-agent"
# Check logs
tail -f ~/.local/share/ledger-ssh-agent.log
tail -f ~/.local/share/gpg-agent.log

Problem: Server rejects Ledger key

Solution:

  1. Verify key is on server:

    Terminal window
    ssh user@server.com 'cat ~/.ssh/authorized_keys' | grep "$(ssh-add -L | cut -d' ' -f2)"
  2. Check server logs:

    Terminal window
    ssh user@server.com 'sudo tail /var/log/auth.log'
  3. Try with verbose output:

    Terminal window
    ssh -vvv user@server.com

Problem: Multiple SSH agents interfering

Solution:

Terminal window
# Kill all agents
killall ssh-agent gpg-agent ledger-agent
# Start only one
gpgconf --launch gpg-agent
# Verify
echo $SSH_AUTH_SOCK
ssh-add -L

List all available SSH keys:

Terminal window
# From agent
ssh-add -L
# Specify key explicitly
ssh -i <(ssh-add -L | head -1 | awk '{print $1" "$2}') user@server.com

Configure per-host settings in ~/.ssh/config:

# Use Ledger for GitHub
Host github.com
IdentityAgent /Users/you/.gnupg-ledger/S.gpg-agent.ssh
IdentitiesOnly yes
# Use Ledger for work servers
Host *.company.com
IdentityAgent /Users/you/.gnupg-ledger/S.gpg-agent.ssh
User your-username

ledger-agent supports different derivation paths:

Terminal window
# Default path
ledger-agent ssh://ledger@localhost
# Custom path
ledger-agent ssh://ledger@localhost/0h/1h/2h
# Ed25519 (if supported)
ledger-agent ssh://ledger@localhost --curve ed25519

Forward Ledger agent to remote machine:

Terminal window
# Enable agent forwarding
ssh -A user@server.com
# On remote machine, SSH will use your Ledger
ssh git@github.com

Security Note: Only forward to trusted machines.

Configure Git to always use SSH:

Terminal window
# Set SSH for Git globally
git config --global url."ssh://git@github.com/".insteadOf "https://github.com/"
# Clone repos using SSH
git clone git@github.com:user/repo.git

What Ledger SSH protects against:

  • ✅ Key theft from computer
  • ✅ Malware extracting SSH keys
  • ✅ Unauthorized SSH access
  • ✅ Key exfiltration via network

What it doesn’t protect against:

  • ❌ Malware intercepting SSH session after auth
  • ❌ Compromised remote server
  • ❌ Physical theft of Ledger (PIN protects)
  • ❌ Social engineering attacks
  1. Physical Security

    • Remove Ledger when not in use
    • Lock screen when away
  2. Key Management

    • Use separate keys for different purposes
    • Rotate keys periodically
    • Remove old keys from servers
  3. Agent Security

    • Don’t forward agent to untrusted machines
    • Use IdentitiesOnly yes in SSH config
    • Monitor agent logs for unusual activity
  4. Backup

    • Keep 24-word recovery phrase secure
    • Test recovery process periodically
    • Consider backup Ledger device

If Ledger is lost or damaged:

  1. Restore from seed:

    Terminal window
    # On new Ledger with same seed
    ledger-agent ssh://ledger@localhost
    ssh-add -L # Same key will be generated
  2. Or use backup key:

    • Keep traditional SSH key as backup
    • Store securely (encrypted)
    • Only use when Ledger unavailable



Terminal window
# Get SSH public key
ssh-add -L
# Test GitHub
ssh -T git@github.com
# Test server
ssh user@server.com
# Check agent
echo $SSH_AUTH_SOCK
# Restart agent (GPG)
killall gpg-agent && gpgconf --launch gpg-agent
# Start ledger-agent
ledger-agent -d ssh://ledger@localhost &
# View logs
tail -f ~/.local/share/ledger-ssh-agent.log
Terminal window
# Add to ~/.zshrc
# For GPG agent
export SSH_AUTH_SOCK=$(gpgconf --list-dirs agent-ssh-socket)
# For ledger-agent
export SSH_AUTH_SOCK="${HOME}/.ledger-agent/ssh-agent.sock"

Happy secure SSH! 🔐

See GPG Signing for commit signing with Ledger.