Permission denied (publickey) means the SSH server reached the public-key authentication step, found no matching key for the user, and rejected the connection. The fix is almost always one of: the public key is not in the server’s ~/.ssh/authorized_keys, the file permissions on the server are too open, the sshd_config does not have PubkeyAuthentication yes, or the client is sending a different key than the server expects. The team that walks through this checklist resolves the issue in under five minutes.
Table of contents
- The public key on the server
- File permissions on the server
- sshd_config on the server
- The client is sending the wrong key
- The key file mode on the client
- The user mismatch
- Home directory and AuthorizedKeysFile
- The SELinux and AppArmor context
- FAQ
The public key on the server
The first thing to check: is the public key in the server’s authorized_keys?
# On the server, as the user you are trying to log in as:
cat ~/.ssh/authorized_keys
The team that sees the expected key in the output has the right setup. The team that sees nothing (or the wrong key) needs to re-add it. From the client:
ssh-copy-id user@server
The team that prefers the manual path:
cat ~/.ssh/id_ed25519.pub | ssh user@server "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
That pipeline creates ~/.ssh if needed, sets the right permissions, appends the key, and locks down the file - in that order. Skipping the chmod step is the most common reason for publickey denial.
File permissions on the server
OpenSSH refuses to use authorized_keys if the permissions are too lax:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
chmod 600 ~/.ssh/id_ed25519 # if the server has its own key
The team that has ~/.ssh owned by a different user than the login user gets Permission denied (publickey) even if everything else is right. chown -R user:user ~/.ssh fixes that.
The team that has a backup tool restore ~/.ssh from a tarball often ends up with the wrong owner if the backup tool runs as root. The fix is chown after restore.
sshd_config on the server
/etc/ssh/sshd_config controls what the daemon accepts:
sudo sshd -T | grep -i pubkey
The expected output includes pubkeyauthentication yes. The team that sees no needs to edit /etc/ssh/sshd_config, set PubkeyAuthentication yes, then sudo systemctl reload sshd.
Other settings to check:
PasswordAuthentication no- this disables password fallback (good for security, but means public-key must work first).AuthorizedKeysFile .ssh/authorized_keys- the default. If a custom path is set, the team that looks in~/.sshis looking in the wrong place.PermitRootLogin prohibit-password- root can use a key but not a password. The team that tries to SSH as root with a key and gets denied needs to check this.
The client is sending the wrong key
The client might have multiple keys in ~/.ssh/ and is sending the wrong one:
ssh -v user@server 2>&1 | grep -i 'Offering\|Sending'
The verbose output shows which key the client is offering. If it is not the key in the server’s authorized_keys, the client is sending a different one.
The fix: be explicit in ~/.ssh/config:
Host myserver
HostName server.example.com
User myuser
IdentityFile ~/.ssh/id_ed25519_server
IdentitiesOnly yes
IdentitiesOnly yes forces the client to send only the listed IdentityFile, even if there are other keys in the agent.
The key file mode on the client
The client also has file permission requirements:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_ed25519
chmod 644 ~/.ssh/id_ed25519.pub
ssh refuses to use a private key with permissions more open than 600. The team that copies a key from a backup or a shared drive often ends up with 644 or worse - chmod 600 fixes it.
On Windows, the permissions story is different: OpenSSH on Windows checks the ACL, but the rules are similar - the file should be readable only by the current user. icacls ~/.ssh/id_ed25519 /inheritance:r /grant:r "%USERNAME%:(R)" is the manual fix when the ACL is too open.
The user mismatch
The team that copies a key into authorized_keys for user A and tries to log in as user B gets publickey denied. The key must be in the target user’s ~/.ssh/authorized_keys.
# Wrong: key in /root/.ssh/authorized_keys when logging in as 'deploy'
# Right: key in /home/deploy/.ssh/authorized_keys
The team that uses sudo to manage the keys is sometimes putting them in /root/.ssh/authorized_keys (the root user’s authorized_keys) when the user logging in is a service account like deploy.
Home directory and AuthorizedKeysFile
Some setups mount the home directory over NFS or have AuthorizedKeysFile set to a non-default path:
sudo sshd -T | grep authorizedkeysfile
The team that has /home/%u/.ssh/authorized_keys (the default) but the home directory is a network mount that the SSH daemon cannot read at login gets denied. The fix is either a local home directory or a copy of authorized_keys to a local path and updating AuthorizedKeysFile.
The team that runs SSH in a container (Docker, Kubernetes) has AuthorizedKeysFile set inside the container’s sshd_config and the public key must be in that exact path inside the container.
The SELinux and AppArmor context
On RHEL, Fedora, CentOS Stream, and similar distros, SELinux can deny SSH from reading the key:
restorecon -R -v ~/.ssh
The team that has the wrong SELinux context on ~/.ssh (typically after copying files from a backup) gets denied even if every other check passes. restorecon resets the context to the policy default.
FAQ
Why does ssh say publickey when I have a key?
The server reached the public-key step, found no matching key, and rejected the connection. The error message says publickey because that is the authentication method the server tried. The fix is one of the eight items in this post - usually permissions or the wrong user.
What does ssh -v show that I should look for?
ssh -v user@server 2>&1 | grep -i 'Offering\|Authentication' shows which keys the client is sending and which methods the server is accepting. The team that debugs with -vvv (three verbose levels) sees the full handshake.
Can I have multiple keys in authorized_keys?
Yes - one key per line. The team that has separate keys for laptop, work desktop, and CI/CD adds all three lines to authorized_keys. The server accepts any matching key.
Does PasswordAuthentication no affect public-key?
No. PasswordAuthentication no only disables the password fallback. Public-key is a separate mechanism. The team that has PasswordAuthentication no and is locked out needs to fix the public-key path, not the password path.
How do I make SSH agent forward the key?
Add ForwardAgent yes to ~/.ssh/config for that host. The team that forwards the agent can SSH from server A to server B without copying the private key - server A holds the key in memory and signs the auth request on B’s behalf.
If you are sizing the infrastructure for the kind of project this post covers, the RunxBuild hosting calculator is the right place to model the line items. The compute, the memory, the storage, the bandwidth, the database - each one is a separate number, and the team’s mental model for the platform is the sum of those numbers. The RunxBuild dashboard is where the team sees the actual usage in one place.
Useful related references: