Permission denied (publickey) means one thing and only one thing: the server allows nothing but key authentication, the client offered whatever keys it had, and none of them was accepted. It does not tell you why, and it is deliberately unhelpful - the server will not explain a refusal to somebody who might be an attacker. The explanation exists, though. It is split between two places: ssh -v on your side, which shows which keys were offered and how the server answered, and the authentication log on the server, which says why a key that should have worked was ignored.
Read those two in that order and almost every case lands in one of six buckets: the right key was never offered, the wrong user was asked for, the server cannot trust the files holding your key, the key or its signature algorithm is refused, the agent is in the way, or a security layer such as SELinux or an AllowUsers line is blocking the login before keys matter. This post walks through each, with the exact lines that identify it.
If you are setting up keys for the first time rather than debugging them, SSH keys and sshd_config hardening covers generating, installing and locking down. This post starts at the moment the door does not open.
First: keep a way in#
If you can still reach the server by some route - an open session, a second account, a password that still works, your provider's console - keep it open while you debug. Several of the fixes below involve editing sshd_config or file permissions on the server, and a mistake there turns one locked account into a locked machine.
If you have no way in at all, every server-side fix in this post has to be made through the provider's console or a rescue system. That is slower, but the diagnosis is the same: the client output below still tells you what is wrong, and the logs are still on disk when you get there.
Reading ssh -v: the client side of the story#
Run the connection again with one -v. Two or three (-vvv) add detail that is rarely needed for this error and makes the output harder to scan.
$ ssh -v deploy@203.0.113.10The lines that matter, from a failed attempt, look like this:
debug1: Authenticating to 203.0.113.10:22 as 'deploy'debug1: identity file /home/alex/.ssh/id_rsa type -1debug1: identity file /home/alex/.ssh/id_ed25519 type 3debug1: Authentications that can continue: publickeydebug1: Next authentication method: publickeydebug1: Offering public key: /home/alex/.ssh/id_ed25519 ED25519 SHA256:5xM8n... agentdebug1: Authentications that can continue: publickeydebug1: No more authentication methods to try.deploy@203.0.113.10: Permission denied (publickey).Read it top to bottom with these questions.
- Which user?
as 'deploy'is the account the server is being asked for. If that is your laptop username rather than the server account, you forgotuser@or your~/.ssh/confighas noUserline for this host. - Which key files exist?
type -1means the file was not found or could not be read. Any other number means the client loaded it. If the key you expect showstype -1, the client never had it. - Which keys were offered? Each
Offering public key:line is one attempt, with the file path and the fingerprint. If the key you installed on the server is not in this list, nothing on the server matters yet - fix the client first. - How did the server answer? After an offer,
Server accepts key:means the server found that public key inauthorized_keysand the remaining step is the signature. AnotherAuthentications that can continue: publickeystraight after an offer means the server looked and did not find it, or found it and would not trust it.
That last distinction splits the problem in half. "Offered, not accepted" is a server-side issue: wrong file, wrong permissions, wrong user, a policy refusing the key. "Accepted, then denied" is almost always the signature step: an agent that cannot sign, or an algorithm the two ends cannot agree on.
Fingerprints let you confirm you are offering the key you think you installed. Compare what the client offers with what the server holds:
$ ssh-keygen -lf ~/.ssh/id_ed25519.pub # on your machine$ ssh-keygen -lf /home/deploy/.ssh/authorized_keys # on the server, one line per keyIf no fingerprint on the server matches one offered by the client, you have found the problem without touching a log.
The key that was never offered#
The most common cause of all, and the one people least expect, is that the client simply did not try the right key.
The OpenSSH client looks for default file names: id_rsa, id_ecdsa, id_ecdsa_sk, id_ed25519, id_ed25519_sk, plus whatever the agent holds. A key saved as ~/.ssh/work_server or ~/.ssh/github is invisible unless you name it. Do so on the command line to test, then in the config so you never have to again:
$ ssh -i ~/.ssh/work_server -o IdentitiesOnly=yes deploy@203.0.113.10Host web-01 HostName 203.0.113.10 User deploy IdentityFile ~/.ssh/work_server IdentitiesOnly yesIdentitiesOnly yes matters in the other direction too. Without it, the client offers every key in the agent before it gets to the file you named, and each one counts against the server's MaxAuthTries (default 6). With enough keys loaded you are disconnected with Too many authentication failures while holding the correct key, which looks like the same problem and is not.
Three more ways the right key goes missing:
- `sudo ssh` runs as root and reads
/root/.ssh, not yours. Scripts that callsshundersudo, cron jobs running as another user, and CI runners all have their own home directory and their own keys. - A private key the client refuses to load. If the file is readable by other users, the client prints
WARNING: UNPROTECTED PRIVATE KEY FILE!andThis private key will be ignored.above the debug lines.chmod 600 ~/.ssh/id_ed25519fixes it. - The wrong half.
-i ~/.ssh/id_ed25519.pubpoints at the public key. Recent clients cope by looking for the matching private file, but older ones fail withLoad key: invalid format. Point-iat the file without.pub.
PuTTY users have one more: a .ppk file is not an OpenSSH key. PuTTYgen's Conversions menu exports an OpenSSH private key, and the public key for authorized_keys is the single line shown in its window, not the multi-line ---- BEGIN SSH2 PUBLIC KEY ---- block. That block is the RFC 4716 format and sshd ignores it; ssh-keygen -i -f key.pub converts it to a one-line key if that is all you have.
Wrong user, wrong account, wrong file#
A key is installed for a user, in that user's home directory. Ask for a different user and you get a different authorized_keys, or none.
| What you typed | What the server reads |
|---|---|
ssh root@host | /root/.ssh/authorized_keys |
ssh deploy@host | /home/deploy/.ssh/authorized_keys |
ssh host | The account named like your local user |
The usual mistakes follow from that table:
- Cloud images create a default user (
ubuntu,debian,admin,ec2-userand others, by distribution) and put the key there, often with root login disabled or with root'sauthorized_keysset to print "please log in as ubuntu" and disconnect. - The key went into your own account, not the service account.
ssh-copy-idinstalls into the account you connected as. Adding a key fordeploywhile logged in asalexmeans writing to/home/deploy/.ssh/authorized_keysand then fixing its ownership, covered in the next section. - `AuthorizedKeysFile` has been changed. The default is
.ssh/authorized_keys .ssh/authorized_keys2, relative to the home directory. Some hardened setups move it to/etc/ssh/authorized_keys/%u. Check what the running daemon uses rather than what you assume:
$ sudo sshd -T | grep -Ei 'authorizedkeysfile|pubkeyauthentication|allowusers|allowgroups'sshd -T prints the effective configuration after every Include and default is applied, which is the only version that matters. On Debian and Ubuntu a file in /etc/ssh/sshd_config.d/ can override the main file silently, because sshd keeps the first value it reads for a keyword. To see what a Match block does for one specific login, add the connection details: sshd -T -C user=deploy,host=laptop,addr=198.51.100.7.
Finally, check the key line itself. One public key is exactly one line: type, base64 blob, optional comment. Pasting through a terminal or an editor that wraps long lines splits it into two, and sshd silently skips a broken line. wc -l on a file with one key should say 1, and ssh-keygen -lf on it should print one fingerprint rather than an error.
File permissions and ownership#
This is the classic server-side cause, and the one the client cannot see at all. With StrictModes yes (the default), sshd refuses to use an authorized_keys file if it, its directory, or the home directory above it could be modified by somebody other than the user or root. A file anyone could edit is a file anyone could add their own key to, so the daemon treats it as compromised.
| Path | Mode | Owner |
|---|---|---|
/home/deploy | 755, 750 or 700 | deploy |
/home/deploy/.ssh | 700 | deploy |
.ssh/authorized_keys | 600 (or 644) | deploy |
The rule is no group or world write permission anywhere on that path. Group-writable home directories are the sneaky one: a chmod -R 775 on a home directory to share a project folder will break key logins for that user, and nothing on the client hints at the connection.
Ownership is the other half. Writing the file as root, with sudo nano or sudo cp, leaves it owned by root. Restore it in one go:
$ sudo chown -R deploy:deploy /home/deploy/.ssh$ sudo chmod 700 /home/deploy/.ssh$ sudo chmod 600 /home/deploy/.ssh/authorized_keys$ sudo chmod go-w /home/deployWhen this is the cause, the server log says so plainly - Authentication refused: bad ownership or modes for directory /home/deploy or the same for .ssh or the file. Turning StrictModes off makes the error go away and leaves the actual weakness in place; fix the permissions instead. If you have spent time puzzling over permissions on other kinds of server, file permissions and ownership explains the octal notation in more depth.
Key types and the ssh-rsa SHA-1 change#
Since OpenSSH 8.8 (2021), the ssh-rsa signature algorithm, which signs with SHA-1, is disabled by default on both client and server. It is the most confusing cause of this error because the key type and the signature algorithm share a name. An RSA key still works perfectly well; it is signed with rsa-sha2-256 or rsa-sha2-512 instead. Trouble only starts when one end is too old to know the SHA-2 variants.
There are two directions, and they look different.
A new client talking to an old server. Typical targets are old appliances, NAS boxes, routers, and distributions whose OpenSSH predates 7.2. With -v you see the key considered and then skipped:
debug1: Offering public key: /home/alex/.ssh/id_rsa RSA SHA256:Qk2... explicitdebug1: send_pubkey_test: no mutual signature algorithmno mutual signature algorithm is the giveaway. The cleanest fix is an ed25519 key, if the server is new enough to accept one (OpenSSH 6.5 or later). If it is not, re-enable SHA-1 for that one host only, never globally:
Host old-nas HostName 192.168.1.20 HostKeyAlgorithms +ssh-rsa PubkeyAcceptedAlgorithms +ssh-rsaOn clients older than OpenSSH 8.5 the second keyword is spelled PubkeyAcceptedKeyTypes; current versions accept both. HostKeyAlgorithms is only needed if the server's own host key is RSA and SHA-1 only, which on equipment that old it usually is.
An old client talking to a new server. Here the client output just shows the offer and a refusal, and the server log carries the reason: a line saying the signature algorithm, or on some versions the key type, ssh-rsa is not in PubkeyAcceptedAlgorithms. The wording varies between releases, but the keyword in it does not. The fix belongs on the client: update it, or switch it to an ed25519 key. Old Windows tools, old Java SSH libraries used by deployment and backup software, and embedded clients are the usual suspects. Adding +ssh-rsa to the server re-enables SHA-1 for everybody and should be a short-lived workaround, not the answer.
Also worth knowing: DSA keys (ssh-dss) have been disabled by default since OpenSSH 7.0 and support was removed entirely in later releases. A id_dsa key will not work against a current server under any setting, and the only fix is a new key. The same applies to RSA keys shorter than 1024 bits, which current OpenSSH refuses outright.
When the agent gets in the way#
The agent helps until it does not. Three failures are worth recognising.
The agent has the wrong keys, or none. ssh-add -l lists what it holds. The agent has no identities. means it is running and empty; Could not open a connection to your authentication agent. means SSH_AUTH_SOCK is unset in this shell, so the client cannot reach it at all. New terminal sessions, tmux started before the agent, and su to another user are the usual causes.
$ ssh-add -l$ eval "$(ssh-agent -s)"$ ssh-add ~/.ssh/id_ed25519The server accepts the key and the agent will not sign. This shows as Server accepts key: followed by sign_and_send_pubkey: signing failed ... agent refused operation. Common reasons are a hardware key that needs a touch nobody gave it, a desktop keyring agent that wants a confirmation dialog that never appeared, or a key added with a lifetime (ssh-add -t) that has expired. Removing and re-adding the key (ssh-add -d, then ssh-add) clears most cases; on Linux desktops, checking that the private key file is 600 fixes another surprising share of them.
Too many keys. Covered above, and worth repeating because it is so common: an agent holding six keys exhausts MaxAuthTries before the right one is reached. IdentitiesOnly yes with an explicit IdentityFile per host is the permanent fix.
On Windows, the built-in OpenSSH client uses the ssh-agent service, which is disabled by default. It needs its startup type changed to automatic in Services (or with Set-Service ssh-agent -StartupType Automatic) before ssh-add works at all. Git for Windows ships its own separate OpenSSH, so a key added to one agent is invisible to the other; if git fails while ssh works, that mismatch is the first thing to check.
SELinux, AllowUsers and other gatekeepers#
Some refusals happen before the key is ever compared, and some happen with every file looking correct.
SELinux on Fedora, RHEL, Rocky, Alma and CentOS Stream labels files, and sshd is only allowed to read authorized_keys labelled ssh_home_t. A .ssh directory created by mv from somewhere else, restored from an archive, or copied into place keeps its old label, and the permissions look perfect while the daemon is denied access.
$ ls -Z ~/.ssh/authorized_keysunconfined_u:object_r:user_home_t:s0 /home/deploy/.ssh/authorized_keys$ restorecon -Rv ~/.ssh$ sudo ausearch -m avc -ts recentuser_home_t where ssh_home_t should be is the problem; restorecon resets the labels to the policy's defaults. If the home directory itself lives somewhere unusual, such as /data/home, the policy has no rule for it, and you need to tell SELinux it is equivalent to /home first with semanage fcontext -a -e /home /data/home, then run restorecon. ausearch reads /var/log/audit/audit.log and shows the denial itself, which sshd's own log will not.
`AllowUsers`, `AllowGroups`, `DenyUsers`, `DenyGroups`. If any of these exist, a user not matched is refused regardless of key. The log says User deploy from 198.51.100.7 not allowed because not listed in AllowUsers, and the client sees the same Permission denied (publickey) as for a missing key. Adding a new account and forgetting this line is a very common way to spend an hour.
`PermitRootLogin` set to no refuses root even with a valid key. prohibit-password (the default since OpenSSH 7.0) allows root with a key only.
Shell and account state. A user whose shell is /usr/sbin/nologin authenticates and is then thrown out, which looks different (This account is currently not available). With UsePAM no, an account whose password is locked in /etc/shadow can also be refused for key logins; most Linux distributions run with UsePAM yes, where this does not apply.
Windows OpenSSH Server has its own rule that catches everybody once. For a user in the Administrators group, sshd ignores the user's own .ssh\authorized_keys and reads C:\ProgramData\ssh\administrators_authorized_keys instead, and that file must be writable only by Administrators and SYSTEM:
icacls.exe "C:\ProgramData\ssh\administrators_authorized_keys" /inheritance:r /grant "Administrators:F" /grant "SYSTEM:F"Reading the server logs#
When the client output does not settle it, the server log does. Where it lives depends on the distribution:
| System | Where to look |
|---|---|
| Debian, Ubuntu | /var/log/auth.log or journalctl -u ssh |
| Fedora, RHEL, Rocky, Alma | /var/log/secure or journalctl -u sshd |
| Any systemd system | journalctl -t sshd (by syslog identifier) |
Watch it live while you try to connect from another terminal; matching a timestamp to your attempt is much easier than searching after the fact.
$ sudo journalctl -u ssh -f$ sudo tail -f /var/log/auth.logThe lines that answer the question:
| Log line contains | Meaning |
|---|---|
Invalid user | That account does not exist |
bad ownership or modes | StrictModes refused the file |
not listed in AllowUsers | Policy, not the key |
not in PubkeyAcceptedAlgorithms | SHA-1 ssh-rsa refused |
Connection closed by authenticating user | Client gave up, nothing matched |
Accepted publickey for | It worked - the problem is elsewhere |
Connection closed by authenticating user deploy ... [preauth] with nothing before it is the frustrating one: the account exists, the file was readable, and none of the offered keys was in it. That sends you back to fingerprints. Setting LogLevel VERBOSE in sshd_config makes the daemon log the fingerprint of each key it considers, which turns this from a guess into a comparison. Logs worth keeping covers retaining these logs once the immediate problem is solved.
When even that is not enough, run a second daemon in debug mode on a spare port. It handles one connection, prints every decision to your terminal, and exits, so it does not disturb the real service:
$ sudo /usr/sbin/sshd -d -p 2222$ ssh -v -p 2222 deploy@203.0.113.10 # from your machinePort 2222 has to be open in the firewall for the duration - the ufw guide has the one-line rule - and should be closed again afterwards. The debug output names the exact file it tried to open, the permission it objected to, and the key it compared, which ends nearly every remaining mystery.
A note on noise: a public server's auth log is full of Invalid user lines from scanners trying admin, test and oracle. That is background, not your problem, though it is a good reason to keep fail2ban in front of anything that still accepts passwords.
Git hosts and SFTP: the same error elsewhere#
The single most common place people meet this error is not a server at all but git push. git@github.com: Permission denied (publickey). is the same mechanism with two twists: the user is always git, never your account name, and the key has to be added to your account on the Git host rather than to a file on a server. Test without Git in the way:
$ ssh -T git@github.comA working key answers with a greeting that includes your username and a note that shell access is not provided. A failing one shows the same denial, and ssh -vT git@github.com gives you the offered-keys list from earlier. If the repository URL starts with https://, SSH keys are not involved at all; that is a token problem, not this one.
Managed game, app and web servers usually have no SSH daemon of their own to reach. File access on RE:NODE goes over SFTP with credentials issued per server in the panel, so the fix for a refused SFTP login there is the host, port and username shown on that server's page, not a key file - SFTP and the file manager has the connection details. Every section of this post applies in full on a VDS, where sshd is yours to configure.
A checklist in the right order#
When you just want the order of operations:
ssh -v user@host- confirm the username and that your key is in theOffering public keylines.- If it is not offered, fix the client:
IdentityFile,IdentitiesOnly yes, private key mode600, the agent. - If it is offered and refused, compare fingerprints with
ssh-keygen -lfon both sides. - Check ownership and modes on the home directory,
.sshandauthorized_keys. - Check
sshd -TforAuthorizedKeysFile,AllowUsersandPermitRootLogin. - Watch the auth log while connecting; on SELinux systems, check
ls -Zandausearch. - If the client says
no mutual signature algorithmor the server mentionsPubkeyAcceptedAlgorithms, it is the SHA-1 change - move to ed25519. - Still stuck:
sshd -d -p 2222and read every line.
Once it works, make the fix permanent in ~/.ssh/config so you are not debugging the same thing next month, and review the rest of the setup with the first hour on a new VDS.
FAQ#
Why does ssh-copy-id work but the key still gets refused?
ssh-copy-id installs the key correctly into the account you connected as, but it does not fix a home directory that is group-writable or an SELinux label that was wrong before it ran. Check the server log for bad ownership or modes, and on Fedora or RHEL-family systems run restorecon -Rv ~/.ssh for that user.
Is it safe to turn off StrictModes to make the error go away?
No. StrictModes exists because a writable authorized_keys lets any other local user add their own key and log in as you. Fixing the permissions takes four commands and leaves the protection in place; switching it off hides a real weakness.
My key worked for years and stopped after an update. Why?
Almost always the ssh-rsa SHA-1 change in OpenSSH 8.8, or the removal of DSA keys. One end was updated and the two can no longer agree on a signature algorithm. Generate an ed25519 key and install it alongside the old one; only re-enable ssh-rsa per host for equipment that cannot be updated.
Can I see why the server rejected me without root access?
Not from the server side - the authentication log is readable only by root or the adm group on most systems. You can still get most of the way with ssh -v and fingerprint comparison, then send the exact timestamp of your attempt to whoever administers the server so they can find the matching log line quickly.
Does Permission denied (publickey) mean my key was stolen or the server was hacked?
Almost never. It is the server doing its job: refusing a key it does not recognise. A changed host key warning is the message that should worry you, not this one. If keys you did not add appear in authorized_keys, though, that is a different situation entirely and what to do when your server is hacked is the next read.


