ChmodCalc

chmod 644 — rw-r--r--

The mode almost every file on your machine already has: the owner edits it, everyone else reads it, and nothing about it is executable. Below, what that buys you, what the neighbouring modes change, and the one number that is not in the file's mode at all.

chmod — file mode
whoreadwriteexecutedigit
Owner6
Group4
Others4
special bits — the fourth digit

ls -l → -rw-r--r--

chmod 644 /var/www/html/index.html
chmod u=rw,g=r,o=r /var/www/html/index.html

Absolute symbolic form — identical result, and the only form that can name the setuid, setgid and sticky bits.

what 644 grants on this file

  • Ownerrw-read the contents and change the contents
  • Groupr--read the contents
  • Othersr--read the contents

programs that refuse this mode

  • OpenSSH client (ssh, scp, sftp, git over ssh)

    Permissions 0644 for '/var/www/html/index.html' are too open.

    fix: chmod 600 /var/www/html/index.html

  • Git

    hint: The '/var/www/html/index.html' hook was ignored because it's not set as executable.

    fix: chmod +x /var/www/html/index.html

  • Any POSIX shell (bash, dash, zsh, sh)

    /var/www/html/index.html: Permission denied (exit status 126)

    fix: chmod +x /var/www/html/index.html

644, digit by digit

6 is 4+2 — read plus write, no execute — for the owner. 4 is read alone for the group and for everyone else. There is no execute bit anywhere in the mode, which is the whole argument for using it on content: a file that cannot be executed cannot be executed by accident either.

64 of the 512 three-digit modes have no execute bit anywhere. That is the neighbourhood 644 lives in, and it is where a served file, a template, a stylesheet and a JSON fixture all belong. The execute bit is for programs and for directories, and static content is neither.

The four properties a served file needs, and what satisfies all of them

Take the requirements one at a time. The owner must be able to read the file back and to edit it. A worker process that is neither the owner nor in the owning group must be able to read it. Nobody except the owner should be able to write it. And nothing should be executable. Those four constraints, applied to the whole 512-mode space, leave exactly 2 survivors:

Every three-digit mode that is owner-readable, owner-writable, readable by an outsider, writable by nobody else, and executable by nobody. Produced by filtering all 512 modes, not by choosing likely candidates.
Mode Symbolic Owner Group Others
604 rw----r-- read, write nothing read
644 rw-r--r-- read, write read read

The survivors differ in exactly one bit: whether the owning group may read the file. 644 says yes, 604 says no. Nothing else in the 512 qualifies — which is a more useful thing to know than "644 is the standard", because it tells you there is no cleverer option you have overlooked. If you want the group locked out as well, 604 is the mode, and it is unusual enough that you should leave a comment next to it in your deployment script.

644 against its neighbours

The modes people actually consider for content files, and what each one changes. The columns are computed from the bits:

"Worker can read" assumes the server process is neither the owner nor in the owning group, so the third digit decides. If you have put the worker in the group, read the second digit instead.
Mode Symbolic Worker can read Group can write World can write Executable
644 rw-r--r-- yes no no no
664 rw-rw-r-- yes yes no no
666 rw-rw-rw- yes yes yes no
640 rw-r----- no no no no
600 rw------- no no no no
755 rwxr-xr-x yes no no yes

The row worth staring at is 666. It is often reached for when a file "needs to be writable", and what it actually does is let every account on the machine rewrite the contents — including any process that gets a foothold through an unrelated service. If a web application genuinely needs to write to a path, give the file to the application's user and keep it at 600 or 640; opening it to every account is never the fix.

Still 403 at 644? Check the directories

A 403 with a correct 644 on the file is almost always a directory problem. The kernel resolves a path component by component, and each component needs the search bit; the file's own mode is consulted last. Apache names this precisely in its log — the message is quoted from its source below — and the diagnostic is one command:

namei -l /var/www/html/index.html

That prints the mode and ownership of every component from the root down, so the failing one is visible at a glance instead of being guessed at. Fix the directories with 755 and leave the file at 644.

What breaks a 644 file

644 is safe for content and wrong for three specific things. A shell script at 644 gives you exit status 126 and a "Permission denied" that has nothing to do with the filesystem being read-only. A Git hook at 644 is silently skipped — Git tests the execute bit and never reads the shebang, and prints at most a one-line advisory. And an SSH private key at 644 is refused outright by the client, because its mask is 077 rather than 022. Those three rows, from the projects' own source:

What a 644 file cannot do. All three strings were read from the source of the program that prints them.
What you see Where it comes from Fires on Fix
./deploy.sh: Permission denied (exit status 126)

Any POSIX shell (bash, dash, zsh, sh) · stderr, plus exit status 126

shown for mode 644 on a script you are trying to run, e.g. ./deploy.sh

verbatim, quoted from the standard: POSIX: "Otherwise, if the command name is found, but it is not an executable utility, the exit status shall be 126."

POSIX.1-2024 (IEEE Std 1003.1-2024) — XCU §2.8.2 Exit Status for Commands · Shell Command Language

Linux kernel — include/uapi/asm-generic/errno-base.h · EACCES / EPERM

