How to Fix SSH Permission Denied (publickey): Causes and Fixes (2026)

Oct 31, 2026 · 15 min · Ajay Kumar

This guide is for anyone whose SSH login ends in Permission denied (publickey), whether it is a fresh EC2 instance, a VM you have used for years, or git@github.com. By the end you will know which side is failing (the client never offered the key, the server rejected it, or the client could not sign), and you will have the exact command to fix each cause. Every client and server output below is real: I ran an OpenSSH 10.3p1 client against an unprivileged OpenSSH 10.3p1 sshd on localhost with throwaway keys, and checked flags against the OpenSSH 10.6 manual pages. You need a terminal on the client, and for the server-side steps some other way in: a second working session, the cloud serial console, AWS Session Manager or a rescue volume.

Step 1: Read ssh -vvv to see where authentication fails

The error message is the same for a dozen causes, so start by asking the client what it did. Force one key and filter the noise:

# One key, no agent surprises; change the user, host and key path
ssh -vvv -i ~/.ssh/id_ed25519 -o IdentitiesOnly=yes ubuntu@203.0.113.10 2>&1 \
  | grep -E 'Authenticating to|Will attempt|Offering|Skipping|Server accepts|Authentications that can continue|No more|Permission denied'

A rejected key looks like this (lab output, wrong key on purpose):

debug1: Authenticating to 127.0.0.1:2299 as 'ajaykumar'
debug1: Authentications that can continue: publickey
debug1: Will attempt key: id_wrong ED25519 SHA256:zAp4L+EB3D755ur53jhPwyvlGJmO++pWglIcrDitqMM explicit
debug1: Offering public key: id_wrong ED25519 SHA256:zAp4L+EB3D755ur53jhPwyvlGJmO++pWglIcrDitqMM explicit
debug1: Authentications that can continue: publickey
debug1: No more authentication methods to try.
ajaykumar@127.0.0.1: Permission denied (publickey).

And a working one adds the line that matters:

debug1: Offering public key: id_lab ED25519 SHA256:rJAKtxBDNci9SMlW+Hvr5m6jljOlFs8AEA/57D8qU8U explicit
debug1: Server accepts key: id_lab ED25519 SHA256:rJAKtxBDNci9SMlW+Hvr5m6jljOlFs8AEA/57D8qU8U explicit
Authenticated to 127.0.0.1 ([127.0.0.1]:2299) using "publickey".

Three lines tell you almost everything. Authenticating to ... as 'user' shows the username actually sent. Offering public key with a fingerprint shows which key went out. Server accepts key means the server found that key for that user. Authentications that can continue: publickey with nothing else means the server has passwords disabled, so there is no fallback. Two more client tools help: ssh -G host prints the effective client config after Host and Match blocks, and on OpenSSH 10.5 or newer ssh -Z user@host lists the keys that would be tried, in order.

ssh -vvv -i KEY user@host read the lines after publickey starts Any 'Offering public key' line? no Client never offered it check -i path and key file mode, agent and IdentitiesOnly Steps 3 and 4 or 'Skipping ... not supported' server refuses that key type: SHA-1 ssh-rsa or DSA Step 7 yes 'Server accepts key'? no Server rejected the key wrong user, authorized_keys content or modes, sshd -T, SELinux Steps 2, 5, 6 and 8 yes Accepted, still denied client could not sign: passphrase, file mode, FIDO PIN Step 4
How to route a Permission denied (publickey) failure from the ssh -vvv output to the right step; the log lines are the ones OpenSSH 10.3p1 printed in my lab.

Step 2: Check the username (ec2-user, ubuntu, admin, git)

The most common cause on a new cloud VM is the wrong login name. The key was installed for the image's default user, and you connected as someone else. AWS documents the default usernames: ec2-user for Amazon Linux, Oracle and FreeBSD, ec2-user or root for RHEL and SUSE, ubuntu for Ubuntu, admin for Debian, fedora or ec2-user for Fedora, rocky for Rocky Linux and bitnami for Bitnami. The client gives you no hint; only the server log does:

