The owner digit on a directory, all eight values
700 keeps the group and other digits at zero, so the only interesting question is what the first digit does. Here is each of its eight values applied to a directory, with the five things you might want to do with one. Every column here falls out of the bits themselves, which is why two of the rows come out entirely useless:
| Mode | Owner bits | ls | cd | open a known name | create | delete |
|---|---|---|---|---|---|---|
| 000 | --- | no | no | no | no | no |
| 100 | --x | no | yes | yes | no | no |
| 200 | -w- | no | no | no | no | no |
| 300 | -wx | no | yes | yes | yes | yes |
| 400 | r-- | yes | no | no | no | no |
| 500 | r-x | yes | yes | yes | no | no |
| 600 | rw- | yes | no | no | no | no |
| 700 | rwx | yes | yes | yes | yes | yes |
Look at the 600 row: read and write, no search, and the result is a directory you can list and otherwise cannot touch. The write bit is inert, because creating an entry requires resolving the directory first. This is what happens when a file mode gets applied to a directory — the search bit is the thing that quietly goes missing, and nothing reports it.
300 and 100 are the mirror image: search without read. You can
cd into the directory and open a file whose exact name you know, but
ls refuses. That is not a broken configuration; it is a legitimate pattern for
a directory holding per-user paths that should not be enumerable.
What sshd actually checks, and what ssh recommends
If you have been told that ~/.ssh must be 700, that is a recommendation being
reported as a rule. Here is what the server really does. When StrictModes is on — and its
default is yes — sshd resolves the authorized-keys file and then walks
upward, directory by directory, to the home directory. For each component it rejects on
this condition:
(st.st_mode & 022) != 0
That is group-write or other-write. Nothing else. A ~/.ssh at 755 passes; so
does 750, and so does 711. 128 of the 512
three-digit modes satisfy it. What fails is 775, 757, 777 — anything that lets somebody
else put a file into the directory, because somebody who can write to ~/.ssh
can replace authorized_keys.
Separately, ssh(1)'s FILES section recommends "read/write/execute for the
user, and not accessible by others" — which is 700. That is a recommendation about
confidentiality, and it is good advice. It is not the rule that is failing your login. The
practical consequence: if sshd is refusing your key, do not chmod every directory to 700
and hope. Look for the writable one.
Enforcement source: OpenSSH (portable), misc.c — safe_path() (C source read directly from the project’s master branch). Recommendation source: ssh.1 — FILES (mdoc manual source read directly from the project’s master branch). Default for StrictModes: sshd_config.5 — StrictModes (mdoc manual source read directly from the project’s master branch).
| What you see | Where it comes from | Fires on | Fix |
|---|---|---|---|
Authentication refused: bad ownership or modes for directory /home/deploy/.ssh OpenSSH server (sshd) · the server’s log — /var/log/auth.log or journalctl -u sshd shown for mode 775 on ~/.ssh, and every directory above it up to the home directory verbatim format string: | OpenSSH (portable) — misc.c · safe_path() OpenSSH (portable) — auth2-pubkeyfile.c · auth_openfile() Same two-file assembly as the file variant: safe_path() fills the buffer while walking upward from the key file, auth_openfile() prefixes "Authentication refused: ". | stat(buf, &st) == -1 || (!platform_sys_dir_uid(st.st_uid) && st.st_uid != uid) || (st.st_mode & 022) != 0 — inside the loop that walks each parent in turn quoted verbatim from the source 3072 of the 4096 modes fire it. | chmod 700 /home/deploy/.ssh least-privilege mode 700 |
Authentication refused: bad ownership or modes for file /home/deploy/.ssh/authorized_keys OpenSSH server (sshd) · the server’s log — /var/log/auth.log or journalctl -u sshd shown for mode 664 on ~/.ssh/authorized_keys on the server verbatim format string: | OpenSSH (portable) — misc.c · safe_path() OpenSSH (portable) — auth2-pubkeyfile.c · auth_openfile() safe_path() in misc.c writes this into an error buffer; auth_openfile() in auth2-pubkeyfile.c prints the buffer with logit("Authentication refused: %s", line). | (!platform_sys_dir_uid(stp->st_uid) && stp->st_uid != uid) || (stp->st_mode & 022) != 0 quoted verbatim from the source 3072 of the 4096 modes fire it. | chmod 600 /home/deploy/.ssh/authorized_keys least-privilege mode 600 |
sshd-authkeys-dir — The mask is 022, not 077. sshd does not require ~/.ssh to be 700 — 755 passes this test. 700 is the mode ssh(1) recommends, which is a different statement from the one sshd enforces.
sshd-authkeys-file — The client sees only "Permission denied (publickey)". The sentence above exists only in the server’s log, which is why this failure is so often misdiagnosed as a wrong key.
Where 700 is the wrong answer
- A crontab file. cron's mask requires the owner-execute bit to be clear,
so 700 disqualifies the file and the jobs stop, logged only as
BAD FILE MODE. Use 600. - A home directory on a machine with a web server serving
~/public_html. The worker needs the search bit on the home directory to reach anything below it; 700 removes it and every page 403s. - A shared deployment directory. 700 locks out everyone including your colleague's account and your CI runner. 750 with a deliberate group is the shape you want.
- Anything a service account must read. Ownership is the lever, not the mode — give the directory to the service user and keep 700. Widening it is the wrong lever.
Applying it
chmod 700 ~/.ssh is the whole command. The relative form
chmod go= ~/.ssh does the same thing without asserting anything about the
owner's bits, which is the safer spelling when you are fixing a directory whose current
mode you have not looked at. To find the actual offender in an SSH path:
namei -l ~/.ssh/authorized_keys That prints every component with its mode and owner in one column, so a group-writable parent is obvious immediately. It is a faster diagnosis than chmod-ing your way up the tree, and it leaves the directories that were already fine alone.