Permission denied (publickey): the offer, the refusal, the four causes
ssh -vvv shows what the client offered, journalctl shows why the server refused — wrong user, missing key, open permissions, or ssh-rsa after 8.8.
■ space · the wire, replayed · fig 1
Permission denied (publickey) = every key the client offered was refused — a negotiation verdict, not a password typo.
Diagnose: ssh -vvv shows which keys were offered; journalctl -u ssh shows why they were refused.
Then it's one of four causes: wrong user, key not in authorized_keys, permissions too open, or ssh-rsa refused by OpenSSH 8.8+. ssh-copy-id prevents most recurrences.
01THE WIRE
the client proposes · the server refuses · three lines decide which side your problem lives on
$ ssh -vvv deploy@staging 2>&1 | grep -iE "offering|identity|denied|authentications"
debug1: Offering public key: /home/deploy/.ssh/id_rsa RSA SHA256:9T4xQmV…
02:10:03 — REFUSED. THE DEPLOY DIED ON SSH-RSA02:14:41 — the same client, accepted on ed25519
OFFER id_rsa → REFUSED · OFFER id_ed25519 → ACCEPTED · LEDGER 1R · 1A
the belt is the offer list — every stamp the ring takes, ssh -vvv prints as a line
02THE WITNESS
the ring keeps the tally — the server kept its first: journalctl already logged every verdict
Oct 18 02:10:03 staging sshd[4421]: Failed publickey for deploy from 203.0.113.7 port 51422 ssh2: RSA SHA256:9T4xQmV…
Oct 18 02:14:41 staging sshd[4421]: Accepted publickey for deploy from 203.0.113.7 port 51422 ssh2: ED25519 SHA256:KP7wLn…
two lines, one arc: the RSA offer refused at 02:10 — the deploy's death — then the same client, four minutes later, accepted on ed25519
journalctl -u ssh -n 50 --no-pager | grep -iE "publickey|denied|accepted"
verify: journalctl -u ssh -n 50 | grep Accepted → a line with YOUR fingerprint and user
03THE CAUSE TABLE
the log names the verdict, never the cause — six shapes produce one refusal
| cause | telltale | fix |
|---|---|---|
| Right key exists, wrong one arrives | -vvv offers an identity you don't recognise | ssh-add -l to list, ssh-add -d to drop the stale one |
-i mykey ignored | A config IdentityFile overrides the offer order | Fix or remove the ~/.ssh/config block |
| Key not installed | Server log shows Failed publickey for every offer — authorized_keys: empty, 0 keys | ssh-copy-id user@host |
| Wrong user | Key works for root@host, fails for deploy@host | Key must be in that user's ~/.ssh/authorized_keys |
| Permissions too open | WARNING: UNPROTECTED PRIVATE KEY FILE — the key is skipped, never offered | chmod 700 ~/.ssh; chmod 600 ~/.ssh/* |
| Legacy ssh-rsa key | Server runs OpenSSH 8.8+; old RSA key never accepted | Re-generate as ed25519, or upgrade the key |
every row replays on the wire — press its key on the conveyor and watch the negotiation pick that cause
ssh -o BatchMode=yes … 'echo ok' — no prompt, no hope, just ok.04THE PERMISSION GATE
every cause in the table reached the reader — this one dies on the client's side of the wire
drwx------ · 755 is too open-rw------- · 644 and 664 are too openchmod 700 ~/.ssh
chmod 600 ~/.ssh/*
verify: ls -ld ~/.ssh → drwx------ · ls -l ~/.ssh/id_ed25519 → -rw-------
05THE RETIRED TONGUE
one refusal has a date on it — 8.8, released 2021-10-01, stopped speaking ssh-rsa
"the ssh-rsa signature scheme is ... disabled by default" — the OpenSSH 8.8 release notes, 2021-10-01.
an RSA key generated in 2018 is not broken — its signature format is no longer spoken
ssh-keygen -t ed25519 — a fresh key in the tongue every server still speaksrsa-sha2-256 needs no re-generation — the signature upgrades in placessh-copy-id appends the key and sets sane permissions — it exists because hand-pasting fails on partial linesssh-keygen -t ed25519
ssh-copy-id deploy@staging
$ ssh -o BatchMode=yes deploy@staging 'echo ok'
ok
verify: ssh -o BatchMode=yes deploy@staging 'echo ok' → ok, no prompt
end of the wire · nothing fetched · everything local