# sshd log when the client logs in as a user that does not exist (lab output)
Invalid user ubuntu from 127.0.0.1 port 61768
Connection closed by invalid user ubuntu 127.0.0.1 port 61768 [preauth]

For GitHub, GitLab and other Git hosts, every SSH connection uses the user git, never your own username, and you should not run Git with sudo because root has a different ~/.ssh. GitHub's troubleshooting page suggests testing with:

ssh -T git@github.com          # success greets you by your GitHub username
ssh -vT git@github.com         # same, with the debug lines from Step 1
ssh-add -l -E sha256           # compare this fingerprint with the key in your GitHub settings

Step 3: Make the client offer the right key (IdentitiesOnly, agent, MaxAuthTries)

If you have several keys loaded in ssh-agent, the client offers them one by one, and the server's MaxAuthTries (default 6, per sshd_config) runs out before the right key is tried. In the lab I offered six wrong keys before the right one:

Received disconnect from 127.0.0.1 port 2299:2: Too many authentication failures
Disconnected from 127.0.0.1 port 2299
# and in the server log:
maximum authentication attempts exceeded for ajaykumar from 127.0.0.1 port 61763 ssh2 [preauth]

Pin each host to one key in ~/.ssh/config, and check what the agent holds:

# ~/.ssh/config
Host web-1
    HostName 203.0.113.10
    User ubuntu
    IdentityFile ~/.ssh/web-1_ed25519
    IdentitiesOnly yes
ssh -G web-1 | grep -Ei '^(user|hostname|identityfile|identitiesonly) '   # effective client config
ssh-add -l
# The agent has no identities.          <- fresh agent in my lab
ssh-add ~/.ssh/web-1_ed25519
ssh-add -l
# 256 SHA256:rJAKtxBDNci9SMlW+Hvr5m6jljOlFs8AEA/57D8qU8U lab-user (ED25519)   <- after adding my lab key

IdentitiesOnly yes tells the client to use only the configured identity files even if the agent offers more (ssh_config). Servers on OpenSSH 10.6 also let a few key queries not count against MaxAuthTries (PubkeyAuthOptions max-pk-ok, default 6), but you will meet older servers for years.

Step 4: Fix private key permissions and passphrase problems

OpenSSH refuses a private key that other users can read, then carries on and fails with the same generic error:

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@         WARNING: UNPROTECTED PRIVATE KEY FILE!          @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0644 for 'id_lab_open' are too open.
It is required that your private key files are NOT accessible by others.
This private key will be ignored.
Load key "id_lab_open": bad permissions
ajaykumar@127.0.0.1: Permission denied (publickey).
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_ed25519          # AWS uses chmod 400 for downloaded .pem files; both work
chmod 644 ~/.ssh/id_ed25519.pub
ssh-keygen -lf ~/.ssh/id_ed25519      # fingerprint of the private key, to compare in Step 5
# 256 SHA256:rJAKtxBDNci9SMlW+Hvr5m6jljOlFs8AEA/57D8qU8U lab-user (ED25519)
ssh-keygen -y -f ~/.ssh/id_ed25519    # re-derive the public key; prompts for the passphrase

On Windows, file modes are ACLs. AWS's troubleshooting page gives the fix from PowerShell:

icacls.exe $path /reset
icacls.exe $path /GRANT:R "$($env:USERNAME):(R)"
icacls.exe $path /inheritance:r

One subtle case: in the bad-permissions run the server log still showed Accepted key ED25519 ... found at ...authorized_keys:1 followed by Postponed publickey and a closed connection. The server would have let me in; the client could not produce the signature. The same pattern appears with a mistyped passphrase, an agent that refuses to sign, or a FIDO security key that was not touched or whose PIN was not entered.

Step 5: Fix authorized_keys on the server

Now look at the server side, as the target user. Two things must be true: the key is in the file, and the file and every directory above it are owned by the user (or root) and not writable by group or others. The sshd manual says that if authorized_keys, ~/.ssh or the home directory is writable by other users, sshd ignores the file unless StrictModes is off (do not turn it off).

