🍱 Lunchbox Hands

ssh

"Permissions 0644 Are Too Open": Fix the SSH Private Key Warning

OpenSSH refuses any private key readable by group or others. Fix: chmod 600. If chmod 'does nothing', you're on a Windows drive under WSL.

You ran ssh and got a wall of @ signs instead of a shell. The fix is one line: chmod 600 ~/.ssh/id_rsa (or whatever file the warning names), and OpenSSH will use the key again. If that line runs without error and the warning comes back unchanged, skip to the WSL section. Your key is on a Windows drive, and chmod there is mostly a no-op.

Here’s the full message (the @ block comes from OpenSSH’s authfile.c):

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@         WARNING: UNPROTECTED PRIVATE KEY FILE!          @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0644 for '/home/you/.ssh/id_rsa' 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/you/.ssh/id_rsa": bad permissions

The number after Permissions is your file’s actual mode, so you may see 0640, 0664 or 0777 instead of 0644. The fix is the same for all of them. This post sticks to SSH files. For what the three digits mean in general, read chmod 755 vs 644 first.

The one-line fix

chmod 600 ~/.ssh/id_ed25519      # or id_rsa, or my-server.pem
ssh -i ~/.ssh/id_ed25519 user@host

chmod 400 works too. That’s what AWS tells you to run on a downloaded .pem file, and it’s fine. The only difference is that 400 also stops you from overwriting the key by accident. SSH accepts both.

If you’d rather not memorize octal, the symbolic form says the same thing in words:

chmod u=rw,go= ~/.ssh/id_ed25519   # owner read+write, group and others nothing

What OpenSSH actually checks

The check is short enough to quote. In sshkey_perm_ok():

