Darwvin

Fix "Permission denied (publickey)" and "Host key verification failed"

Fix Permission denied (publickey) and Host key verification failed: read ssh -vvv output, check keys and permissions, sshd logs and known_hosts fingerprints.

Published 12 min read

Permission denied (publickey) means the server did not accept any key your client offered. The parenthesized part lists the login methods the server still allows, and a bare (publickey) means keys are the only option. Host key verification failed means ssh could not verify the server's host key, so it stopped before logging in. Either the name is missing from known_hosts and ssh cannot ask (or is told not to), or the stored key differs from the one the server now offers.

Work through it in order

Read the debug output first, then check which account and key the client uses, then the local key files, then the server. Handle host keys last, because the fix there is a security decision, not a config change.

Start with the debug output

Ask ssh for its most detailed output, -vvv, and filter for the lines that matter:

ssh -vvv alice@server.example.com 2>&1 | grep -E 'Will attempt|Offering|Server accepts|Authentications that can continue|Authentication succeeded|denied'

On Windows, pipe to Select-String in place of grep. A failing run looks roughly like this. The wording varies a little between releases:

debug1: Will attempt key: /home/alice/.ssh/id_ed25519 ED25519 SHA256:...
debug1: Offering public key: /home/alice/.ssh/id_ed25519 ED25519 SHA256:...
debug1: Authentications that can continue: publickey
debug1: No more authentication methods to try.
alice@server.example.com: Permission denied (publickey).

Read it as three questions. Which keys went out? The "Will attempt" and "Offering public key" lines answer that. Agent keys are offered too unless IdentitiesOnly is set. Did the server accept one? A Server accepts key line means the server will accept that key for this account, and the signature check comes next. If you never see that line for the key you expect, the server does not accept it for this account. What does the server still allow? Authentications that can continue lists the remaining methods. If it lists only publickey, passwords are off, and the problem is the key, the account or the file.

The parenthesized list changes with the server's policy. Permission denied (publickey,password) means passwords are still accepted, so ssh prompts for one once the key fails. A bare Permission denied (publickey) means keys are the only method left. If the output has no Offering public key line at all, ssh had no identity to offer, which points at the client's configuration rather than the server.

Use the debug lines to choose where to look:

What the debug output showsWhat it usually meansGo to
No Offering public key linessh had no identity to offerSteps 1 and 2
An offer, but no Server accepts keyThe server does not have that key for this account, or it rejected the filesStep 4
UNPROTECTED PRIVATE KEY FILE warningYour private key has a group or other permission bit setStep 3
Too many authentication failuresThe agent offered more keys than the server allows attemptsStep 2
REMOTE HOST IDENTIFICATION HAS CHANGEDThe stored host key no longer matches the serverStep 5

Step 1: Check the account and key ssh really uses

Before you blame the server, check what ssh will do. ssh -G prints the effective configuration after every Host and Match block is applied, without connecting:

ssh -G db01 | grep -E '^(user|hostname|port|identityfile|identitiesonly) '

The output uses lowercase keywords, such as user alice. A user typed on the command line, as in ssh bob@db01, overrides the User line in the config. A key that works for alice fails for bob, so compare the account first.

Step 2: Make sure the right key goes out

The agent holds keys, and ssh offers them one at a time. The server limits how many authentication attempts a connection gets: MaxAuthTries defaults to 6 (sshd_config(5)). With several keys loaded, the attempts run out before ssh reaches the right one, and the server disconnects with Too many authentication failures.

OpenSSH 10.6 changed how key checks are counted. Up to six public key queries, set by PubkeyAuthOptions max-pk-ok with a default of 6, do not count against MaxAuthTries. Further attempts after those queries count as failures. Earlier releases counted those queries against MaxAuthTries too, so the limit can be reached sooner on older servers. Either way, pin the key per host:

Host db01
    HostName db01.internal.example.com
    User alice
    IdentityFile ~/.ssh/id_ed25519_work
    IdentitiesOnly yes

