SSH Permission Denied (publickey): Fix It Without Locking Yourself Out
Nothing humbles a senior admin faster than Permission denied (publickey) on a box you swear you configured yesterday. Coffee (or chai) in hand, second terminal open, pride slightly bruised — let’s fix it without turning a key problem into a lockout.
This error means the server did not accept any public key your client offered. It does not always mean “your key is wrong.” Permissions, wrong user, wrong file, ssh-agent emptiness, and overly clever sshd_config all wear the same mask.
Before you change auth settings, read our SSH hardening checklist. Hardening and recovery are the same muscle: never cut the branch you’re sitting on.
Rule zero: keep a break-glass path
- Cloud serial / console access (AWS EC2 serial, GCP serial, Azure serial, vendor KVM)
- Or a second working SSH session already open as a sudo user
- Or a bastion that still works
If you only have one path in and you’re about to edit sshd_config or authorized_keys, stop and open console first. Lockouts are boring; they’re also expensive.
Reproduce with maximum honesty
On your laptop:
To test just the key, tell ssh to use the public key method alone, with only the identity you name. Password and keyboard-interactive fallbacks then cannot hide the real publickey failure. -F /dev/null skips your ssh config so no extra IdentityFile lines are added; if you normally rely on a port or jump host from that config, pass those options explicitly:
ssh -F /dev/null -o PreferredAuthentications=publickey -o IdentitiesOnly=yes -i ~/.ssh/id_ed25519 -vvv user@host
ssh -vvv user@server
Skim for lines like:
Offering public key: ...Authentications that can continue: publickeyNo more authentication methods to try
Also confirm which identities are actually available:
ssh-add -l
ls -la ~/.ssh/
Empty agent + key not default-named (id_ed25519 / id_rsa) = client never offers what you think it will. Force it:
ssh -i ~/.ssh/my_special_key -o IdentitiesOnly=yes user@server
IdentitiesOnly=yes stops ssh from spraying every key in the agent (some servers disconnect after too many failed attempts).
Server-side checklist (in order)
1) Correct user
Cloud images love ubuntu, ec2-user, admin, debian, centos. Wrong username → publickey denial even with a perfect key.
2) authorized_keys content
Via console:
sudo -iu thatuser
cat ~/.ssh/authorized_keys
One key per line. No email wrapping. No Windows CRLF weirdness if you can help it. The public key must match the private key you’re offering (ssh-keygen -y -f privatekey prints the public half for comparison).
3) Permissions — the silent killer
OpenSSH is picky on purpose:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
# home directory must not be group/world-writable
chmod go-w ~
Ownership must be the user, not root (unless you’re intentionally using a system path). SELinux contexts on RHEL-like systems can also block reads — check ausearch / restorecon -Rv ~/.ssh if permissions look perfect and it still fails.
4) sshd_config reality
sudo sshd -T | egrep 'pubkeyauthentication|authorizedkeysfile|permitrootlogin|passwordauthentication'
Look for drop-ins under /etc/ssh/sshd_config.d/ overriding the main file. AllowUsers / AllowGroups / DenyUsers will refuse you politely with the same publickey message.
After any config edit:
sudo sshd -t && sudo systemctl reload ssh # Debian/Ubuntu service name may be ssh or sshd
Keep the working session open. Always.
StrictModes: ownership and permissions
StrictModes is on by default in sshd. It checks the file modes and ownership of the user’s files and home directory before accepting a login (see sshd_config(5)). That covers the home directory, ~/.ssh and authorized_keys. If one of them is writable by group or others, or owned by someone other than the user (or root), sshd ignores the key and logs something like Authentication refused: bad ownership or modes for directory /home/alice. Run these on the server, replacing alice with the affected user:
sudo journalctl -u ssh -u sshd | grep -i 'bad ownership'
namei -l /home/alice/.ssh/authorized_keys
sudo chmod go-w /home/alice
sudo chown -R alice:alice /home/alice/.ssh
sudo chmod 700 /home/alice/.ssh
sudo chmod 600 /home/alice/.ssh/authorized_keys
On Debian and Ubuntu the service is ssh; on RHEL-family systems it is sshd. The first command lists both.
Client config footguns
Your ~/.ssh/config might be “helping”:
Host prod
HostName 203.0.113.10
User deploy
IdentityFile ~/.ssh/prod_deploy
IdentitiesOnly yes
People SSH to the raw IP and wonder why the Host block never applies. Or they have a corporate jump host ProxyJump pointing somewhere dead.
Also check file modes on the private key (chmod 600). ssh will refuse overly open private keys.
Windows clients and WSL
From PowerShell/OpenSSH on Windows, paths and permissions differ. WSL keys are not automatically your Windows keys. If you dual-wield environments, be explicit with -i.
Windows admins bouncing between stacks sometimes hit parallel “tool not found” issues — different problem, same discipline: verify the binary and path you’re actually invoking (see also WMIC on Windows 11).
When it worked yesterday
- User’s home moved or NFS home not mounted
- Config management rewrote
authorized_keysfrom an old source of truth - Key rotated on the laptop but not in the vault / cloud metadata
- Someone set
PasswordAuthentication nobefore keys were verified (classic) - Fail2ban banned your IP after too many experiments — check from another network or console
Safe recovery playbook
- Open vendor console / serial.
- Login locally or via cloud user.
- Fix
~/.sshperms andauthorized_keys. - Test from laptop with
-vvvand-i. - Only then tighten hardening (disable passwords, root login) using the hardening checklist.
- Encode the working state in Ansible so the next rebuild doesn’t recreate the outage — that’s the spirit of the DevOps roadmap.
Cloud provider key injection quirks
AWS instance metadata / EC2 user-data, GCP OS Login, Azure `authorized_keys` extensions — each can rewrite or ignore the home-directory file you just edited.
- EC2: the default user gets the key from the key pair at launch. Later key changes are on you (or SSM). Replacing an instance without updating your runbook recreates yesterday’s confusion.
- GCP OS Login: IAM-linked keys may bypass traditional authorized_keys. Debug in the identity path, not only in
~/.ssh. - Packer/golden images: baking a broken cloud.cfg that resets SSH keys on boot will undo your fix every restart.
Always ask: what is the source of truth for keys on this class of host? If the answer is “I edited a file by hand,” config management is your next ticket.
Multiple keys in the agent
ssh will try several identities. Some servers disconnect after N failures (MaxAuthTries). From the client it still looks like publickey denial.
ssh -o IdentitiesOnly=yes -i ~/.ssh/correct_key user@host
Clean the agent when debugging:
ssh-add -D
ssh-add ~/.ssh/correct_key
Match blocks and Conditional access
sshd Match User / Match Address blocks can disable pubkey for a subset of users or force other auth. Read the full effective config:
sudo sshd -T -C user=deploy,host=hostname,addr=203.0.113.10
The -C connection spec makes Match blocks evaluate the way a real login would.
Logging that helps tomorrow
On the server (via console):
sudo tail -n 50 /var/log/auth.log
# or
sudo journalctl -u ssh -u sshd -n 50 --no-pager
Look for “Authentication refused: bad ownership,” “not listed in AllowUsers,” or repeated attempts from the wrong key fingerprint. Increase LogLevel temporarily if needed, then dial it back.
Team process beats heroics
Document onboarding: how to get a key signed/installed, who owns the bastion, how to recover with serial console, and how often to rotate. Put it next to the hardening baseline. New hires shouldn’t invent SSH folklore in a DM.
Certificate-based SSH (short intro)
Teams past a certain size move from raw authorized_keys to SSH certificates (user keys signed by an internal CA for a limited validity window). If your org uses that, “publickey denied” may mean expired cert, wrong principal, or clock skew — not a missing line in authorized_keys.
Ask whether your bastion expects certificates before you rewrite home-directory files for the third time.
FAQ
Can I temporarily enable passwords to get in?
Only via console, only briefly, only if policy allows, and turn it off again after keys work. Don’t open password auth to the world “for five minutes.”
Does changing the SSH port cause this error?
No — wrong port usually times out or says connection refused. Publickey denial means you reached sshd and auth failed.
Why does one laptop work and another doesn’t?
Different keys, agent contents, or ~/.ssh/config. Compare with ssh-keygen -y and -vvv.
Root login denied vs publickey denied?
Different settings. PermitRootLogin no blocks root even with a valid key. Use a sudo user.
Is ed25519 required?
Not required, but preferred for new keys in 2026. Ancient servers may need RSA — upgrade the server when you can.
