chmod 600 sets the mode to rw-------: the owner can read and write (6), and the group and everyone else get nothing (0). It’s the mode for secrets: SSH private keys, ~/.ssh/authorized_keys and ~/.ssh/config, .env files, TLS private keys, ~/.pgpass, anything holding a password or a token.
Why tools insist on it
OpenSSH ignores a private key that the group or others can access at all:
WARNING: UNPROTECTED PRIVATE KEY FILE!
Permissions 0644 for '/home/alice/.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.
600 and 400 pass; 640 and 644 don’t. ssh-keygen writes new private keys as 600 itself, so the error usually means a key that was copied, unzipped or checked out. PostgreSQL’s libpq likewise skips a ~/.pgpass with group or world access and asks for u=rw (0600) or less.
chmod 600 ~/.ssh/id_ed25519 ~/.ssh/authorized_keys ~/.ssh/config
chmod 700 ~/.ssh
stat -c '%a %n' ~/.ssh/id_ed25519 # 600 /home/alice/.ssh/id_ed25519
Create it private, don’t fix it after
chmod after writing leaves a window in which the secret is readable. A umask of 077 makes the file 600 from the moment it exists, since 666 with 077 cleared is 600:
(umask 077; printf '%s\n' "$DEPLOY_KEY" > ~/.ssh/id_ed25519)
The parentheses keep the umask to that one command.
Gotchas
- It’s not for directories. A
600directory has no execute, so even its owner can’t open the files in it. Directories that hold secrets want700. - Root still reads it. So does anyone with
sudo. The mode keeps out other users, not administrators. - The service user must own it. A
600file owned byrootis unreadable to a service running aswww-data. Eitherchownit to the service’s user, or keep root as owner and use640with the service’s group.
In git, CI and Kubernetes
- Git can’t store
600: it records100644, and the file comes back644on checkout. Secrets shouldn’t be in the repository anyway. - GitHub Actions: write a deploy key from a secret with the umask trick:
- name: Install the deploy key
env:
DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
run: |
mkdir -p ~/.ssh && chmod 700 ~/.ssh
(umask 077; printf '%s\n' "$DEPLOY_KEY" > ~/.ssh/id_ed25519)
- Kubernetes mounts Secret files as
0644unless told otherwise. SetdefaultMode: 0600on the volume,384in JSON, ormodeon a single key underitems. - Dockerfile:
COPY --chmod=600sets it, but a key copied into an image layer stays in that layer. Use a BuildKit secret mount (RUN --mount=type=secret,id=key) for anything the build only needs once.