IdentitiesOnly yes limits ssh to the configured files, even when the agent holds others (ssh_config(5)). The SSH config guide covers the rest of the file format.

Step 3: Check the private key on your machine

ssh refuses a private key you own when any group or other permission bit is set on it. It prints an UNPROTECTED PRIVATE KEY FILE warning, says the key will be ignored, and carries on without it, so the failure looks like a server rejection. Check the modes and fix them:

ls -l ~/.ssh/
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_ed25519_work

Keep private keys at 600 or stricter. Public keys do not need that protection.

Step 4: Check the server

One ordinary case is a rebuilt server. The account exists, but its home directory is new, and nobody copied authorized_keys across. Check that file first.

Modes and ownership

StrictModes is on by default. It checks the modes and ownership of the user's files and home directory before accepting a login (sshd_config(5)). The sshd(8) FILES section is more specific: if authorized_keys, the ~/.ssh directory or the home directory is writable by other users, sshd will not allow the file to be used unless StrictModes is set to no. Check these on the server:

ls -ld ~ ~/.ssh
ls -l ~/.ssh/authorized_keys

You want drwx------ on ~/.ssh and -rw------- on authorized_keys, and no group or other write bit on your home directory. Fix a writable home directory with chmod go-w ~, and make sure the files belong to the login account.

Settings that reject the key

sshd -T prints the effective configuration after Include processing. Match blocks apply only when you add -C with connection details, so the output shows the global settings (sshd(8)):

sudo sshd -T | grep -Ei '^(pubkeyauthentication|authorizedkeysfile|strictmodes|allowusers|denyusers|permitrootlogin|authenticationmethods) '

Look for pubkeyauthentication yes, an authorizedkeysfile that points where you expect, and an allowusers line that includes your account. AllowUsers allows logins only for the names it lists. If authenticationmethods requires a second method, a key alone is not enough, and the client shows the remaining method in its output.

What the log says

The log names the file or directory that failed a check, so read it before guessing:

sudo journalctl -u ssh -n 50 --no-pager     # Debian and Ubuntu
sudo journalctl -u sshd -n 50 --no-pager    # Fedora and RHEL

A StrictModes refusal looks like this (typical output):

sshd[2191]: Authentication refused: bad ownership or modes for directory /home/alice

On Debian with rsyslog, the same messages also go to /var/log/auth.log. Keep LogLevel at its default for everyday use. sshd_config(5) says that logging with a DEBUG level violates the privacy of users and is not recommended, so use it briefly, on a test server.

If the log tells you nothing, run a one-off debug daemon on another port. With -d, it stays in the foreground and handles one connection, so you can watch the exchange:

sudo /usr/sbin/sshd -d -p 2222

From the client, run ssh -p 2222 alice@server.example.com. The debug daemon exits when that connection closes.

SELinux and Windows servers

On Fedora and RHEL, a wrong SELinux label produces the same symptom even when the modes are right. ls -Z ~/.ssh shows the labels, and sudo restorecon -Rv ~/.ssh restores the defaults. The key setup guide covers the label in more detail. On a Windows server, an administrator account must use administrators_authorized_keys with the ACL described in that guide.

Step 5: Handle host key errors

Two host key problems look alike, and both end with Host key verification failed. An unknown host fails that way when ssh cannot ask, for example in a script, or when StrictHostKeyChecking is yes. A changed host fails because the key stored for that name no longer matches the server, and ssh prints REMOTE HOST IDENTIFICATION HAS CHANGED first.

What known_hosts protects

RFC 4251 (section 4.1) explains that the server's host key lets the client verify that it is talking to the correct server, which requires the client to know that key in advance. ssh stores the keys it has accepted in ~/.ssh/known_hosts. ssh(1) says that if a host's identification changes, ssh warns about it and disables password authentication, to prevent server spoofing and man-in-the-middle attacks. Treat a changed key as an alarm until you have checked it. A typical run looks like this, with some banner lines trimmed:

