Why Your 1Password SSH Keys Are Being Ignored: Stop Key Spraying, Start Pinning
I spent longer than I’d like to admit debugging this. Everything looked right. Keys
were in the vault. The agent was running. ssh myserver returned Permission denied (publickey). Classic.
Added -v. The agent was returning 12 keys. SSH was collecting them and then just…
not using any of them.
debug1: get_agent_identities: agent returned 12 keys
debug1: Will attempt key: /Users/you/.ssh/id_rsa
debug1: Will attempt key: /Users/you/.ssh/id_ecdsa
debug1: Trying private key: /Users/you/.ssh/id_rsa
debug1: Trying private key: /Users/you/.ssh/id_ecdsa
debug1: No more authentication methods to try.
Permission denied (publickey).
Fetched 12 keys, offered none to the server. Weird yet interesting.
This article explains exactly why SSH ignores your vault keys, why the naive fix leads to a second failure, and the correct pattern: per-host key pinning, where SSH offers exactly one key per server with the private key never leaving 1Password.
The Real Problem: SSH Is Ignoring Your Agent Keys Entirely
The standard advice when setting up 1Password SSH agent is to add IdentitiesOnly yes:
Host myserver
HostName 1.2.3.4
User root
IdentityAgent "~/Library/Group Containers/2BUA8C4S2C.com.1password/t/agent.sock"
IdentitiesOnly yes
Makes sense on paper — restrict SSH to only use keys you’ve explicitly configured. But read what the man page actually says:
Specifies that ssh should only use the configured authentication identity and certificate files (either the default files, or those explicitly configured in the ssh_config files or passed on the command-line), even if ssh-agent or a PKCS11Provider or SecurityKeyProvider offers more identities.
Configured identity files. That means IdentityFile directives — or, absent those,
the default paths on disk: ~/.ssh/id_rsa, ~/.ssh/id_ecdsa, etc.
If your private keys live exclusively in 1Password — as they should — none of those
files exist on disk. So IdentitiesOnly yes with no IdentityFile directive tells
SSH: offer nothing. The agent is called, dutifully returns all 12 keys, and SSH
discards every single one because there is no IdentityFile to match against.
Twelve keys fetched. Zero offered. Authentication fails. The verbose output makes it look like the agent is working fine. It is. It’s SSH that’s throwing the keys away.
Removing IdentitiesOnly Just Moves the Problem
The obvious fix is to remove IdentitiesOnly yes. Now SSH doesn’t filter anything,
so it offers all 12 agent keys to the server. Except:
debug1: Offering public key: prod-db-1 ED25519 SHA256:mK7... agent
debug1: Authentications that can continue: publickey
debug1: Offering public key: dev-laptop ED25519 SHA256:pQ2... agent
debug1: Authentications that can continue: publickey
debug1: Offering public key: staging-server ED25519 SHA256:rT9... agent
Received disconnect from 1.2.3.4 port 22:2: Too many authentication failures
My correct key was 11th in the list. The server’s MaxAuthTries was 6. Disconnected
before SSH even got to it.
This is key spraying — SSH blindly throwing every key in your vault at every server
in vault order. A realistic vault has 10–15 keys across production, staging, cloud
providers, personal machines. OpenSSH’s default MaxAuthTries is 6. You can see
where this goes.
Key spraying has two compounding problems beyond just failing:
-
Non-deterministic. Whether it succeeds depends entirely on which position your correct key happens to be in the agent’s return order — which can change as you add or reorganise keys in your vault.
-
Audit noise. Every rejected key offer is a logged failed authentication attempt on the target server. You are generating false-positive noise in your own security logs — and if you connect to servers that don’t know you at all, in someone else’s.
Removing IdentitiesOnly yes traded one failure mode for another.
The Actual Fix: Per-Host Key Pinning
The root cause of both failures is the same: SSH doesn’t know which key to use for which server. In the first case it guesses wrong (nothing). In the second it tries everything. The fix is to tell it explicitly.
OpenSSH has a lesser-known feature that makes this work cleanly with 1Password:
IdentityFile accepts a .pub file. When it points to a public key, SSH doesn’t
load a private key from disk — it uses the public key’s fingerprint to locate the
matching private key in the agent. The agent handles the signing. The private key
never leaves 1Password.
Step 1: Export the correct public key from your 1Password agent.
# List all keys with their full public key material
SSH_AUTH_SOCK="~/Library/Group Containers/2BUA8C4S2C.com.1password/t/agent.sock" \
ssh-add -L
# Save the one for this server
echo "ssh-ed25519 AAAA...your-key... myserver" > ~/.ssh/myserver.pub
chmod 644 ~/.ssh/myserver.pub
Step 2: Pin it in ~/.ssh/config.
Host myserver
HostName 1.2.3.4
User root
IdentityAgent "~/Library/Group Containers/2BUA8C4S2C.com.1password/t/agent.sock"
IdentityFile ~/.ssh/myserver.pub
IdentitiesOnly yes
Now the flow is deterministic:
SSH reads ~/.ssh/myserver.pub
→ fingerprint matches the correct key in 1Password agent
→ 1Password signs the server's challenge
→ server verifies signature against authorized_keys
→ authenticated ✓
One key offered. One round-trip. No private key on disk. No spraying. No
MaxAuthTries problem regardless of how large your vault grows.
Why This Is Safe
The .pub file on disk is just a lookup hint. It’s a public key — it’s already
public by definition. There’s nothing sensitive in it.
The private key stays in 1Password and performs the signing operation inside 1Password’s own process. It never passes through SSH process memory. It never touches disk. The only thing that crosses between 1Password and SSH is a signature.
Think of it the same way you’d think about a YubiKey — the private key lives inside the hardware, the challenge goes in, a signature comes out. 1Password is just a software version of that.
The actual threat surface here is your 1Password vault credentials — not the .pub
file sitting in ~/.ssh. Protect the vault with a strong master password and a
hardware 2FA key and you’re in good shape.
Scale This Across Your Infrastructure
Once you have the pattern down, roll it out consistently:
# Export once per key
ssh-add -L | grep "production" > ~/.ssh/production.pub
ssh-add -L | grep "staging" > ~/.ssh/staging.pub
ssh-add -L | grep "db-servers" > ~/.ssh/db.pub
Host prod-*
IdentityAgent "~/Library/Group Containers/2BUA8C4S2C.com.1password/t/agent.sock"
IdentityFile ~/.ssh/production.pub
IdentitiesOnly yes
Host staging-*
IdentityAgent "~/Library/Group Containers/2BUA8C4S2C.com.1password/t/agent.sock"
IdentityFile ~/.ssh/staging.pub
IdentitiesOnly yes
Each server group gets exactly one key offered. No ambiguity, no vault-ordering
dependency, no MaxAuthTries roulette.
Enforce this as a team standard: every Host block in ~/.ssh/config must have
an explicit IdentityFile. Make it a code review checklist item. With an external
agent holding many keys, it’s the difference between a deterministic, auditable auth
flow and one that depends on how your teammates happen to order their vault.
Debugging Checklist
If SSH auth breaks after following this setup, work through these in order:
# 1. Is the 1Password agent socket running?
ls "~/Library/Group Containers/2BUA8C4S2C.com.1password/t/agent.sock"
# 2. Is 1Password unlocked and SSH agent enabled?
# Settings → Developer → Use the SSH agent
SSH_AUTH_SOCK="~/Library/Group Containers/2BUA8C4S2C.com.1password/t/agent.sock" \
ssh-add -l
# 3. Confirm which key SSH is actually offering
ssh -v myserver 2>&1 | grep -E "Offering|denied|disconnect|attempt"
# 4. Verify the public key is in authorized_keys on the server
# (use your provider's web console if SSH is broken)
grep "myserver" ~/.ssh/authorized_keys
Common failure modes:
- 1Password locked → agent returns 0 keys → immediate auth failure
- SSH agent not enabled → Settings → Developer → “Use the SSH agent”
- Wrong key in
authorized_keys→ key was rotated in vault but server wasn’t updated - Server rebuilt → cloud-init wiped
authorized_keyson redeploy (common with VPS providers)
Summary
| Config | What SSH Does |
|---|---|
IdentityAgent only | Sprays all agent keys — fails on MaxAuthTries |
IdentityAgent + IdentitiesOnly yes, no IdentityFile | Ignores all agent keys — offers nothing |
IdentityAgent + IdentityFile key.pub + IdentitiesOnly yes | Offers exactly one key — correct |
The invariant: every Host block should have an explicit IdentityFile.
With 1Password as your agent, a .pub file on disk is all it takes — no private
key material, no security trade-off, completely deterministic behaviour.
Private keys belong in 1Password. Connection behaviour belongs in your config.