ls -ld ~ ~/.ssh ~/.ssh/authorized_keys
namei -l ~/.ssh/authorized_keys          # owner and mode of every directory on the path
ssh-keygen -lf ~/.ssh/authorized_keys    # one fingerprint per key; compare with Step 4
# 256 SHA256:rJAKtxBDNci9SMlW+Hvr5m6jljOlFs8AEA/57D8qU8U lab-user (ED25519)
# 256 SHA256:zAp4L+EB3D755ur53jhPwyvlGJmO++pWglIcrDitqMM wrong-key (ED25519)

# Fix ownership and modes
chmod go-w ~
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
chown -R "$(id -un):$(id -gn)" ~/.ssh

My lab hit this by accident: I pointed AuthorizedKeysFile at a file under /tmp, which is world-writable, and every login failed with this log line until I moved the file:

Authentication refused: bad ownership or modes for directory /private/tmp

When adding a key, keep it to one line, with the type, the base64 blob and an optional comment. Keys exported from PuTTYgen in RFC 4716 format (---- BEGIN SSH2 PUBLIC KEY ----) must be converted first:

ssh-copy-id -i ~/.ssh/id_ed25519.pub ubuntu@203.0.113.10          # if another login method still works
ssh-keygen -i -f key-from-puttygen.pub >> ~/.ssh/authorized_keys   # RFC 4716 to OpenSSH, one line

On Windows Server with OpenSSH, members of the local Administrators group do not use their profile's authorized_keys at all: Microsoft's key management page puts their keys in C:\ProgramData\ssh\administrators_authorized_keys, readable only by Administrators and SYSTEM.

Step 6: Read the sshd log and the effective config with sshd -T

The server log names the cause in plain words, so read it before you edit anything:

sudo journalctl -u ssh -n 50 --no-pager     # Debian and Ubuntu (unit is ssh)
sudo journalctl -u sshd -n 50 --no-pager    # RHEL, Rocky, Fedora, Amazon Linux
sudo tail -n 50 /var/log/auth.log           # Debian/Ubuntu syslog; /var/log/secure on RHEL

Next, ask sshd what configuration it really applies to your connection. sshd -T prints the effective config, and -C evaluates Match blocks for a given user, host and address. Output names are lower case up to 10.3 and mixed case from 10.4 (completed in 10.5), so grep case-insensitively:

sudo sshd -T -C user=deploy,host=laptop,addr=203.0.113.10 \
  | grep -Ei '^(pubkeyauthentication|authorizedkeysfile|authenticationmethods|allowusers|allowgroups|denyusers|denygroups|pubkeyacceptedalgorithms|strictmodes) '

With a lab config containing Match User deploy and AuthorizedKeysFile /etc/ssh/keys/%u, two users get two different answers:

$ sshd -T -f sshd_match.conf -C user=deploy,host=laptop,addr=203.0.113.10 | grep -i ^authorizedkeysfile
authorizedkeysfile /etc/ssh/keys/%u
$ sshd -T -f sshd_match.conf -C user=ubuntu,host=laptop,addr=203.0.113.10 | grep -i ^authorizedkeysfile
authorizedkeysfile .ssh/authorized_keys .ssh/authorized_keys2