@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Add correct host key in /home/alice/.ssh/known_hosts to get rid of this message.
Offending ED25519 key in /home/alice/.ssh/known_hosts:12
Host key for db01.internal.example.com has changed and you have requested strict checking.
Host key verification failed.

The boring explanations come first: a rebuilt server, a replaced cloud instance, or an address that now belongs to another machine. Until you have compared the fingerprint through a channel you trust, treat an impostor as possible.

Verify the new fingerprint through a channel you trust

On the server itself, through console access or a session you already trust, print the host key fingerprint:

ssh-keygen -l -f /etc/ssh/ssh_host_ed25519_key.pub

The output looks like 256 SHA256:... root@db01 (ED25519). Compare that string with the fingerprint your client shows. Comparing it over the same connection you are trying to trust proves nothing, because an impostor would show its own fingerprint there.

RemoteOpsX shows the SHA-256 fingerprint of a server's host key before it trusts the host. On the desktop, a new key is trusted only after you confirm it, or when it matches a key you already trust, such as one in your OpenSSH known_hosts. The Android preview trusts a new key only after an explicit tap. On both, a changed key stays blocked until you replace the trusted identity after checking it out of band.

Remove the stale entry

After you verify the new key, remove the old entry for that name, and for the address if you ever connected by IP:

ssh-keygen -R db01.internal.example.com
ssh-keygen -R 203.0.113.10

ssh-keygen -R removes every key stored for the host, including hashed entries (ssh-keygen(1)). Its output names the line it removed and the file it rewrote, and the original file is kept as known_hosts.old:

# Host db01.internal.example.com found: line 12
/home/alice/.ssh/known_hosts updated.
Original contents retained as /home/alice/.ssh/known_hosts.old

For a non-default port, pass the name in the bracketed form that known_hosts uses, such as ssh-keygen -R "[db01.internal.example.com]:2222". Reconnect, compare the fingerprint the prompt shows with the value from the server, and only then type yes.

Choose StrictHostKeyChecking on purpose

ssh_config(5) defines the values below. The table shows what each does with a new host and with a changed one.

ValueNew hostChanged hostUse it for
ask (default)Asks you to confirm, then stores the keyRefusesInteractive work
accept-newStores the key without askingRefusesScripts that meet new hosts
yesRefuses unless the key is already in known_hostsRefusesAutomation where you manage known_hosts
no (or off)Stores the key and continuesContinues, with password, keyboard-interactive, agent and port forwarding turned offA short test you control

Scripts should not depend on no. If a job must reach hosts you have not seen, load their keys from a verified source first. ssh-keyscan gathers host keys without checking them, so it cannot tell you whether they are the right ones.

FAQ

Why does the key work from one computer and not another?

Each computer has its own agent, ~/.ssh/config and key files. Run ssh -G on both machines and compare the user and identityfile lines, then compare the fingerprints that ssh-add -l lists. The usual difference is a key or config entry that exists on only one machine.

Is it safe to run ssh-keygen -R?

Removing an entry makes ssh treat the host as unknown, so with the default ask setting the next connection asks you to confirm. That prompt is the moment to compare fingerprints, so never accept the new key without comparing it.

Can I turn off host key checking for a quick test?

Use -o StrictHostKeyChecking=no for one command, on a network you control, and treat that session as untrusted. A connection without checking gives no protection against an impostor, so keep the option out of config files.

Why does host key verification fail only in cron jobs?

A job that cannot prompt has nobody to answer for an unknown host, so ssh refuses. Load verified keys into known_hosts ahead of time, or use accept-new for new hosts. accept-new still refuses a changed key, which is what you want.

How verbose should server logging be?

Keep the default for normal operation. sshd_config(5) says DEBUG logging violates user privacy and is not recommended, so use it briefly on a test server only.

References

Get product news by email

We use your address only for these emails. The unsubscribe link is in every email. Read our privacy policy.