Permission denied (publickey) is the most-hit SSH error there is, and the message is doing you no favors. It does not mean “your key is wrong.” It means: the server rejected every authentication method you offered, and public key was the only method it was willing to discuss. The parenthetical is the server’s list of methods still open — (publickey) means password auth is off, so a key has to work, and none of yours did.
That’s a wide net: the wrong username produces this error. So does a key the client never offered, a key the server never received, a permissions problem sshd silently refuses to overlook, and an SELinux label you’ve never heard of. The fix is systematic, not guesswork — here’s the loop, then the culprits in order of how often they’re actually the answer.
The diagnostic loop: read what the client tried
Run the failing connection with maximum verbosity:
ssh -vvv user@host
Three things to find in the output:
Authentications that can continue: publickey— the server’s method list. Ifpasswordis absent, no password will ever be asked; a key must succeed.Offering public key: /home/you/.ssh/id_ed25519— every key the client tried. If the key you meant isn’t in this list, the problem is client-side (culprit 2). If it is —- what came back.
Server accepts keyfollowed by a signature exchange means the key matched and something later failed (rare). The usual sight is the offer followed immediately by the next key orPermission denied— the server looked at the key and shrugged, which points server-side (culprits 3–8).
If you have any other way into the box (provider web console, another user, another admin), the server’s log names the real reason outright:
sudo journalctl -u sshd -f # systemd distros
sudo tail -f /var/log/auth.log # Debian/Ubuntu
sudo tail -f /var/log/secure # RHEL/Alma/Rocky
Lines like Authentication refused: bad ownership or modes for directory /home/deploy end the investigation on the spot.
Culprit 1: wrong user
The single most common cause, especially on cloud images, because the login user isn’t what you’d guess: ubuntu on Ubuntu images, ec2-user on Amazon Linux, admin on Debian AMIs, root on most bare VPSes, azureuser on Azure defaults. The right key with the wrong username produces exactly this error — the server checks ~wronguser/.ssh/authorized_keys, finds nothing, denies.
Before touching anything else, confirm the user your image documents, and pin it in ~/.ssh/config so you never retype it (the ssh_config guide covers the pattern):
Host web1
HostName 203.0.113.10
User ubuntu
Culprit 2: your key was never offered
If ssh -vvv doesn’t show your key in an Offering public key line, the client doesn’t know about it. Point at it explicitly:
ssh -i ~/.ssh/id_ed25519 user@host
If that works, make it permanent with IdentityFile in the config block above. Two related traps: an agent stuffed with many keys can trip the server’s attempt limit before reaching the right one (Too many authentication failures — fix with IdentitiesOnly yes), and a key with a passphrase you cancelled out of is silently skipped. Check what the agent actually holds:
ssh-add -l
Culprit 3: the public key isn’t on the server
The public half must be one line in ~user/.ssh/authorized_keys — for that user, in that user’s home. The reliable way to put it there (uses password auth or an existing key for the copy):
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@host
Doing it by hand — say, through a provider console — append the single line and never overwrite the file:
mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys
# paste the one line from id_ed25519.pub, Enter, Ctrl-D
Classic misses: the key landed in root’s file but you log in as deploy; the line got wrapped by an editor and is now two lines; you pasted the private key (the file starts with ssh-ed25519 AAAA… — if you see -----BEGIN, that’s the wrong half).
Culprit 4: permissions and ownership
sshd’s StrictModes (on by default) refuses to honor authorized_keys if the path to it is writable by anyone but the owner. This is the classic “I copied the key and it still doesn’t work.” The full fix, run as the target user:
chmod go-w ~
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
chown -R $USER:$USER ~/.ssh
The home directory line matters and gets forgotten: a group-writable /home/deploy fails the check just as hard as a world-readable key file. The server log’s bad ownership or modes line tells you exactly which directory it objects to.
Culprit 5: wrong key format or passphrase
OpenSSH cannot read PuTTY’s .ppk files — pointing -i at one fails with invalid format in verbose output. Export an OpenSSH-format key from PuTTYgen (Conversions → Export OpenSSH key), or generate a fresh modern key and enroll it (Ed25519, in almost all cases). To verify a private key file is readable and the passphrase is what you think it is, ask it to print its public half:
ssh-keygen -y -f ~/.ssh/id_ed25519
If that prints a key, the file and passphrase are fine — and you can diff the output against the line in authorized_keys to confirm they’re actually the same key.
Culprit 6: server config disallows it
Less common, but cheap to check when you have server access. Ask sshd what it’s actually running with:
sudo sshd -T | grep -Ei 'pubkeyauthentication|authorizedkeysfile'
PubkeyAuthentication must be yes (default, but hardening scripts sometimes flip it), and AuthorizedKeysFile tells you where sshd looks — if someone pointed it at /etc/ssh/authorized_keys/%u, your carefully placed ~/.ssh/authorized_keys is being ignored. After any change to /etc/ssh/sshd_config:
sudo systemctl reload sshd
Culprit 7: SELinux contexts (RHEL-family)
On RHEL, Alma, Rocky, Fedora and friends, a ~/.ssh created by unusual means — restored from backup, copied as root, made on a mounted volume — can carry the wrong SELinux label, and sshd is denied read access to a file with perfect permissions. Everything above checks out, the error persists, and ls -Z ~/.ssh shows something other than ssh_home_t. One command repairs the labels:
restorecon -R -v ~/.ssh
If you’re out of ideas on a RHEL-family box, run it anyway — it’s harmless when the labels were already right.
Culprit 8: root login is disabled
Modern defaults set PermitRootLogin prohibit-password (key-only) or no (never). If you’re trying root@host and the key genuinely is in /root/.ssh/authorized_keys, check:
sudo sshd -T | grep -i permitrootlogin
With no, no credential will ever work for root — log in as the unprivileged user and sudo, which is the arrangement the default is pushing you toward anyway.
Make it stop happening
Almost every culprit above is a per-host fact you shouldn’t have to re-derive: which user, which key, which port. Pin them in ~/.ssh/config and the error class largely disappears — and for the errors that remain, the decoder ring covers the other nine you’ll meet.
The same facts can live in a client instead of a dot-file. Termalin stores the user, port and key with the saved host, unlocks your keys once through its built-in agent (no passphrase-cancel silently skipping a key), verifies the server’s identity on first connect, and shows live status per host — so “is it even up?” and “am I using the right key?” stop being diagnostic steps. It also imports PuTTY sessions directly, which retires culprit 5 for good.
Termalin is a free, cross-platform SSH client that keeps per-host users, ports and keys with the host and verifies servers on first connect — download it, or read how it handles keys safely.