if ((st.st_uid == getuid()) && (st.st_mode & 077) != 0) {

077 is a mask covering every group and other bit. If any of those six bits is set, the key is rejected. Read, write or execute, it doesn’t matter. Owner bits are never looked at. Here’s what that means for common modes:

ModeSymbolicmode & 077Result
600rw-------0Accepted
400r--------0Accepted
700rwx------0Accepted (odd, but legal)
640rw-r-----040Rejected
604rw----r--004Rejected
610rw---x---010Rejected, for one execute bit
644rw-r--r--044Rejected
777rwxrwxrwx077Rejected

The 610 row surprises people. A group execute bit on a text file grants nothing useful, but OpenSSH doesn’t try to reason about which bits matter. Anything beyond the owner is a fail.

There’s one more detail in that if. The check only runs when you own the file. The source comment says it plainly: if the key is owned by a different user, “we don’t care.” So if you copied a key with sudo cp, it’s now owned by root. You won’t see this warning. If the copy is 600 you’ll get a plain permission-denied error. If it’s 644, ssh silently uses a key that everyone can read. Fix that with sudo chown "$USER": ~/.ssh/id_ed25519, then chmod.

Why OpenSSH is right to refuse

A private key is your password, in file form. Mode 644 means every account on the machine can read it: other users, a web server running as www-data, a compromised service. Any of them can copy it and log in as you, wherever that key is trusted. OpenSSH could warn you and carry on. It refuses instead, because a key that has been readable by others has to be assumed copied.

The usual way a key ends up 644 isn’t carelessness. It’s how the file got there:

  • You downloaded it. A downloaded file usually gets your default mode, which is 644 on most systems. Cloud-console .pem downloads and keys from a browser generator land this way. Why new files are 644 explains the mechanism.
  • You pasted it into an editor and saved a new file. Same umask, same 644.
  • You restored it from a zip, a USB stick, or a sync folder that doesn’t keep Unix modes.

All three are fixed by the same chmod. Keys made by ssh-keygen don’t start out this way, because it switches the umask to 077 while it writes the private key.

The correct mode for every ~/.ssh file

FileModeSymbolicWho enforces it
~/.ssh/id_ed25519 (private key)600rw-------ssh client: rejects any group or other bit
~/.ssh/id_ed25519.pub (public key)644rw-r--r--Nobody. It’s public, 644 is fine
~/.ssh (directory)700rwx------sshd on the server (StrictModes)
~/.ssh/authorized_keys600rw-------sshd on the server (StrictModes)
~/.ssh/config600rw-------ssh client: fatal error if group or others can write

Fix a whole directory at once:

chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_* ~/.ssh/config ~/.ssh/authorized_keys 2>/dev/null
chmod 644 ~/.ssh/*.pub

The order matters. The second line also matches id_ed25519.pub, so the third line puts the public keys back to 644.

The config row has its own error. If ~/.ssh/config is group- or world-writable, ssh stops before connecting with Bad owner or permissions on /home/you/.ssh/config. The ssh(1) man page explains why: “Because of the potential for abuse, this file must have strict permissions.” A config file can set ProxyCommand, which runs a program. A writable config lets someone else choose that program.

Server side: StrictModes and authorized_keys

Sometimes the client is fine and the server turns the key down. You’ll get Permission denied (publickey) with no hint why. The reason is in the server’s auth log (/var/log/auth.log on Debian and Ubuntu, or journalctl -u ssh):

Authentication refused: bad ownership or modes for directory /home/you/.ssh

That comes from StrictModes. sshd_config(5) describes it as checking “file modes and ownership of the user’s files and home directory before accepting login.” The default is yes. In the source, sshd walks from authorized_keys up through ~/.ssh to your home directory. It refuses the file if any of them is owned by someone other than you (or root), or is writable by group or others (mode & 022).

The server check is about write, not read. A 644 authorized_keys passes, and 600 is just tidier. The classic failures are:

  • Home directory is 775 or 777. Very common on hosts where users share a group. chmod go-w ~ fixes it without breaking anything that only reads from your home directory.
  • ~/.ssh is 775. Use chmod 700 ~/.ssh.
  • authorized_keys is owned by root because you created it with sudo. Use sudo chown you: ~/.ssh/authorized_keys.

Setting StrictModes no makes the error go away. It also lets anyone who can write to your home directory add their own key to it. Fix the modes instead.

WSL: when chmod “does nothing”

This is the case the old Stack Overflow answers miss. You’re in WSL, you run chmod 600, and ls -l still shows -rwxrwxrwx. The warning now says Permissions 0777.

Microsoft’s WSL docs explain it. Files on a Windows drive (/mnt/c and friends) have no Linux mode bits by default. WSL works out your Windows user’s effective access and shows that same value for owner, group and other. If you have full control of the file, which you do in your own user folder, that comes out as 777. And chmod on those files does almost nothing. Per the docs, its one effect is that removing all write bits sets the Windows read-only attribute. You can’t get a 600 there, so OpenSSH will never accept a key stored on /mnt/c.

Fix 1 (simplest): move the key into the Linux filesystem. AWS’s own WSL instructions do this for .pem files.

mkdir -p ~/.ssh && chmod 700 ~/.ssh
cp /mnt/c/Users/you/.ssh/id_ed25519 ~/.ssh/
chmod 600 ~/.ssh/id_ed25519

Don’t symlink ~/.ssh to the Windows folder to “share” keys. ssh checks the file the link points to, so the 777 follows you.

Fix 2: turn on DrvFs metadata. WSL can store Linux modes in NTFS extended attributes, but it’s off by default. Add this to /etc/wsl.conf inside the distro:

[automount]
options = "metadata"

Then run wsl --shutdown from PowerShell and reopen the terminal. The docs warn that settings only apply once the distro has fully stopped, which takes about 8 seconds after you close every shell. After that, chmod on /mnt/c files sticks. Your Windows permissions still cap what Linux can do, but that’s about getting more access. It doesn’t stop you from removing it.

Checking a mode without guessing

If you’re working out a mode for something that isn’t on the table above, the chmod calculator shows the octal and symbolic forms side by side. Click the read, write and execute boxes for owner, group and other, or type a three-digit value like 600, and it prints the chmod line. A key is safe when the group and other rows are all dashes.

Need a fresh key instead of rescuing an old one? The SSH key generator makes Ed25519 (the default) or RSA pairs in your browser and downloads them as id_ed25519 and id_ed25519.pub (or id_rsa.pem and id_rsa.pub). A download lands at 644 like any other, so run chmod 600 on the private half before first use. Ed25519 vs RSA covers which type to pick. For PEM key pairs used outside SSH (JWT signing, TLS testing), use the RSA / EC key-pair generator. The same rule applies to its private key file: 600, and nobody else gets to read it.