The whole owner-only family
600 belongs to a small, closed set: the modes where the group digit and the other digit are both zero. There are 8 of them out of 512, which is few enough to print entirely — and printing them entirely is more useful than repeating "use 600", because it shows what the neighbouring choices actually do.
| Mode | Symbolic | Owner reads | Owner writes | Owner runs | OpenSSH accepts as a key | What it is for |
|---|---|---|---|---|---|---|
| 000 | --------- | no | no | no | yes | nothing is readable — even the owner must chmod before reading |
| 100 | --x------ | no | no | yes | yes | run-only: a compiled binary can work, a shell script cannot |
| 200 | -w------- | no | yes | no | yes | write-only: you can append, and you cannot read back what you wrote |
| 300 | -wx------ | no | yes | yes | yes | write and run without read — the same shell-script problem |
| 400 | r-------- | yes | no | no | yes | the same thing, protected against your own accidental overwrite |
| 500 | r-x------ | yes | no | yes | yes | a private script you do not want to edit by mistake |
| 600 | rw------- | yes | yes | no | yes | a private key, a token file, a .env — the owner reads and edits it |
| 700 | rwx------ | yes | yes | yes | yes | a private script or directory |
Every row says yes in the OpenSSH column. That is the finding and not an
oversight: the client's test is (st.st_mode & 077) != 0, which looks only
at the group and other digits. It does not care what the owner may do. A key at
000 passes the permission check and then fails to open, which is a different
error with a different message — worth knowing when you are staring at one.
Source: OpenSSH (portable), authfile.c — sshkey_perm_ok(). C source read directly from the project’s master branch.
600 versus 400
Both are accepted everywhere a private key is accepted. The difference is whether you can
overwrite the file without a chmod first. For a key you generated once and will never
edit, 400 costs nothing and turns "I accidentally redirected output over my key" from a
catastrophe into an error message. For a .env file you edit weekly, 600 is
the practical choice. Neither protects the file from deletion — that is a property of the
directory, not of the file.
The three rows below 600 that look like typos and are not
200 is write-without-read. You can append to the file and you cannot read
back what you wrote — genuinely useful for a log a process should not be able to inspect,
and confusing the first time you meet it. 100 is run-without-read, which
works for a compiled binary and does not work for a shell script, because the shell must
read a script to interpret it; the error you get is "Permission denied" issued by
the interpreter, with the kernel never involved. And 000 means even the owner must
chmod the file before touching it, which is a perfectly legitimate way to park something
you want to be deliberate about.
Where 600 is the right answer
The contexts below are the ones where a secret file lives, with the basis for each value
stated. Note the honesty of the .env row: unlike an SSH key, nothing enforces
a mode on it. No loader that this site checked refuses to read a world-readable
.env. The reason to use 600 there is that everything else on a shared host
can read a 644 file, not that a program will complain — which is a weaker claim than the
SSH one, and is labelled as such.
| Where | Mode | Basis | The rule, and why |
|---|---|---|---|
.env and similar secret files file | 600 rw------- | convention | No enforcement found. None of the loaders whose behaviour this site verified refuses to read a world-readable .env — unlike an SSH private key, nothing stops you. The reason to use 600 is that everything else on a shared host can read a 644 file, not that a program will complain. If your deployment needs the application user to read it and the file is owned by a deploy user, 640 with a shared group is the usual shape. No source cited, because none was found. That is the whole content of a “convention” row. |
/etc/sudoers.d/<name> file | 440 r--r----- | enforced | sudo_check_secure(): (sb->st_mode & S_IWOTH) → "%s is world writable"; ISSET(sb->st_mode, S_IWGRP) && (gid == (gid_t)-1 || sb->st_gid != gid) → the group-writable refusal. Either one and sudo refuses to run. Two mode conditions, not one. World-writable is rejected outright; group-writable is rejected as well unless the file’s group is the sudoers group sudo was built to expect. 440 is the conventional value that clears both. 400 clears them too and grants strictly less, so 440 is simply what people settle on here — no source makes it a minimum. Edit these with visudo, which validates before replacing the file. |
- enforced the program refuses to proceed otherwise — the condition is quoted from its source
- convention no enforcement and no documented requirement was found — this is what people do
| Where | Mode | Basis | The rule, and why |
|---|---|---|---|
~/.ssh (the directory) directory | 700 rwx------ | documented | ssh(1) FILES: "There is no general requirement to keep the entire contents of this directory secret, but the recommended permissions are read/write/execute for the user, and not accessible by others." What sshd actually enforces is weaker: safe_path() rejects the directory only when (st_mode & 022) is non-zero, so 755 passes the login check. 700 is the documented recommendation, not the enforced floor — a distinction most cheat-sheets collapse. |
~/.ssh/id_ed25519, id_rsa (private keys) file | 600 rw------- | enforced | (st.st_uid == getuid()) && (st.st_mode & 077) != 0 → the key is ignored The mask is 077, so every bit outside the owner triad fails, read included. Exactly 8 of the 512 three-digit modes satisfy it, and 600 is the only one of those that is both readable and writable by its owner without granting execute. (200 is writable without execute too, but you cannot read the key back.) |
~/.ssh/authorized_keys (on the server) file | 600 rw------- | documented | ssh(1) FILES: "This file is not highly sensitive, but the recommended permissions are read/write for the user, and not accessible by others." Enforced floor: (st_mode & 022) == 0 when StrictModes is on. Public keys are public, so readability is not the risk; writability is. Anyone who can append a line to this file owns the account. |
~/.ssh/config file | 600 rw------- | enforced | (sb.st_mode & 022) != 0 → fatal("Bad owner or permissions on %s") ssh exits here; it does not warn and carry on. A writable client config lets somebody else add a ProxyCommand and route your sessions through a machine of their choosing. |
- enforced the program refuses to proceed otherwise — the condition is quoted from its source
- documented the project’s own manual recommends this value
Setting it without a mistake
The absolute form is chmod 600 ~/.ssh/id_ed25519. The relative form that
strips everyone else without touching the owner's bits is chmod go= ~/.ssh/id_ed25519
— an empty permission list after = clears the class entirely, which is exactly
what the 077 mask is testing for. On a whole directory of keys:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_* ~/.ssh/config
chmod 644 ~/.ssh/*.pub Public keys can stay readable; they are public. What must not be loose is the private key and the client config, and those two use different masks — 077 for the key, 022 for the config — which the SSH key permissions page sets out check by check.