TLS & Security Error Guide: 'UNPROTECTED PRIVATE KEY FILE!' (SSH Key Permissions)
Fix SSH 'Permissions are too open' / UNPROTECTED PRIVATE KEY FILE: set 0600 on keys, fix directory modes and ownership, and resolve bad permissions on the server.
- #security
- #troubleshooting
- #errors
- #ssh
Overview
SSH refuses to use a private key whose file permissions are too permissive, because a key readable by other users on the system is effectively compromised. When a private key is world- or group-readable (e.g. mode 0644), the OpenSSH client ignores it and aborts the authentication that depended on it. The same protection applies on the server side to authorized_keys, host keys, and the .ssh directory itself: if those are writable by others, sshd distrusts them and rejects the login.
The client-side error is unmistakable:
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: UNPROTECTED PRIVATE KEY FILE! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0644 for '/home/deploy/.ssh/id_ed25519' are too open.
It is required that your private key files are NOT accessible by others.
This private key will be ignored.
Load key "/home/deploy/.ssh/id_ed25519": bad permissions
deploy@app-03: Permission denied (publickey).
On the server side it surfaces only in the auth log as Authentication refused: bad ownership or modes. The fix is precise file modes and ownership — not loosening anything, but tightening to the modes OpenSSH requires.
Symptoms
sshprints “UNPROTECTED PRIVATE KEY FILE!” / “Permissions 0644 … are too open” and falls back to “Permission denied (publickey)”.- Key auth works for one user but not another who copied the key with loose modes.
- On the server, login is refused and
/var/log/auth.logshows “Authentication refused: bad ownership or modes for file …”. - A key copied via a shared mount or extracted from a tarball inherited group/other read bits.
ls -l ~/.ssh/id_ed25519
ssh -v deploy@app-03.internal.example.com 2>&1 | grep -iE 'bad permissions|too open|Permission denied'
-rw-r--r-- 1 deploy deploy 411 Jun 23 13:40 /home/deploy/.ssh/id_ed25519
Load key "/home/deploy/.ssh/id_ed25519": bad permissions
deploy@app-03.internal.example.com: Permission denied (publickey).
Common Root Causes
1. Private key mode too open (group/other readable)
The key is 0644, 0640, or 0664 instead of 0600.
stat -c '%a %U:%G %n' ~/.ssh/id_ed25519
644 deploy:deploy /home/deploy/.ssh/id_ed25519
Any read bit beyond the owner makes OpenSSH ignore the key.
2. Wrong ownership on the key or .ssh directory
The key is owned by a different user (often root after a sudo cp), so the connecting user cannot use it safely.
stat -c '%a %U:%G %n' ~/.ssh ~/.ssh/id_ed25519
700 deploy:deploy /home/deploy/.ssh
600 root:root /home/deploy/.ssh/id_ed25519
A root-owned key in a user’s .ssh is refused.
3. .ssh directory itself too permissive
The directory is 0755/0777, letting others traverse or modify it.
stat -c '%a %n' ~/.ssh
777 /home/deploy/.ssh
A world-writable .ssh is distrusted even if the key mode is correct.
4. Server-side: authorized_keys writable by group/other
sshd refuses keys when ~/.ssh/authorized_keys or ~/.ssh is writable by anyone but the owner.
sudo grep -i 'Authentication refused' /var/log/auth.log | tail -2
stat -c '%a %U:%G %n' ~/.ssh ~/.ssh/authorized_keys
Authentication refused: bad ownership or modes for directory /home/deploy/.ssh
755 deploy:deploy /home/deploy/.ssh
644 deploy:deploy /home/deploy/.ssh/authorized_keys
With StrictModes yes (the default), a group-writable .ssh blocks login.
5. Home directory writable by group/other
Even with perfect .ssh modes, a group-writable $HOME causes sshd to reject keys under StrictModes.
stat -c '%a %n' /home/deploy
775 /home/deploy
sshd walks the path and refuses if $HOME is writable by others.
6. Key copied via shared mount / tarball that stripped owner-only modes
A key pulled from a CI artifact, NFS share, or unzip/tar extraction lands with default 0644.
ls -l ./deploy_key
-rw-r--r-- 1 ci ci 411 Jun 23 13:55 ./deploy_key
The artifact’s permissions did not preserve 0600.
Diagnostic Workflow
Step 1: Read the exact complaint (client) or auth log (server)
ssh -v <user>@<host> 2>&1 | grep -iE 'bad permissions|too open|bad ownership'
# server
sudo grep -iE 'Authentication refused|bad ownership or modes' /var/log/auth.log | tail
Step 2: Inspect modes and ownership along the whole path
stat -c '%a %U:%G %n' "$HOME" ~/.ssh ~/.ssh/id_ed25519 ~/.ssh/authorized_keys 2>/dev/null
Look for anything wider than: $HOME not group/other-writable, .ssh 700, private key 600, authorized_keys 600 (or 644 but never group/other-writable).
Step 3: Confirm StrictModes on the server (if server-side)
sudo sshd -T 2>/dev/null | grep -i strictmodes
strictmodes yes
With strictmodes yes, loose $HOME/.ssh/authorized_keys modes block login.
Step 4: Fix client-side key permissions
chown "$USER":"$USER" ~/.ssh ~/.ssh/id_ed25519
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_ed25519 ~/.ssh/id_ed25519.pub 2>/dev/null
chmod 644 ~/.ssh/id_ed25519.pub
Step 5: Fix server-side permissions and retry
chmod go-w "$HOME"
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
chown -R "$USER":"$USER" ~/.ssh
ssh <user>@<host> 'echo connected'
connected
Example Root Cause Analysis
A CI deploy job fails to SSH to production with:
@@@ WARNING: UNPROTECTED PRIVATE KEY FILE! @@@
Permissions 0644 for '/builds/deploy/id_ed25519' are too open.
This private key will be ignored.
Permission denied (publickey).
The key value is correct and the corresponding public key is in the server’s authorized_keys, so this is purely a permissions problem. Checking how the key arrived in the job:
stat -c '%a %U:%G %n' /builds/deploy/id_ed25519
644 root:root /builds/deploy/id_ed25519
The CI step writes the key from a secret into a file, but the runner creates files with the default umask, leaving them 0644. OpenSSH refuses the world-readable key and never even attempts publickey auth, falling straight to “Permission denied.”
Fix: tighten the mode immediately after writing the key (and avoid leaving it on disk longer than needed):
install -m 600 /dev/null /builds/deploy/id_ed25519 # create with 600 up front
# or after writing:
chmod 600 /builds/deploy/id_ed25519
ssh -i /builds/deploy/id_ed25519 deploy@app-03.internal.example.com 'echo connected'
connected
The deploy authenticates. The pipeline is updated to always write the key with 0600.
Prevention Best Practices
- Standardize the canonical modes:
$HOMEnot group/other-writable,~/.ssh700, private keys600, public keys644,authorized_keys600. - In automation, write secret key files with
install -m 600(orumask 077before the write) so they are never momentarily world-readable. - Never
chmod 644/777a key or.sshto “make it work” — that is the exact condition OpenSSH is protecting against; tighten instead. - Keep
StrictModes yeson servers and fix the offending modes rather than disabling the check. - Prefer an SSH agent or short-lived SSH certificates over long-lived key files on disk where possible. See the security hardening guides for an SSH key-handling baseline.
- When a deploy suddenly loses key auth, the free incident assistant can map “bad permissions”/“bad ownership or modes” messages to the exact file and mode to correct.
Quick Command Reference
# Client-side complaint or server-side auth log
ssh -v <user>@<host> 2>&1 | grep -iE 'bad permissions|too open'
sudo grep -iE 'Authentication refused|bad ownership or modes' /var/log/auth.log | tail
# Inspect modes/ownership along the path
stat -c '%a %U:%G %n' "$HOME" ~/.ssh ~/.ssh/id_ed25519 ~/.ssh/authorized_keys 2>/dev/null
# Is StrictModes on? (server)
sudo sshd -T 2>/dev/null | grep -i strictmodes
# Correct client-side key modes
chmod 700 ~/.ssh; chmod 600 ~/.ssh/id_ed25519; chmod 644 ~/.ssh/id_ed25519.pub
chown -R "$USER":"$USER" ~/.ssh
# Correct server-side modes
chmod go-w "$HOME"; chmod 700 ~/.ssh; chmod 600 ~/.ssh/authorized_keys
# Write a secret key file safely in CI
install -m 600 /dev/null ./deploy_key # then write contents
Conclusion
“UNPROTECTED PRIVATE KEY FILE!” / “Permissions are too open” / “bad ownership or modes” all mean OpenSSH found a key or directory accessible to users beyond the owner and refused to trust it. Tighten, never loosen:
- The private key is group/other-readable — set it to
0600. - The key or
.sshis owned by the wrong user — fix ownership to the connecting user. - The
.sshdirectory is too permissive — set it to0700. - Server-side
authorized_keys/.sshis group/other-writable —sshdrefuses it underStrictModes. - A group/other-writable
$HOMEblocks key auth on the server. - A key copied via a shared mount, tarball, or CI artifact inherited
0644.
Walk the modes and ownership from $HOME down to the key, set the canonical permissions, and in automation create secret files with 0600 from the start — the protection only works when you tighten to the modes OpenSSH expects.
Download the Free 500-Prompt DevOps AI Toolkit
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.