Chmod for an SSH key: the permission ssh refuses to work without
Almost everyone who looks up chmod for an SSH key has the same wall of hash marks on screen: a warning about an unprotected private key file, a line saying the permissions are too open, and a connection that falls back to asking for a password. The usual advice is chmod 600, it usually works, and that is where most pages stop.
It is worth knowing the actual rule instead, because the same wall appears for reasons chmod 600 does not fix. The key may live on a volume that cannot store a mode at all. The failure may be on the server rather than on the Mac. Or the complaint may be about ~/.ssh/config, which is governed by a different test entirely and does not need 600. The test ssh applies is short enough to read, and reading it turns a guess into a decision.
What ssh actually tests on a private key
The check lives in one function in the OpenSSH source, sshkey_perm_ok. It calls fstat on the key it just opened and applies a single condition: the file is rejected if it is owned by the user running ssh and any of the nine low bits outside the owner's three are set. In C that condition reads (st.st_uid == getuid()) && (st.st_mode & 077) != 0. When it trips, the four lines printed are fixed text:
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: UNPROTECTED PRIVATE KEY FILE! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0644 for '/Users/you/.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.
Two things follow from that one line of C, and both matter.
The mask is 077, not 177 or 377. Only the group triplet and the other triplet are examined. Whatever the owner's own bits say is irrelevant to this test, which is why chmod 700 on a private key passes it despite marking a text file executable. The comparison is also strictly against zero, so there is no partial credit: a single group-read bit is enough, and 0640 fails exactly as loudly as 0666.
The ownership condition is the part that catches people out. The permission test only runs when the key is owned by the user invoking ssh. The comment in the source says as much: if the key is owned by a different user, ssh does not care. A key copied in as root, or restored with the wrong owner, therefore skips this check and fails later and less clearly, at the point where the file cannot be read or the passphrase prompt never appears. That is a chown problem wearing a chmod costume, and ls -l shows it in the owner column.
The manual page agrees with the source in plainer words. Under FILES, man ssh describes the private key files as containing sensitive data that should be readable by the user but not accessible by others, and states that ssh will simply ignore a private key file if it is accessible by others.
The modes that pass, and the two that surprise people
Because the rule is only about the last six bits, the set of acceptable modes is larger than the single number everyone quotes. The table below is the whole picture for a key owned by the account running ssh.
| Mode | Group and other bits | ssh accepts the key | Note |
|---|---|---|---|
600 |
none | Yes | What ssh-keygen writes, and the sane default |
400 |
none | Yes | Read-only for the owner, fine for a key never edited |
700 |
none | Yes | Passes, but marks a text file executable for no reason |
000 |
none | Yes by this test | The owner cannot read it either, so the open fails first |
640 |
group read | No | The single most common result of copying a key around |
644 |
group and other read | No | What most editors and unzip tools leave behind |
660 |
group read and write | No | Common on shared build machines |
The 000 row is the useful oddity. Nothing in sshkey_perm_ok requires the owner to have any access, so a key with no bits set at all satisfies the permission test and then fails at the file open instead, with a permission denied error that mentions no key at all. When a key has stopped working and the warning banner is absent, checking for an empty mode is quicker than rereading the verbose log.
Two commands read the mode without guessing at ls output. stat -f '%OLp %Su %N' ~/.ssh/* prints the low nine bits in octal next to the owner name and the path, which is the exact pair of facts the test uses. ls -l@e ~/.ssh gives the symbolic form plus any extended attributes and access control list entries, which matters for a reason covered further down.
The config file follows a different rule, and 644 is fine
A second failure mode looks related and is not. When ssh refuses to read ~/.ssh/config it says Bad owner or permissions on /Users/you/.ssh/config and stops. Being told to run chmod 600 on that file too is common, and it works, but it obscures what the test is.
The condition in readconf.c is ((sb.st_uid != 0 && sb.st_uid != getuid()) || (sb.st_mode & 022) != 0). The mask is 022, which is group write and other write. Read access is not examined at all. A config file at 644 is perfectly acceptable to ssh; a config file at 664 is not. Ownership is slightly looser here as well, since root is allowed to own the file in addition to the user running the command.
This distinction is not academic. Group write is what a shared group directory, an unpacked archive with a permissive umask, or a sync client with its own idea of modes will hand back. If the repair is applied as a blanket chmod -R 600 ~/.ssh, the directory itself loses its execute bit and nothing inside it can be reached, which produces a third, unrelated failure. The directory needs 700, the files need their own treatment, and man ssh states the recommendation for ~/.ssh/ directly: read, write and execute for the user, and not accessible by others.
Public keys are the mirror image. man ssh says the .pub files are not sensitive and can, but need not, be readable by anyone. There is no permission test on them, so 644 is normal and tightening them achieves nothing.
The server side is checked differently, and walks upwards
When the mode on the Mac is correct and the server still asks for a password, the check that failed is on the other machine. sshd applies StrictModes, which man sshd_config describes as checking file modes and ownership of the user's files and home directory before accepting a login, with a default of yes. On failure the server logs a line containing Authentication refused: bad ownership or modes, and the client sees nothing but a password prompt.
The important detail is that this test does not stop at the file. The helper it uses, safe_path, rejects the file if group write or other write is set, and then walks each component of the canonical path upwards, applying the same 022 test to every directory until it reaches the home directory. So ~/.ssh/authorized_keys at 600 inside ~/.ssh at 700 still fails if the home directory itself is 775, or if any directory above it in the real path is group writable.
That is why key authentication breaks on a freshly provisioned server where a deployment script set a permissive umask, and why it breaks on hosts where home directories were created group writable so that a team could share them. man sshd states the consequence in the FILES section: if the file, the .ssh directory, or the user's home directory are writable by other users, then the file could be modified or replaced, and sshd will not allow it to be used unless StrictModes has been set to no.
Setting StrictModes no is the wrong fix and worth naming as such. The check exists because anyone who can write the directory can add their own public key to it. The correct repair is chmod go-w ~ on the server, then chmod 700 ~/.ssh and chmod 600 ~/.ssh/authorized_keys, and then a look at the parent path if the home directory is somewhere unusual.
When chmod runs, reports nothing, and changes nothing
Three situations on a Mac swallow a chmod silently, and all three are worth ruling out before rerunning it with sudo.
The volume cannot store a mode
A key kept on a USB stick, an SD card or a camera card is usually on exFAT or FAT. Those filesystems have no POSIX permission bits. macOS synthesises them for the whole volume at mount time instead, and man mount_exfat and man mount_msdos document the options that set them: -u for the owner, -g for the group, and -m for the maximum file permissions, with the default mask taken from the directory the filesystem is mounted on. A per-file chmod therefore has nothing to write to. The same applies to a key sitting on an SMB share, where the mode shown comes from the server's mapping rather than from the local file. The fix is not a permission at all: copy the key onto the internal APFS volume, chmod 600 it there, and use it from there.
An access control list is overriding the bits
macOS layers ACLs on top of the nine bits, and man chmod documents the whole grammar for them. A key that arrived through a download folder, a managed share or a restore can carry an inherited ACL that grants access the mode does not show. The clue is a trailing + in ls -l output, and ls -le prints the entries themselves. Since sshkey_perm_ok reads st_mode and not the ACL, this does not trigger the warning, but it does mean the key is genuinely readable by someone the mode says cannot reach it. chmod -N removes the ACL from the named files, and man chmod lists it alongside -i and -I for stripping inherited entries only.
The mode is right and the key is not the one being offered
Verbose output settles this in one pass. ssh -v lists each identity as it is tried, including the ones an agent supplied, and the line that matters reads Offering public key: followed by a path. If the path is not the key that was just repaired, the mode was never the problem. man ssh_config documents IdentitiesOnly, which tells ssh to use only the configured identity files even when an agent offers more, and that is the setting that ends the confusion on machines with several keys loaded.
A repair order that does not widen the hole
Work from the outside in, and verify at each step rather than at the end.
Start with the directory, since every later check walks through it: chmod 700 ~/.ssh. Then the private keys, with the symbolic form rather than a number, because chmod go-rwx ~/.ssh/id_ed25519 clears exactly the bits the 077 mask tests and leaves the owner's bits alone. Leave the .pub files as they are. For ~/.ssh/config, chmod go-w ~/.ssh/config is enough to satisfy the 022 test, and there is no need to make a file unreadable that contains no secrets. Confirm with stat -f '%OLp %Su %N' ~/.ssh/*, which prints the mode and the owner for everything in one listing so that a stray owner shows up at the same time.
The reason this order matters is that each of these files is checked by a different rule, and a single recursive command cannot satisfy all three. Keeping the shell and the folder in the same window makes the verification step cheap enough to actually do, which is one of the differences the Compared with other file managers page lays out.
If the key stays unusable after that, the remaining causes are outside chmod. The passphrase may be cached in the wrong place, which man ssh-add addresses through --apple-use-keychain for storing it and --apple-load-keychain for loading it, and which man ssh_config mirrors with UseKeychain and AddKeysToAgent. Or the key is fine and the server is applying StrictModes to a home directory nobody has looked at yet.
What to change first
Run stat -f '%OLp %Su %N' ~/.ssh/* before anything else, because it shows the mode and the owner together and most failures are visible in that one listing. Then fix the directory to 700, clear group and other bits from the private keys with chmod go-rwx, and leave the config file and the public keys alone unless they are group writable. If the warning banner never appeared in the first place, the problem is on the server or in the file's owner, not in its mode, and Atriens exists partly so that the folder and the shell needed for that check are in the same window.
Frequently asked questions
Is 600 or 400 better for an SSH private key?
Both satisfy ssh, because the test only looks at the group and other bits and both leave those empty. 400 is slightly stricter since it also removes the owner's write bit, which is a reasonable choice for a key that will never be edited again. It makes rotating or re-encrypting the key marginally more annoying, as the write bit has to be restored first.
Why does ssh complain about ~/.ssh/config when the permissions look fine?
Because that file is judged by a different mask. The condition in the client's own source tests st_mode & 022, which is group write and other write only, and it also requires the file to be owned by either the user or root. A file at 664 fails while a file at 644 passes, so the fix is chmod go-w ~/.ssh/config rather than chmod 600.
chmod 600 ran without an error but the key is still rejected. What else could it be?
Check where the key lives and who owns it. On exFAT, FAT or an SMB share there are no per-file permission bits to change, since man mount_exfat and man mount_msdos show that the mode is set for the whole volume at mount time. If the key is owned by another account, ssh skips the permission test entirely and fails later, so ls -l on the file is the faster check.
Does the same rule apply to authorized_keys on the server?
No, it is stricter in one direction and looser in another. sshd applies StrictModes, which uses the group write and other write mask, but it applies that test to the file and then to every directory above it up to the home directory. A home directory at 775 breaks key authentication even when ~/.ssh is 700 and authorized_keys is 600.
Should the public key file be locked down as well?
There is no need. man ssh states that the .pub files are not sensitive and can, but need not, be readable by anyone, and no permission test is applied to them. Leaving them at 644 is normal, and tightening them does not improve anything.