termal.in

← Blog

SSH permission denied (publickey): a fast, systematic fix

· Termalin team sshtroubleshootingssh-keystutorial

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:

  1. Authentications that can continue: publickey — the server’s method list. If password is absent, no password will ever be asked; a key must succeed.
  2. 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 —
  3. what came back. Server accepts key followed 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 or Permission 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.

Try it on one host.

Termalin is a fast SSH client for you — and your agents.

Free tier · 14-day Pro trial · pricing