The other trap is ordering. For each keyword, sshd uses the first value it reads, and Debian and Ubuntu Include /etc/ssh/sshd_config.d/*.conf near the top of the main file. A drop-in therefore beats anything you write lower down (lab config, path shortened and the HostKey line omitted):

$ cat d/10-legacy.conf
PubkeyAuthentication no
$ cat sshd_include.conf
Include /path/to/d/*.conf
PubkeyAuthentication yes
$ sshd -T -f sshd_include.conf | grep -i ^pubkeyauthentication
pubkeyauthentication no

Fix the file that wins, then validate and reload with sudo sshd -t && sudo systemctl reload ssh (or sshd), keeping your current session open until a new one works. Recent Ubuntu releases start sshd through socket activation, and a change to Port or ListenAddress also needs systemctl daemon-reload and a restart of ssh.socket (Launchpad bug 2069041).

Step 7: Fix key type errors (ssh-rsa SHA-1 and DSA)

OpenSSH 8.8 disabled RSA signatures with SHA-1 (ssh-rsa) by default, and 10.0 removed DSA completely (release notes). A modern client talking to a server that only accepts ssh-rsa silently skips the key:

debug1: kex_ext_info_client_parse: server-sig-algs=<ssh-rsa>
debug1: Skipping ssh-rsa key id_rsa_lab - corresponding algorithm not supported by server
debug1: No more authentication methods to try.
ajaykumar@127.0.0.1: Permission denied (publickey).

Older servers that do not advertise server-sig-algs produce send_pubkey_test: no mutual signature algorithm instead; a server that rejects what the client sends logs userauth_pubkey: signature algorithm ssh-rsa not in PubkeyAcceptedAlgorithms. The real fix is a new Ed25519 key. As a stopgap for one legacy host, the 8.8 notes suggest:

# ~/.ssh/config, temporary, one host only
Host old-host
    HostkeyAlgorithms +ssh-rsa
    PubkeyAcceptedAlgorithms +ssh-rsa

DSA keys have no stopgap on OpenSSH 10: ssh-keygen -t dsa returns unknown key type dsa. Generate a replacement with ssh-keygen -t ed25519 and install its public half.

Step 8: Fix SELinux labels on RHEL, Rocky and Fedora

If modes and content are right on a RHEL-family host and sshd still ignores the file, check the SELinux label. Files copied or created by some tools end up with a type sshd may not read, and Red Hat's knowledge base describes restorecon fixing exactly this:

getenforce                                   # Enforcing means labels matter
ls -Z ~/.ssh/authorized_keys                 # expect ...:ssh_home_t:...
sudo ausearch -m AVC -ts recent | grep sshd  # denials, if auditd is running
sudo restorecon -Rv ~/.ssh

# Home directories outside /home: label them like /home, then relabel
sudo semanage fcontext -a -e /home /srv/home
sudo restorecon -Rv /srv/home

Step 9: Re-inject a key on EC2, Google Cloud or Azure

Cloud images install your key once, at first boot, for the default user. If that key is lost or the file was damaged, use the provider's side channel instead of guessing.

On AWS, EC2 Instance Connect pushes a public key into instance metadata for 60 seconds (the instance needs the EC2 Instance Connect package), and Session Manager needs no SSH at all:

aws ec2-instance-connect send-ssh-public-key \
  --region us-west-2 --availability-zone us-west-2b \
  --instance-id i-0123456789abcdef0 --instance-os-user ec2-user \
  --ssh-public-key "file://$HOME/.ssh/id_ed25519.pub"
ssh -o IdentitiesOnly=yes -i ~/.ssh/id_ed25519 ec2-user@ec2-198-51-100-1.compute-1.amazonaws.com

aws ssm start-session --target i-0123456789abcdef0   # needs SSM Agent and the Session Manager plugin

On Google Cloud, OS Login decides everything: when it is enabled, metadata SSH keys are ignored and you need roles/compute.osLogin or roles/compute.osAdminLogin. Google's SSH troubleshooting page documents a built-in checker:

gcloud compute ssh my-vm --zone us-central1-a --troubleshoot
gcloud compute instances describe my-vm --zone us-central1-a | grep -A1 enable-oslogin
gcloud compute project-info describe | grep -A1 enable-oslogin
gcloud compute os-login describe-profile      # keys in your OS Login profile

On Azure, the VMAccess extension resets keys and sshd configuration from the CLI. If you sign in with Microsoft Entra ID, a missing Virtual Machine Administrator Login or User Login role also returns Permission denied (publickey).

az vm user update --resource-group myResourceGroup --name myVM \
  --username azureuser --ssh-key-value "$(cat ~/.ssh/id_ed25519.pub)"
az vm user reset-ssh --resource-group myResourceGroup --name myVM

Troubleshooting

Symptom Likely cause Fix
No Offering public key line in ssh -vvv Wrong -i path, key not loaded, or IdentityFile not matched Check ssh -G host, then ssh-add -l; pass -i with IdentitiesOnly=yes
UNPROTECTED PRIVATE KEY FILE! Private key readable by group or others chmod 600 the key (icacls on Windows)
Too many authentication failures Agent offered too many keys before the right one IdentitiesOnly yes and one IdentityFile per host
Server log: Invalid user ubuntu Wrong login name for the image Use the image default user (ec2-user, ubuntu, admin) or git for Git hosts
Server log: Authentication refused: bad ownership or modes Home, ~/.ssh or authorized_keys writable by others or wrong owner chmod go-w ~, chmod 700 ~/.ssh, chmod 600 ~/.ssh/authorized_keys, chown
Server log: Failed publickey ... SHA256:... That fingerprint is not in the file sshd reads Compare ssh-keygen -lf on both sides; check authorizedkeysfile in sshd -T
Server log: not allowed because not listed in AllowUsers AllowUsers or AllowGroups excludes the user Fix the directive, sshd -t, reload
Skipping ssh-rsa key ... not supported by server SHA-1 ssh-rsa on one side only New Ed25519 key; per-host +ssh-rsa only as a stopgap
Server log: Accepted key then Postponed publickey, then disconnect Client could not sign: passphrase, permissions, agent or FIDO PIN Fix the client key (Step 4); touch the security key
Correct modes on RHEL, still ignored SELinux label is not ssh_home_t restorecon -Rv ~/.ssh; semanage fcontext for non-standard homes

FAQ

Why does SSH say Permission denied (publickey) when my key is in authorized_keys?

Usually because sshd is reading a different file or ignoring this one. Check the username you sent, run sshd -T -C user=... to see the effective AuthorizedKeysFile, and read the server log for bad ownership or modes. On RHEL-family hosts, also check the SELinux label.

What permissions should .ssh and authorized_keys have?

Use 700 for ~/.ssh and 600 for authorized_keys and private keys, all owned by the user. The home directory must not be group- or world-writable; 755 or 750 is fine. sshd checks for write access by others, so a 644 authorized_keys still works, but 600 is the recommended setting.

What is the default SSH username for EC2 and other cloud images?

On EC2 it is ec2-user for Amazon Linux, ubuntu for Ubuntu, admin for Debian, rocky for Rocky Linux and ec2-user or root for RHEL. On Azure it is the admin username set when the VM was created. On Google Cloud it is your OS Login username when OS Login is enabled, otherwise the username attached to the metadata key.

How do I see which SSH key the client is offering?

Run ssh -vvv user@host and look for Offering public key lines with fingerprints, or on OpenSSH 10.5 and newer run ssh -Z user@host to list the keys in order. Compare those fingerprints with ssh-keygen -lf ~/.ssh/authorized_keys on the server.

When I get this error in production, I check two things before touching anything: the Authenticating to ... as line on the client, and the last ten lines of the sshd journal on the server, because between them they usually name the cause. Then I fix it in configuration management rather than by hand, so the next host does not repeat it. The official references are the OpenSSH manuals for ssh, ssh_config, sshd and sshd_config, the OpenSSH release notes, and your provider's page for EC2, Google Cloud or Azure. For hardening once you are back in, see SSH security best practices for 2026; and if the key you are rotating ever touched a Git repository, read how to stop secrets leaking into Git before you push again.

Related: SSH Security Best Practices 2026, How to Stop Secrets Leaking Into Git and How to Troubleshoot Linux Performance in 60 Seconds.

I'm Ajay Kumar — I build and operate PandaStack, an open-source Firecracker microVM cloud for AI agents, and write about the infrastructure and tooling around it.

Need this kind of infrastructure work? See what I do or email hello@ajayk.sh.


Related