specified by POSIX, with no single expression guarding it: the command name resolves but is not an executable utility. Each shell implements it its own way

described, not quoted — the message is not guarded by one expression

512 of the 4096 modes fire it.

Assumes that the caller is the owner, which is the usual case for a script in your own checkout. Otherwise read the group or other digit.

chmod +x ./deploy.sh

least-privilege mode 755

hint: The '.git/hooks/pre-commit' hook was ignored because it's not set as executable.

Git · stderr, as advice during commit/push

shown for mode 644 on .git/hooks/pre-commit and friends

  • hint: You can disable this warning with `git config set advice.ignoredHook false`.

verbatim format string: The '%s' hook was ignored because it's not set as executable.\nYou can disable this warning with `git config set advice.ignoredHook false`.

Git — hook.c · find_hook()

Git — advice.c · vadvise()

find_hook() in hook.c passes the format above to advise(); vadvise() in advice.c splits it on newlines and prints each line through fprintf(stderr, _("%shint:%s%.*s%s\n"), …), so every line reaches the terminal with a "hint: " prefix that is not part of the format string.

found_hook = access(path.buf, X_OK) >= 0; … if (!found_hook) { if (errno == EACCES && advice_enabled(ADVICE_IGNORED_HOOK))

quoted verbatim from the source

512 of the 4096 modes fire it.

chmod +x .git/hooks/pre-commit

least-privilege mode 755

Permissions 0644 for '/home/deploy/.ssh/id_ed25519' are too open.

OpenSSH client (ssh, scp, sftp, git over ssh) · stderr, before the connection is attempted

shown for mode 644 on ~/.ssh/id_ed25519 (a private key)

  • @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
  • @ WARNING: UNPROTECTED PRIVATE KEY FILE! @
  • @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
  • It is required that your private key files are NOT accessible by others.
  • This private key will be ignored.

verbatim format string: Permissions 0%3.3o for '%s' are too open.

OpenSSH (portable) — authfile.c · sshkey_perm_ok()

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

quoted verbatim from the source

4032 of the 4096 modes fire it.

chmod 600 /home/deploy/.ssh/id_ed25519

least-privilege mode 600

shell-exit-126 — The wording of the message before the exit status is the shell’s own and varies — bash prints "bash: ./deploy.sh: Permission denied". What is specified, and therefore what you can rely on in a script, is the 126.

git-hook-ignored — The advice is printed at most once per hook name per process, and only when errno is EACCES — a hook that is missing entirely produces nothing at all. If you are grepping a log for this, grep for the text after the prefix: the "hint: " comes from the advice printer, not from the message.

ssh-privkey-too-open — The check only runs when the key is owned by the user running ssh. A key owned by somebody else is not mode-checked at all — the comment in the source says so explicitly. Note also that the printed number is st_mode & 0777, so a set-user-ID bit on the key never appears in the message.

Going the other way — from 644 up to an executable mode — the relative form is chmod a+x file, which touches only the execute bits and leaves the read and write bits exactly as they were.

## Questions people actually ask

What does chmod 644 mean?

rw-r--r--. The owner can read and write; the group and everyone else can read only. Nobody can execute it. It is the default mode for a newly created file on a system with a umask of 022, which is why so many files already have it.

Is 644 the right mode for HTML, CSS and images?

Yes, and for a specific reason: a web server reads static files and never runs them, so the execute bit contributes nothing and only widens the blast radius of an upload bug. What the server does need is the read bit for whichever class it falls into — usually "others", since the worker user is rarely the file owner. That is exactly what the third digit of 644 provides.

What is the difference between 644 and 664?

One bit: group write. 664 lets anyone in the owning group edit the file, which is what you want for a shared checkout where several people deploy, and what you do not want for anything a web application can write to. If you use 664, make the owning group small and deliberate; a 664 file in a group that includes the web server user is effectively a writable web root.

Why does my file still give 403 at 644?

Because the file's own mode is only half of it. A request has to resolve every directory on the path first, and that needs the search bit on each of them — Apache says so in as many words: "search permissions are missing on a component of the path". Check the directories with namei -l /var/www/html/index.html, which prints the mode of every component in one go. The directory mode you want is 755.

Should I use 644 or 640 for a config file with secrets?

Neither, if it is genuinely secret. 644 lets every account on the machine read it and 640 lets the whole owning group read it. For credentials use 600 and give the reading process ownership, or 640 with a group that contains exactly that one service account. The mode is not the security boundary on its own — the group membership is the other half.

How do I set 644 on files without touching directories?

Split by type, not by mode: find /var/www/html -type f -exec chmod 644 {} +. Running chmod -R 644 instead would strip the search bit from every directory in the tree and make the whole thing unreachable, which is the single most damaging thing a well-meaning chmod can do.

elsewhere on this site

  • The chmod calculator itself — all 512 three-digit modes in one table.
  • chmod 777 — What 777 actually grants, which errors it does not fix, and the sticky-bit alternative.
  • chmod 600 — Owner-only read and write — the mode OpenSSH demands of a private key.

other tools we run