You add a public key, run ssh, and get the shortest possible answer:
alice@server.example: Permission denied (publickey).The client can't tell you why the server rejected the key. That's deliberate, because it shouldn't give details to an attacker. The server does log the reason, though. Most guides for SSH Permission denied (publickey) have you work through a list of checks on the client. This guide starts on the server: find the log line that explains the rejection, match it to its cause, fix it, and confirm the fix. It's written for Debian 13 (trixie), which ships openssh-server 1:10.0p1-7+deb13u4. OpenSSH 10 removed a few legacy options, and some of these errors come from that.
Quick fix: permissions on ~/.ssh
The most common cause is wrong permissions. StrictModes is on by default, and sshd ignores authorized_keys if the file, the ~/.ssh directory or the home directory can be written by anyone except the user. Run this as root on the server, replacing alice with the account you're logging in as:
chown alice:alice /home/alice/.ssh /home/alice/.ssh/authorized_keys
chmod 700 /home/alice/.ssh
chmod 600 /home/alice/.ssh/authorized_keys
chmod go-w /home/alice
namei -l /home/alice/.ssh/authorized_keysIf that doesn't fix it, don't start guessing. Read the journal.
Read the server-side reason in the journal
Since OpenSSH 9.8 the server has been split into two binaries: a listener, sshd, and a per-connection sshd-session. Authentication messages therefore usually show up under sshd-session. Both run inside ssh.service, so you can filter on the unit:
journalctl -u ssh.service -S "15 min ago" --no-pager | grep -E 'refused|not allowed|PubkeyAcceptedAlgorithms|Could not open|unsupported public key'These are the lines you're looking for. I've trimmed timestamps and hostnames, and the wording comes from the OpenSSH 10.0 source:
sshd-session[4121]: Authentication refused: bad ownership or modes for directory /home/alice/.ssh
sshd-session[4188]: userauth_pubkey: signature algorithm ssh-rsa not in PubkeyAcceptedAlgorithms
sshd-session[4203]: User alice from 203.0.113.10 not allowed because not listed in AllowUsersWith the default LogLevel INFO, all three are logged. Some reasons, such as an unsupported key algorithm, are only logged at VERBOSE. If the journal shows only the failed attempt and no reason, use the debug instance described next.
| Log line (fragment) | Cause | Section |
|---|---|---|
Authentication refused: bad ownership or modes for file/directory ... | StrictModes: wrong owner or group/world-writable path | Permissions and ownership |
signature algorithm ssh-rsa not in PubkeyAcceptedAlgorithms | Old client that signs with RSA/SHA-1 | Wrong key type |
signature algorithm rsa-sha2-512 not in PubkeyAcceptedAlgorithms | Hardened drop-in that restricts accepted algorithms | Hardened drop-ins |
unsupported public key algorithm: ssh-dss (VERBOSE) | DSA key, removed in OpenSSH 10.0 | Wrong key type |
Could not open user 'alice' ... (DEBUG if the file is missing; INFO for other errors such as permission denied) | AuthorizedKeysFile points somewhere unexpected, or the key file can't be read | AuthorizedKeysFile |
not allowed because not listed in AllowUsers | AllowUsers/DenyUsers/AllowGroups | AllowUsers and friends |
Lockout-safe testing with sshd -T and sshd -ddd
Keep one root session open for all of the following. The running listener keeps the configuration it loaded until you reload it, so you can edit files and test them without touching the daemon you're logged in through.
First, check the syntax, then print the effective configuration for the exact connection that fails. -C applies any matching Match blocks. Without it, sshd -T can report a different value than the one your login actually gets:
sshd -t
sshd -T -C user=alice,host=client.example,addr=203.0.113.10 | grep -Ei '^(strictmodes|authorizedkeysfile|pubkeyacceptedalgorithms|allowusers|denyusers|allowgroups|port|loglevel) 'Then start a separate debug daemon on a spare port. -d keeps sshd in the foreground and sends debug output to stderr. With -ddd you get the most detail. It handles one connection and then exits. -p overrides any Port from the config files, unless the config uses ListenAddress with an explicit port; in that case also pass -o ListenAddress=0.0.0.0:2222. sshd has to be started with its absolute path:
/usr/sbin/sshd -ddd -p 2222Your firewall must allow port 2222 for this test. Close it again afterwards. From the client, connect to that port with only the key you intend to use:
ssh -vvv -p 2222 -i ~/.ssh/id_ed25519 -o IdentitiesOnly=yes alice@server.exampleThe server terminal will show the exact check that fails. Only after a login on 2222 works should you apply the change to the real daemon, and keep your existing session open while you do:
sshd -t && systemctl reload ssh.serviceTest that a new login works before you close the old session. If Fail2ban is running, your own failed attempts count against you. Either whitelist your address or be ready to unban it, as described in Fail2ban on Debian 13 with nftables.
Diagnosing and fixing each cause
StrictModes: permissions and ownership
For this check, sshd examines the key file and every directory above it up to the home directory. Each one must be owned by the user or root and must not be writable by others. Upstream OpenSSH also rejects any group write bit (the mode must not include 022). Debian relaxes this with its user-group-modes.patch: group write is accepted if the group contains only the file's owner, which matches the private per-user group that adduser creates by default. Group write is still rejected when the group has other members, for example a shared group like users. The log line names the path that failed. namei -l shows mode and owner for each part of the path:
namei -l /home/alice/.ssh/authorized_keysf: /home/alice/.ssh/authorized_keys
drwxr-xr-x root root /
drwxr-xr-x root root home
drwxrwxr-x alice users alice
drwx------ alice alice .ssh
-rw------- alice alice authorized_keysHere the home directory is writable by the shared group users. That's common after chmod -R 775 combined with a chgrp to a shared group, or a restore from a tar archive with the wrong umask and group. Run chmod go-w /home/alice to fix it. Files created by root with echo ... >> end up owned by root, which is accepted, but a file owned by some other user is rejected. Don't set StrictModes no. It turns off a real protection just to hide a single wrong chmod.
Verify: namei -l shows no w for other anywhere on the path, and no w for group unless the group is the user's private group. sshd -T | grep strictmodes still says yes.
Wrong key type: DSA and ssh-rsa
OpenSSH 10.0 (released 2025-04-09) removed DSA support entirely. Debian had already disabled it at compile time in 1:9.8p1-1, and the trixie release notes say DSA keys are no longer supported, whatever the config says. Since OpenSSH 8.8, RSA signatures using SHA-1 (ssh-rsa) have been disabled by default. RSA keys themselves still work with rsa-sha2-256 and rsa-sha2-512.
| Key / signature | OpenSSH 10.0 on Debian 13 default |
|---|---|
DSA (ssh-dss) | Not supported, can't be re-enabled |
RSA with SHA-1 (ssh-rsa) | Rejected, can be re-enabled as a stopgap |
RSA with rsa-sha2-256/rsa-sha2-512 | Accepted (minimum size RequiredRSASize 1024) |
ECDSA, Ed25519, FIDO sk- keys | Accepted |
To find the key types, list what's in the authorized file on the server and what the client is configured to offer:
ssh-keygen -l -f /home/alice/.ssh/authorized_keys
ssh -G server.example | grep -i pubkeyacceptedalgorithms
(ssh -Q PubkeyAcceptedAlgorithms only lists every algorithm the client binary supports, not what your configuration will actually offer.A current OpenSSH client with an RSA key signs with rsa-sha2-*. If you see signature algorithm ssh-rsa in the log, the client is an old one: an outdated library, an embedded device or legacy tooling. The proper fix is an Ed25519 key on that client (ssh-keygen -t ed25519) or an upgraded client. If you have to keep it running for now, re-enable SHA-1 only for that one source address and remove it later:
Match Address 203.0.113.50
PubkeyAcceptedAlgorithms +ssh-rsaVerify: sshd -T -C user=alice,host=legacy,addr=203.0.113.50 | grep pubkeyacceptedalgorithms includes ssh-rsa, and the same command with another address doesn't.
Hardened drop-ins that drop algorithms
Debian's sshd_config includes /etc/ssh/sshd_config.d/*.conf, and for each keyword sshd uses the first value it reads. A hardening drop-in like the one below will therefore win over anything you add later in the main file:
PubkeyAcceptedAlgorithms ssh-ed25519,sk-ssh-ed25519@openssh.comWith that drop-in, every user with an RSA or ECDSA key is locked out, and the journal shows signature algorithm rsa-sha2-512 not in PubkeyAcceptedAlgorithms. Look for every file that sets the keyword:
grep -rniE '^\s*(pubkeyacceptedalgorithms|authorizedkeysfile|allowusers|allowgroups|denyusers|strictmodes)' /etc/ssh/sshd_config /etc/ssh/sshd_config.d/You have two options. Move the users to Ed25519 keys, or add the RSA algorithms back to the list: PubkeyAcceptedAlgorithms ssh-ed25519,sk-ssh-ed25519@openssh.com,rsa-sha2-512,rsa-sha2-256. For a deliberate baseline instead of copy-pasted lists, see Hardening SSH on Debian 13 with OpenSSH 10.
Verify: sshd -T | grep pubkeyacceptedalgorithms lists the algorithm your client uses. On the client, ssh -G server.example | grep -i pubkeyacceptedalgorithms shows what it will offer.
AuthorizedKeysFile points elsewhere
The default is .ssh/authorized_keys .ssh/authorized_keys2, relative to the home directory. Central setups often change it to something like /etc/ssh/authorized_keys/%u, sometimes inside a Match block, and then the keys users put in their home directory are silently ignored. If the file sshd looks for doesn't exist, which is the usual case here, Could not open user 'alice' ... with the path sshd tried is logged only at debug level, so you'll see it in sshd -ddd output or with LogLevel DEBUG, not in the default journal. At LogLevel INFO the journal shows that line only for other errors, such as permission denied.
sshd -T -C user=alice,host=client.example,addr=203.0.113.10 | grep authorizedkeysfile
namei -l /etc/ssh/authorized_keys/alicePut the key where sshd actually looks, or fix the directive. StrictModes checks apply to that path too.
AllowUsers, DenyUsers and AllowGroups
Once AllowUsers is set, only the listed users can log in. sshd checks the rules in this order: DenyUsers, AllowUsers, DenyGroups, AllowGroups. A user@host pattern also restricts where the user may log in from, and numeric UIDs don't match. The client still just sees Permission denied (publickey). The journal says which rule blocked the login:
User alice from 203.0.113.10 not allowed because not listed in AllowUsers
User alice from 203.0.113.10 not allowed because none of user's groups are listed in AllowGroupsAdd the user or group to the directive that sshd actually uses (the grep -rn above finds it), or add the user to the allowed group with usermod -aG sshusers alice.
Verify: sshd -T -C user=alice,host=client.example,addr=203.0.113.10 | grep -E '^allow(users|groups)'.
ssh.socket: talking to a different daemon than you think
Debian ships an optional ssh.socket unit with ListenStream=22 hard-coded. When the socket is enabled, systemd opens the listening socket itself and passes it to ssh.service, so a Port line in sshd_config doesn't change where the server listens. Admins then connect to the port they configured and end up on something else: a container's sshd, a NAT rule pointing at another host, or a debug instance. That daemon has its own keys and config and rejects the login. Check which unit is active and what owns the port:
systemctl is-enabled ssh.socket ssh.service
ss -tlnp 'sport = :22'Warning: changing the listen port of the service you're connected through can lock you out. Open the new port in the firewall first and keep your current session open. To move a socket-activated sshd, override the socket. An empty ListenStream= resets the port list:
systemctl edit ssh.socket[Socket]
ListenStream=
ListenStream=2200systemctl daemon-reload
systemctl restart ssh.socket
ss -tlnp 'sport = :2200'If you don't need socket activation, the simpler choice is to use only ssh.service and let Port in sshd_config decide.
Verify: ssh -G server.example | grep -E '^(hostname|port|user|identityfile) ' on the client matches the daemon you expect.
Client side: ssh -vvv and ssh -G
Check the client only after the server has told you what it rejected. ssh -G prints the effective client config after Host and Match blocks are applied. That catches a wrong User, Port or IdentityFile from ~/.ssh/config. ssh -vvv shows which keys are offered:
ssh -G server.example | grep -E '^(user|port|hostname|identityfile|identitiesonly) '
ssh -vvv server.example 2>&1 | grep -E 'Offering|Authentications that can continue'If the key you expect never shows up as offered, the problem is on the client: wrong path, a key the client can't load (for example DSA), or an agent that offers other keys first.
Preventing the error
- Create key directories with the correct mode from the start:
install -d -m 700 -o alice -g alice /home/alice/.ssh. - Run
sshd -tandsshd -T -C ...for a real user before every reload, including from config management. - Keep algorithm restrictions in a single, named drop-in and document the key types it excludes.
- Switch users away from DSA and SHA-1-only clients before the upgrade, not after.
- Decide between
ssh.socketandssh.serviceand record the choice. Also back up/etc/ssh. The restic setup on Debian 13 handles that with an append-only repository.
Checklist
- Keep a root session open.
journalctl -u ssh.service: find the reason line.Authentication refused: fix the path shown and check withnamei -l.not in PubkeyAcceptedAlgorithms: new Ed25519 key, or fix the drop-in, then check withsshd -T.not allowed because: fix AllowUsers/AllowGroups, then check withsshd -T -C user=....Could not open user(journal for read errors, debug output for a missing file): checkauthorizedkeysfileinsshd -T.- No reason logged: run
/usr/sbin/sshd -ddd -p 2222andssh -vvv -p 2222. - Wrong port or daemon:
systemctl is-enabled ssh.socket,ss -tlnp,ssh -G. sshd -t && systemctl reload ssh.service, then test a new login before logging out.
Sources
- sshd_config(5) — openssh-server — Debian trixie
- sshd(8) — openssh-server — Debian trixie
- ssh(1) — openssh-client — Debian trixie
- namei(1) — util-linux — Debian trixie
- systemd.socket(5) — Debian trixie
- Debian package openssh-server in trixie
- Debian openssh 1:10.0p1-7+deb13u4 debian/systemd/ssh.socket
- Debian openssh 1:10.0p1-7+deb13u4 debian/patches/user-group-modes.patch
- Debian openssh changelog
- Debian 13 release notes: Issues to be aware of for trixie
- OpenSSH Release Notes
- OpenSSH 8.8 release notes
- openssh-portable V_10_0_P1 auth.c
- openssh-portable V_10_0_P1 auth2-pubkey.c
- openssh-portable V_10_0_P1 auth2-pubkeyfile.c
- openssh-portable V_10_0_P1 misc.c
- SSH Ignores Config? - Linux Containers Forum
Comments