The line, and the detail everyone misreads
sh
$ sftp alice@files.example.com
alice@files.example.com: Permission denied (publickey).That message is generated in one place in OpenSSH's client, and its format string is worth seeing:
text
fatal("%s@%s: Permission denied (%s).",
authctxt->server_user, authctxt->host, authlist);The last field is authlist — the list of authentication methods the server is still willing to accept, not the one that failed. So Permission denied (publickey) means: I tried everything I had, the server only offers public-key authentication, and my key was not accepted. If it said (publickey,password) you would have a password fallback; because it does not, there is no point looking for one.
| What you see | Cause | Fix |
|---|---|---|
Fails instantly, -v shows no "Offering public key" | Client never found or offered the key | -i, IdentitiesOnly yes |
-v offers the key, server still refuses | Key not in authorized_keys, or wrong user | Check the file for that exact account |
| Works for one user, not another | Permissions on ~, ~/.ssh, authorized_keys | 700 on the directory, 600 on the file |
-v shows "no mutual signature algorithm" | ssh-rsa disabled since OpenSSH 8.8 | New key type, or re-enable per host |
| Server log says "invalid format" | Wrong key file: .ppk, or a public key as private | Convert to OpenSSH format |
| Only administrator accounts fail on Windows | administrators_authorized_keys not used | Write that file, fix its ACL |
| Fails only in a script or on a jump host | No agent, or agent not forwarded | ssh-add, ForwardAgent deliberately |
1. Read the verbose log first
Everything below is guesswork until you have seen the client's own account of the handshake.
sh
ssh -v alice@files.example.com
sftp -v alice@files.example.comThree lines carry the answer, and all three are literal strings in OpenSSH's source. Authentications that can continue: publickey is the server's method list. Offering public key: /Users/alice/.ssh/id_ed25519 means the client actually sent that key — if no such line appears, the problem is entirely client-side. And no mutual signature algorithm is the algorithm mismatch covered in step 4.
On the server, raise the log level in sshd_config: LogLevel accepts QUIET, FATAL, ERROR, INFO, VERBOSE, DEBUG, DEBUG1, DEBUG2, DEBUG3, with INFO as the default. VERBOSE is enough to log key fingerprints; the manual warns that logging at DEBUG level "violates the privacy of users and is not recommended".
2. Fix the permissions, both ends
This is the most common server-side cause and the least visible, because a correct key in a world-writable directory is silently ignored. The sshd manual is explicit: for ~/.ssh "the recommended permissions are read/write/execute for the user, and not accessible by others", and for ~/.ssh/authorized_keys "the recommended permissions are read/write for the user, and not accessible by others". The reason follows: "If this file, the ~/.ssh directory, or the user's home directory are writable by other users, then the file could be modified or replaced by unauthorized users", and in that case "sshd will not allow it to be used unless the StrictModes option has been set to 'no'". StrictModes defaults to yes.
sh
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
chmod go-w ~
ls -ld ~ ~/.ssh ~/.ssh/authorized_keysDo the same on the client: a private key readable by others is refused by ssh itself, chmod 600 ~/.ssh/id_ed25519.
3. Check the file, the format and the username
authorized_keys is one key per line, and the manual describes the fields as "options, keytype, base64-encoded key, comment", with the options field optional. Line breaks inserted by a copy-paste — or by a browser downloading the key — split the base64 blob and silently invalidate the entry.
sh
ssh-keygen -lf ~/.ssh/id_ed25519.pub
tail -c 100 ~/.ssh/authorized_keys | cat -A | tail -2Two more things in this family. The key must be in the authorized_keys of the account you are logging in as, which is why sftp root@host fails with a key installed for alice. And AuthorizedKeysFile in sshd_config can point somewhere else entirely — it "may include wildcards" and tokens, so a hardened host may read /etc/ssh/keys/%u instead of the home directory.
4. The algorithm change that broke old RSA keys
If your key worked for years and stopped, this is why. The OpenSSH 8.8 release notes state: "This release disables RSA signatures using the SHA-1 hash algorithm by default. This change has been made as the SHA-1 hash algorithm is cryptographically broken." That removes the ssh-rsa signature algorithm — not RSA keys as such, but the SHA-1 signature made with them.
The current default list in ssh_config for PubkeyAcceptedAlgorithms contains rsa-sha2-512 and rsa-sha2-256 and no ssh-rsa. Ask your own client what it will do:
sh
ssh -Q PubkeyAcceptedAlgorithmsThe documented stopgap, straight from the release notes, re-enables it for one destination — the notes describe it as "only as a stopgap measure until legacy implementations can be upgraded or reconfigured with another key type (such as ECDSA or Ed25519)":
text
Host old-host
HostkeyAlgorithms +ssh-rsa
PubkeyAcceptedAlgorithms +ssh-rsaThe + prefix appends to the default set rather than replacing it; - removes, and ^ puts entries at the head. The better fix is a new key: ssh-keygen -t ed25519.
5. Agent, passphrase and the too-many-keys problem
If the private key is encrypted and nothing prompts you — a cron job, a CI runner, a GUI app — the client has no way to unlock it. Load it into the agent once:
sh
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519
ssh-add -lThe opposite failure exists too. A server with MaxAuthTries at its default of 6 will cut you off before the client reaches the right key if the agent offers a dozen. IdentitiesOnly is the fix: it tells ssh to "only use the configured authentication identity and certificate files … even if ssh-agent or a PKCS11Provider or SecurityKeyProvider offers more identities".
sh
ssh -o IdentitiesOnly=yes -i ~/.ssh/id_ed25519 alice@files.example.comFor jump hosts, prefer ProxyJump over forwarding your agent. If you do forward it, do it per host, never globally.
6. Windows servers have a second key file
This one wastes whole afternoons because the standard advice is wrong on Windows for exactly the accounts you test with. Microsoft's documentation is clear: for an administrative user, the public key goes into a file called administrators_authorized_keys in C:\ProgramData\ssh\, and "This file only applies to administrator accounts. You must use it instead of the user-specific file within the user's profile location." The ACL must allow only administrators and SYSTEM:
powershell
icacls.exe "$env:ProgramData\ssh\administrators_authorized_keys" /inheritance:r /grant "Administrators:F" /grant "SYSTEM:F"Also note, from the same page, that Windows OpenSSH does not support the AuthorizedKeysCommand and AuthorizedKeysCommandUser directives, and that key-based authentication works with local and Active Directory accounts but not Microsoft Entra ID accounts.
Connecting once the key works
With authentication fixed, the remaining question is what you do with the connection. AnyStorage (v0.2.24; macOS 11+, Windows 10+, Ubuntu 20.04+) connects to SFTP hosts with key authentication alongside S3-compatible endpoints and WebDAV, in one window, and the free tier allows two connections. When you want the host as a drive rather than a file list, it mounts through a built-in local WebDAV server at http://127.0.0.1:3211 — no FUSE, no WinFsp, no macFUSE, which also means no sshfs to debug on top of the key problem you just solved.
Does (publickey) mean my key is wrong?
It means the server will only accept public-key authentication and nothing you offered satisfied it. That includes "you offered nothing", which is why ssh -v and the "Offering public key" line are the first check.
Why does ssh work but sftp fail?
Same authentication, different service. If keys work for ssh and sftp fails later, look at the Subsystem line in sshd_config — sftp-server or internal-sftp — and at ChrootDirectory, whose ownership sshd checks unconditionally.
Can I still use my old RSA key?
Yes, if it is a genuine RSA key of adequate length: the modern rsa-sha2-256 and rsa-sha2-512 signature algorithms use the same key. Only the SHA-1 ssh-rsa signature is disabled by default.
Is a .ppk file usable?
Not directly. PuTTY's format is its own; export the key with PuTTYgen's Conversions menu to an OpenSSH private key, or generate a new pair with ssh-keygen.
Next steps
- Turn a working SFTP login into a drive: mount SFTP as a network drive
- Desktop clients compared: FTP and SFTP client
- The mount errors on the other road: rclone mount FUSE errors