Fix SSH Permission denied (publickey)
Permission denied (publickey) is five different faults. Read the ssh -v output, find which one you have, and fix it without locking yourself out.
What Permission denied (publickey) actually means
Permission denied (publickey) means your client sent one or more public keys and the server accepted none of them. The network is fine and sshd is running: the refusal happens at the last step of authentication. The fix is never a guess, because ssh -v tells you which of five causes you have.
The words inside the parentheses are the methods the server was willing to accept. Permission denied (publickey) on its own means password login is switched off on that server, so there is no password to fall back to. Permission denied (publickey,password) means passwords were on offer and you failed those as well.
One message covers five separate faults, and it is vague on purpose. A server that replied "no such user" or "that key is not installed" would help anyone scanning for valid accounts. So do not start swapping keys and editing config files. Run one command, read three lines of output, and five possible causes drop to one.
Run ssh -v first, and read three lines
Repeat the command that failed, with -v added:
ssh -v deploy@203.0.113.10A trimmed but realistic run looks like this:
OpenSSH_9.6p1 Ubuntu-3ubuntu13, OpenSSL 3.0.13 30 Jan 2024
debug1: Connecting to 203.0.113.10 [203.0.113.10] port 22.
debug1: Connection established.
debug1: Authenticating to 203.0.113.10:22 as 'deploy'
debug1: Authentications that can continue: publickey
debug1: Next authentication method: publickey
debug1: Will attempt key: /home/you/.ssh/id_ed25519 ED25519 SHA256:0eL/8sBw... agent
debug1: Offering public key: /home/you/.ssh/id_ed25519 ED25519 SHA256:0eL/8sBw... agent
debug1: Authentications that can continue: publickey
debug1: No more authentication methods to try.
deploy@203.0.113.10: Permission denied (publickey).Three lines carry everything you need.
Authenticating to 203.0.113.10:22 as 'deploy' is the username that will actually be used. Not the one you meant to use: the one ssh worked out from the command line, from ~/.ssh/config, or from your local login name.
Authentications that can continue: publickey is the server's list of accepted methods, sent before any key is tried. If publickey is missing from that first list, the server has public key login switched off, so no key can ever work.
Offering public key: ... is one line per key your client really sent, naming the file it came from and its SHA256 fingerprint. A key with no Offering line was never sent to the server.
Now split the problem in two:
- There is no
Offering public keyline for the key you expect. The fault is on your machine, because the server has not seen your key at all. - The key is offered and
Authentications that can continue: publickeycomes back again. The server received that key and refused it, so the fault is on the server.
The causes below are ordered by how often they turn out to be the answer.
Cause 1: you are connecting as the wrong username
The most common cause is also the least interesting one. sshd, the SSH (secure shell) server daemon, never tells you that an account does not exist. It runs the whole exchange for an invented username and refuses at the end with the same message, because leaking valid account names helps an attacker. A typo in the username looks exactly like a broken key.
Check the Authenticating to ... as line before anything else. If it names your laptop login instead of the server account, you left the username off the command.
ssh -v deploy@203.0.113.10
ssh -v root@203.0.113.10The default account depends on the image your provider builds. As of August 2026, Ubuntu cloud images usually ship an ubuntu account, Debian images ship debian or admin, Rocky Linux and AlmaLinux ship rocky and almalinux, and many VPS providers instead install your key straight into root. Your provider's control panel records which account it created. No command run from outside the server can ask it.
A Host block in ~/.ssh/config also sets the username, and it wins over your local login name:
Host vps-prod
HostName 203.0.113.10
User deployIf you created the account yourself and then could not log in as it, the key was probably installed for the image's default user and never copied across. That step is part of the first ten minutes on a new VPS, and it is easy to skip.
Cause 2: the key you think you are sending is not the one being sent
By default ssh offers only the keys held by ssh-agent plus a fixed set of filenames in ~/.ssh: id_ed25519, id_ecdsa, id_rsa, and the hardware and DSA variants of those names. A key saved as ~/.ssh/vps-prod is invisible to ssh until you name it, which is why the verbose output shows no Offering public key line for it.
Name the file, and stop the agent keys from taking its place:
ssh -i ~/.ssh/vps-prod -o IdentitiesOnly=yes deploy@203.0.113.10-i on its own is not enough when the agent holds keys, because ssh still offers the agent's keys first and the named file last. That matters, because the server counts every refused key against MaxAuthTries, which defaults to 6. An agent holding seven keys can use up the limit before your correct key is reached, and the message then changes to:
Received disconnect from 203.0.113.10 port 22:2: Too many authentication failuresIdentitiesOnly=yes limits the attempt to the file you passed. List what the agent is holding with ssh-add -l, and clear it with ssh-add -D if it has collected years of old keys. Then write the settings down so the next login does not depend on remembering flags:
Host vps-prod
HostName 203.0.113.10
User deploy
IdentityFile ~/.ssh/vps-prod
IdentitiesOnly yesOne more client-side trap. ssh refuses to use a private key that other accounts on your own machine can read. It prints a warning and then ignores the key, so the key is never offered and the server never sees it:
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: UNPROTECTED PRIVATE KEY FILE! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0644 for '/home/you/.ssh/vps-prod' are too open.chmod 600 ~/.ssh/vps-prod fixes it. Moving a key through a USB stick or a Windows share is the usual way the mode gets lost. Where keys live and what to call them is covered in SSH key management basics.
Cause 3: the public key never reached authorized_keys
If ssh -v shows the key going out and the server still refuses, the next question is whether that key sits in the account's authorized_keys file. Open your provider's console to check, since you cannot log in over SSH to look.
sudo ls -l /home/deploy/.ssh/
sudo ssh-keygen -lf /home/deploy/.ssh/authorized_keysssh-keygen -lf on an authorized_keys file prints one fingerprint per entry:
256 SHA256:0eL/8sBw... you@laptop (ED25519)
3072 SHA256:9Tq2yZk... old-laptop (RSA)Compare those with the fingerprint on your Offering public key line. If it is not in the list, the key is not installed on that account, whatever you remember doing.
Four ways it goes wrong, all common:
- You pasted the private key instead of the
.pubfile. A public key line starts withssh-ed25519orssh-rsa. A private key starts with-----BEGIN OPENSSH PRIVATE KEY-----. - The paste wrapped onto several lines. Each entry must sit on exactly one line, so a wrapped key is read as several broken entries and matches nothing.
- The key went into
/root/.ssh/authorized_keyswhile you log in asdeploy, or the other way round. The file is per account, and there is no shared one. - The provider's "add my key" box wrote it to the image's default user only, so the account you created later has an empty
.sshdirectory.
The safe way to add a key from the console, as root:
sudo install -d -m 700 -o deploy -g deploy /home/deploy/.ssh
sudo tee -a /home/deploy/.ssh/authorized_keys >/dev/null <<'EOF'
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIExampleKeyDataHere you@laptop
EOF
sudo chown deploy:deploy /home/deploy/.ssh/authorized_keys
sudo chmod 600 /home/deploy/.ssh/authorized_keysRun sudo ssh-keygen -lf /home/deploy/.ssh/authorized_keys again afterwards. The new fingerprint should now be in the list. From a machine that can still log in with a password, ssh-copy-id -i ~/.ssh/vps-prod.pub deploy@203.0.113.10 does the same work and sets the modes correctly for you.
Cause 4: why sshd ignores authorized_keys when permissions are too open
StrictModes yes is the sshd default. Under it, sshd refuses to read authorized_keys if that file, the .ssh directory, or the account's home directory can be written by anyone other than the owner. The reason is direct: if the group or the world can write to your home directory, any account with that access can replace authorized_keys and take over the login. sshd treats an untrusted path as if no key existed.
The client sees the plain Permission denied message. The server log records the real reason:
Authentication refused: bad ownership or modes for directory /home/deploy/.sshor, when the file itself is the problem:
Authentication refused: bad ownership or modes for file /home/deploy/.ssh/authorized_keysWhat sshd will accept:
- The home directory: not group-writable and not world-writable.
755,750and700all pass.775and777fail. ~/.ssh: mode700.~/.ssh/authorized_keys: mode600.- Ownership: all three owned by the account you log in as, not by root.
Ownership matters as much as the mode. A file inside /home/deploy/.ssh owned by root fails the same check, which is what happens when you create it with sudo nano and forget to hand it back. Fix both at once:
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/deploy
ls -ld /home/deploy /home/deploy/.sshThe last command shows the result. You want drwxr-xr-x or tighter on the home directory and drwx------ on .ssh. If those strings are not obvious yet, read how to read a permission string like drwxr-xr-x before you change modes on a live server.
On Rocky Linux and AlmaLinux, add SELinux (security-enhanced Linux) to the list of suspects. A .ssh directory created by an unusual route can carry the wrong file label, so sshd is denied read access even though the modes look right. sudo restorecon -Rv /home/deploy/.ssh puts the labels back, and sudo ausearch -m avc -ts recent shows whether SELinux was the component refusing.
Cause 5: sshd is configured to refuse you
Reading /etc/ssh/sshd_config is not enough on a current Ubuntu or Debian system. That file begins with Include /etc/ssh/sshd_config.d/*.conf, and OpenSSH keeps the first value it finds for any setting. A drop-in file such as 50-cloud-init.conf is therefore read first and wins over anything you edit lower down in the main file. This is why an edit can look correct and change nothing at all.
Ask sshd for the configuration it is really using:
sudo sshd -T | grep -Ei 'pubkeyauth|authorizedkeysfile|permitrootlogin|allowusers|allowgroups|denyusers'A healthy answer looks like this:
permitrootlogin prohibit-password
pubkeyauthentication yes
authorizedkeysfile .ssh/authorized_keys .ssh/authorized_keys2What to look for in your own output:
pubkeyauthentication no. No key will ever be accepted. This also appears inssh -vas a firstAuthentications that can continue:list with nopublickeyin it.authorizedkeysfilepointing somewhere else, for example/etc/ssh/authorized_keys/%u. Your file in the home directory is then ignored completely, and the mode rules from cause 4 apply to the new path instead.allowusersorallowgroupspresent. Any account not listed is refused with exactly this error and no explanation.denyusersanddenygroupsdo the same in reverse.permitrootlogin nowhile you are trying to log in as root.prohibit-passwordis the useful middle setting: root may use a key but not a password.
Match blocks do not appear in a plain sshd -T, because their result depends on who is connecting. Ask about one specific connection:
sudo sshd -T -C user=deploy,host=vps.example.com,addr=198.51.100.7One more setting affects older keys. OpenSSH 8.8 stopped accepting SHA-1 signatures (ssh-rsa) by default, so an RSA key that worked for years can stop working right after a server upgrade. The client says so plainly:
debug1: send_pubkey_test: no mutual signature algorithmThe correct fix is a new key: ssh-keygen -t ed25519 -C "deploy@vps-prod", then install the .pub file as shown above. Setting PubkeyAcceptedAlgorithms +ssh-rsa on the server re-enables the old signatures and gets you in today, so treat it as a way to reach the box, not as the end of the job. The rest of the server-side settings worth reviewing are in hardening the SSH server on a VPS.
How to prove a private key matches the installed public key
Most of the guesswork in this error comes from not knowing whether two files are a pair. One command answers it:
ssh-keygen -y -f ~/.ssh/vps-prodThat prints the public key derived from the private key. It never reads the .pub file next to it, so it tells you what the private key really is rather than what a stale .pub file claims. If the key has a passphrase, the command asks for it, which also proves you still know the passphrase.
ssh-keygen -lf ~/.ssh/vps-prod.pub
ssh-add -lThe first prints the fingerprint of one public key file. The second prints the fingerprints your agent is holding. Now line up four views of the same string: the fingerprint on the Offering public key line from ssh -v, the fingerprint of your .pub file, the fingerprints in ssh-keygen -lf on the server's authorized_keys, and the fingerprint in the server log. The point where they stop matching is your fault.
Read the server log while the login fails
The client is told nothing useful on purpose. The server writes the real reason. Start a log follower on the console session, then run the failing ssh command from your laptop.
sudo journalctl -u ssh -fUbuntu 24.04 does not install rsyslog by default, so /var/log/auth.log may not exist there. On Rocky Linux and AlmaLinux the unit is named sshd and the same records also land in /var/log/secure.
Set LogLevel VERBOSE in the sshd configuration and reload the service. Every attempt then logs the fingerprint the server actually received:
Failed publickey for deploy from 198.51.100.7 port 49812 ssh2: ED25519 SHA256:0eL/8sBw...That line tells you which side the fault is on. A fingerprint you recognise means your key arrived and was rejected by the server, so look at causes 3, 4 and 5. A fingerprint you do not recognise means your client sent a key you did not intend, so go back to cause 2.
When the log is still unclear, run a second sshd on another port in debug mode. It stays in the foreground, serves one connection, prints its reasoning, then exits:
sudo /usr/sbin/sshd -ddd -p 2222From the console session on the same server, connect to it over the loopback address:
ssh -p 2222 -i ~/.ssh/vps-prod -o IdentitiesOnly=yes deploy@127.0.0.1Going over 127.0.0.1 keeps the firewall out of the test. The debug output names the file it opened, the fingerprint it compared, and the exact refusal, including lines such as Authentication refused: bad ownership or modes for directory /home/deploy. Press Ctrl+C when you have your answer. The real sshd on port 22 is untouched throughout.
How to avoid locking yourself out
Every step that edits server configuration needs a way back in that does not depend on SSH. Set this up while SSH still works, not after it breaks.
- Open your provider's console, over serial or VNC (virtual network computing), and confirm you can log in there.
- Make sure you know a working local password for an account with sudo. If you do not have one, reset the root password from the provider console first.
- Keep your current SSH session open. An open session survives
systemctl restart ssh, so it stays a way back in if the new config is wrong. - Check the syntax before you restart:
sudo sshd -tprints nothing when the file is valid, and prints the file and line number when it is not. - Open a second terminal and log in fresh before closing the first one. Broken config stops new logins and leaves existing ones alone, so the session you are sitting in cannot tell you whether the change worked.
Restart with sudo systemctl restart ssh on Debian and Ubuntu, or sudo systemctl restart sshd on Rocky Linux and AlmaLinux. On Ubuntu 24.04 sshd is started from a socket unit, so a change to Port or ListenAddress also needs sudo systemctl restart ssh.socket before it takes effect.
FAQ
Why do I get Permission denied (publickey) when the same key works on another server?
Because the key is fine and something around it is not. Run ssh -v and find the Offering public key line. If your key is not listed, ssh never sent it: the file is not in ~/.ssh under a default name and not loaded in the agent, so add -i /path/to/key -o IdentitiesOnly=yes. If the key is listed and the server still refuses, then that key is missing from the account's authorized_keys, the path to it is group-writable, or sshd config blocks the user. The server log separates those cases.
How do I see which key SSH is actually sending?
ssh -v host prints one debug1: Offering public key: line per key, each naming the source file and a SHA256 fingerprint. ssh-add -l lists the fingerprints the agent holds. ssh-keygen -lf ~/.ssh/id_ed25519.pub prints the fingerprint of a single key file, and ssh-keygen -y -f ~/.ssh/id_ed25519 prints the public key that a private key really derives. For the login to succeed, the fingerprint from the Offering line must also appear in ssh-keygen -lf run against the server's authorized_keys.
Why does sshd ignore my authorized_keys file?
Because StrictModes is on by default, and either the file, the .ssh directory, or the home directory is writable by group or world, or owned by the wrong account. sshd will not trust a path that someone else can change, so it behaves as if no key existed. Set the home directory to 755 or tighter, .ssh to 700, authorized_keys to 600, and make all three owned by the login account. With LogLevel VERBOSE the server records Authentication refused: bad ownership or modes for directory /home/deploy/.ssh.
My key stopped working right after a server upgrade. What changed?
If it is an RSA key, this is most likely the SHA-1 change. OpenSSH 8.8 disabled ssh-rsa SHA-1 signatures by default, so a key that can only sign that way is now refused. The verbose client output shows debug1: send_pubkey_test: no mutual signature algorithm. Generate a modern key with ssh-keygen -t ed25519 and install its .pub file. If you need access immediately, PubkeyAcceptedAlgorithms +ssh-rsa on the server re-enables the old signatures, and you should remove that line once the new key works.
I edited sshd_config and now I cannot log in at all. How do I get back in?
Use your provider's console, which does not go through SSH. Log in there with a local password, run sudo sshd -t to see the syntax error and its line number, undo the change, and restart the service. Then check sudo sshd -T to confirm the running values, because a file in /etc/ssh/sshd_config.d/ may be overriding the main config. If you have no local password, reset the root password from the console first, then repair the file.