What chmod 755 Actually Means (and Why 777 Isn't the Fix)

755 is not a number. It is nine switches written in shorthand, three for the owner, three for the group, three for everyone else. Learn the 4-2-1 trick once and you stop looking it up. Then learn the part almost nobody knows: most permission denied errors on a file are really about a directory.

Tech Talk News Editorial9 min read
ShareXLinkedInRedditEmail
What chmod 755 Actually Means (and Why 777 Isn't the Fix)

Key takeaways

  • A chmod number is three groups of three switches: read is 4, write is 2, execute is 1, and each octal digit covers one class of user, so 755 means read-write-execute for the owner and read-execute for the group and for everyone else.
  • On a directory the execute bit does not mean run, it means search: the right to look up a name inside that directory and pass through it, which is why a directory set to 644 lets you list filenames but open nothing.
  • Most permission denied errors on a file are actually about a missing execute bit on a directory somewhere in the path, because path_resolution(7) requires search permission on every directory the kernel walks through and returns EACCES the moment one is missing.
  • The umask decides default permissions by subtraction: with the typical default of 022, a new file requested at 0666 lands at 0644 and a new directory requested at 0777 lands at 0755. New files are not executable because programs request 0666, which has no execute bit for the umask to clear; directories do get 0755, which is executable.
  • A fourth octal digit holds the special bits: 4000 is setuid, 2000 is setgid, and 1000 is the sticky bit, which is the t in /tmp at mode 1777 and the reason a world-writable directory does not let anyone delete anyone else's files.

Something breaks at 11pm. A script that ran fine yesterday prints Permission denied, and a small voice in the back of your head says: just chmod 777 it. I have been that voice. It works maybe a third of the time, and every time it works you learn nothing and leave a file behind that anyone on the box can rewrite.

Every file and directory on a Linux or macOS system carries nine permission bits, stored alongside its owner and its group. They decide who can read it, who can change it, and who can run it. chmod is the command that sets them, and 755 is the number people paste into a terminal without knowing what they just said. If your whole relationship with files runs through a graphical editor or IDE, these bits stay invisible right up until the day they aren't.

Here's the thing worth internalizing. 755 is not a number. It is nine switches written in shorthand. Once you see the three groups of three, you never look it up again.

Three groups of three, and the numbers 4, 2 and 1

The nine bits split into three classes of user, always in the same order: the owner of the file, the group the file belongs to, and everyone else. Inside each class there are three switches, also always in the same order: read, write, execute.

Each switch gets a value. Read is 4, write is 2, execute is 1.[1] Those aren't arbitrary, they're the place values of three binary digits, so any combination of the three sums to a unique number between 0 and 7. That is exactly one octal digit per class, which is why a chmod number is three digits long.

4
Read (r), per user class
2
Write (w), per user class
1
Execute or search (x), per user class
7
One digit, one class
All three added together (4+2+1)

Takeaway

There is nothing to memorize past 4, 2 and 1. Every chmod number people actually type is those three values added up three times: 755 is (4+2+1)(4+1)(4+1), and 644 is (4+2)(4)(4).

So 755 says the owner can read, write and execute; the group can read and execute but not write; everyone else gets the same as the group. 644 says the owner can read and write, and nobody else can do anything but read. 600 says the owner can read and write and the rest of the machine is locked out entirely.

Reading ls -l one character at a time

The other half of the skill is reading the output rather than writing the input. Here is a real directory listing from my machine:

 Shell
$ ls -l
-rwxr-xr-x 1 drago drago 18 Sep  5 01:29 deploy.sh
drwxr-xr-x 2 drago drago 40 Sep  5 01:29 newdir
-rw-r--r-- 1 drago drago  0 Sep  5 01:29 notes.md
drwx------ 2 drago drago 40 Sep  5 01:29 private
deploy.sh is 755, notes.md is 644, newdir is 755, private is 700.

Ten characters on the left of each row. The first one is not a permission at all, it is the file type: - for a regular file, d for a directory, l for a symbolic link, plus s, p, b and c for sockets, pipes and devices. The kernel stores that in the same 16-bit field as the permissions, above them: 0100000 marks a regular file, 0040000 a directory, 0120000 a symlink.[4]

The remaining nine are the three groups, left to right, owner then group then other, and inside each group it is always rwx in that order with a dash for a switch that is off. So -rwxr-xr-x is a regular file at 755 and drwx------ is a directory at 700. Two columns over, the two names are the owning user and the owning group, which is what decides which of the three groups applies to you.

On a directory, x means traverse, not run

This is where the mental model most people build quietly goes wrong. On a regular file, the execute bit means the kernel will run this. On a directory it means something else entirely: search permission, the right to look up a name inside the directory and pass through it. GNU's own chmod documentation spells it out in the definition of the letter: execute, or search for directories.[2]

Read and execute are genuinely separate powers on a directory, and separating them produces one of the strangest outputs in Unix. I set a directory to 644, which gives read without search, and ran three commands:

 Shell
$ chmod 644 reports
$ ls -ld reports
drw-r--r-- 2 drago drago 60 Sep  5 01:29 reports

$ ls reports
q3.csv

$ ls -l reports
total 0
-????????? ? ? ? ?            ? q3.csv

$ cat reports/q3.csv
cat: reports/q3.csv: Permission denied
A directory with r but no x: the names are readable, everything behind them is not.

Look at what happened there. ls printed the filename, because listing names is a read of the directory itself. Then ls -l printed a row of question marks, because getting the size and the mode of q3.csv means looking that name up through the directory, and that is search. And cat failed outright, even though q3.csv is mine and sits at -rw-r--r--, unchanged and perfectly readable, one level down.

A directory with read but no execute is a card catalog with the shelves locked. You can read every title on every card. You cannot open a single book.

The first time I saw that -?????????line I assumed the filesystem was corrupt. It isn't. It is the kernel telling you, in the least helpful way available, that it could see the name and could not follow it.

Most permission denied errors are about a directory

That demo is a toy, but the mechanism behind it is the single most useful thing in this article. When you open a file, the kernel does not jump to it. It resolves the path one component at a time, and it needs search permission on every directory it walks through. From path_resolution(7): if the process does not have search permission on the current lookup directory, an EACCES error is returned.[3] The open(2) manual says the same thing from the other end, defining EACCES as the requested access not being allowed or search permission being denied for one of the directories in the path prefix.[7]

Path resolution

Opening /srv/app/reports/q3.csv walks four directories before it reaches the file

Each component is checked in order

  • /Needs execute (search) on the root directory
  • /srvNeeds execute
  • /srv/appNeeds execute
  • /srv/app/reportsNeeds execute. This is the one that is usually missing

The kernel stops at the first directory that denies search

It does not continue, and it does not look further down the path

What you get back

  • All four allow searchThe file mode is finally consulted and the open succeeds or fails on its own merits
  • Any one denies searchEACCES, printed as Permission denied, naming the file you asked for
Never reachedThe file's own permission bitsWhen a parent directory denies search, the mode on q3.csv is never read, which is why chmod on the file changes nothing

Behavior per path_resolution(7) and open(2).

Takeaway

The error message names the file, but the file is often innocent. If ls -l on the file looks fine and you still get Permission denied, the problem is above it in the tree.

There is a one-line diagnostic for this and hardly anyone uses it. namei -l takes a path and prints the mode of every component, so the guilty directory identifies itself:

 Shell
$ namei -l reports/q3.csv
f: reports/q3.csv
drw-r--r-- drago drago reports
                        q3.csv - Permission denied

$ chmod 755 reports
$ namei -l reports/q3.csv
f: reports/q3.csv
drwxr-xr-x drago drago reports
-rw-r--r-- drago drago q3.csv
namei -l walks the path and stops where the kernel stops.

Symbolic mode is usually the one you should type

Octal is absolute. It sets all nine bits at once and throws away whatever was there. Symbolic mode changes one thing and leaves the rest alone, which is what you normally want. The grammar is three parts: who, an operator, and which permissions. Who is u for the owning user, g for the group, o for others, and a for all. The operator is + to add, - to remove, and = to set exactly, clearing anything unmentioned.[2]

 Shell
chmod u+x deploy.sh      # give the owner execute, touch nothing else
chmod go-w shared.conf   # take write away from group and other
chmod a=r LICENSE        # everyone reads, nobody writes, nobody executes
chmod -R u+rwX,go+rX .   # capital X: directories get search, files don't get execute

That last line is the one to steal. The capital X means execute or search only if the file is a directory or already has execute permission for some user.[2] It exists precisely because chmod -R 755 on a source tree marks every README, every JSON file and every image as an executable program. That is a small act of vandalism you then commit to git. chmod -R u+rwX,go+rX is what people mean when they type the other thing.

umask decides what new files get, by subtraction

Nobody runs chmod on every file they create, so something else picks the defaults. That something is the umask, a per-process mask of bits to turn off. The permissions listed in the umask are removed from whatever mode a program asks for when it creates a file.[5]

The typical default is 022.[5] A program creating a regular file asks for 0666, the mask clears the two write bits, and you get 0644. A program creating a directory asks for 0777 and gets 0755. The arithmetic in the manual is exact: 0666 & ~022 = 0644.[5]

 Shell
$ umask
0022

$ touch newfile.txt && mkdir newdir && ls -l
drwxr-xr-x 2 drago drago 40 Sep  5 01:29 newdir
-rw-r--r-- 1 drago drago  0 Sep  5 01:29 newfile.txt
Nothing asked for 644 and 755. The umask subtracted them out of 666 and 777.

Why this matters

The umask only ever clears bits, and 022 clears write for group and other. It never touches execute. A new regular file is not executable because the program creating it asks for 0666, which has no execute bit to clear in the first place; a new directory asks for 0777 and keeps its execute bits, landing at 0755. That is why a script you just downloaded needs chmod +x before it runs, and it is a defensible default: new files should not be runnable by accident.

The fourth digit: setuid, setgid and the sticky bit

Above the nine bits sit three more, and they take a fourth octal digit in front: 4000 for set user ID, 2000 for set group ID, 1000 for the sticky bit.[1] Same 4-2-1 pattern, one level up.

setuidmakes a program run as the file's owner rather than as you.[2] The canonical example is on every Linux box:

 Shell
$ ls -l /usr/bin/passwd
-rwsr-xr-x 1 root root 93640 Feb  2  2026 /usr/bin/passwd

$ ls -ld /tmp
drwxrwxrwt 5903 root root 121960 Sep  5 01:29 /tmp
An s where the owner's x would be, and a t where other's x would be.

The ssits where the owner's execute bit is printed. It is there because changing your password means writing /etc/shadow, a file you have no business writing, so the program runs as root for the duration. Powerful, and a foothold if you ever set it on something you wrote yourself.

setgid on a directory is the friendliest of the three. A new file normally takes the group of the process that made it. With 2000 on the parent directory, Linux uses BSD semantics instead and new files inherit the group of the directory.[4] That is how a shared project folder stays shared:

 Shell
$ chmod 2775 shared && ls -ld shared
drwxrwsr-x 2 drago adm 40 Sep  5 01:29 shared

$ touch shared/inherited.txt && ls -l shared/
-rw-r--r-- 1 drago adm 0 Sep  5 01:29 inherited.txt

$ touch plain/notinherited.txt && ls -l plain/
-rw-r--r-- 1 drago drago 0 Sep  5 01:29 notinherited.txt
Same user, same command. Only the parent directory's setgid bit differs.

Takeaway

Set 2775 once on a team directory and you stop having the recurring conversation about why a colleague can see a file but not edit it.

The sticky bit is why /tmp works. /tmp is mode 1777: literally everybody can write to it. The only thing standing between that and chaos is the t at the end, which means a file in that directory can be renamed or deleted only by the owner of the file, by the owner of the directory, or by a privileged process.[4] Without it, world-writable would mean anyone could delete anyone's work. POSIX declares the effect of t on anything other than a directory to be unspecified, so it is a directory feature in practice.[6]

Side note

If a special bit is set but the underlying execute bit is off, ls prints a capital letter instead: -rwSr--r-- rather than -rwsr--r--. A capital Sis almost always somebody's finger slipping, and it does nothing at all. A capital T on a directory is rarer and usually just as accidental.

Why 777 is the wrong fix, and what to do instead

chmod 777 is a sentence with a meaning, and the meaning is: every account on this machine may read, modify and run this file. Not just you. The web server process. The build agent. Whatever an attacker lands as after finding a hole in something unrelated. On a script it is worse than data exposure, because an attacker who can rewrite the file gets to choose the code that runs the next time cron fires. Least privilege is not an abstract principle here, it is a concrete checklist item you either did or didn't do.

And here is the part that should end the habit on its own: it usually doesn't even work. If the real problem is a missing search bit on a parent directory, chmod 777 the-file changes a mode the kernel never gets far enough to read.

What to do instead, in order:

  1. Run namei -lon the full path and read down the list. The first line that isn't readable to you is your answer.
  2. Check who you actually are with id, and compare it to the owner and group columns in ls -l. A surprising share of permission problems are ownership problems, fixed with chown or chgrp, not chmod.
  3. If it is running in a container, check the user ID inside the image against the one on the host volume. Bind mounts pass numeric user IDs straight through, and the way containers share the host kernel rather than emulating a machine is exactly why a uid 1000 mismatch shows up as a permission error.
  4. Then set the narrowest mode that works. 755 for directories and things meant to run, 644 for everything else, 600 for anything holding a secret, and g+w plus setgid when a team genuinely needs shared write.

Nine switches, three groups, 4-2-1, and one rule that saves you the most time: when the file looks fine and the error persists, look up the tree. Go run namei -lon the next thing that breaks. You'll find it in about four seconds.

Primary sources

  1. 1.PrimaryGNU Coreutils manual, Numeric Modes. Read 400, write 200, execute 100 for the owner, scaled down per class, plus setuid 4000, setgid 2000 and sticky 1000.
  2. 2.Primarychmod(1), Linux man-pages. The ugoa letters, the + - = operators, the rwxXst permissions, and the definition of capital X.
  3. 3.Primarypath_resolution(7), Linux man-pages. Search permission is required on each directory walked; EACCES is returned when it is missing.
  4. 4.Primaryinode(7), Linux man-pages. File type bit values, the S_ISUID / S_ISGID / S_ISVTX octal values, sticky-bit deletion rules and BSD group inheritance on setgid directories.
  5. 5.Primaryumask(2), Linux man-pages. The typical default of 022 and the worked arithmetic 0666 & ~022 = 0644.
  6. 6.PrimaryPOSIX.1-2024, chmod utility. The t symbol specifies S_ISVTX; its effect on any file type other than a directory is unspecified.
  7. 7.Primaryopen(2), Linux man-pages. EACCES covers search permission denied on one of the directories in the path prefix.

Frequently asked questions

What does chmod 755 mean?
It means read, write and execute for the file owner, and read and execute for the group and for everyone else. The three digits are three classes of user in a fixed order (owner, group, other), and each digit is the sum of read (4), write (2) and execute (1). So 7 is 4+2+1 and 5 is 4+1, with the missing 2 meaning nobody but the owner can change the file.
What is the difference between chmod 755 and chmod 644?
755 has the execute bit set and 644 does not. 644 gives the owner read and write (4+2) and everyone else read only (4), which is the correct mode for a document, a config file or a source file. 755 adds execute for all three classes, which is what a script, a compiled binary or a directory needs. On Linux and macOS a new file is created at 644 by default, so an executable is something you have to ask for.
What does the execute bit do on a directory?
On a directory the execute bit grants search permission, not the right to run anything. It is the permission to look up a name inside that directory and pass through it to whatever is underneath. Read and execute are separate powers on a directory: read alone lets you list the filenames, and execute alone lets you open a file whose exact name you already know.
Why do I get permission denied when the file permissions look correct?
Almost always because a directory in the path is missing its execute bit. The kernel resolves a path one component at a time and needs search permission on every directory it walks through, so it returns EACCES before it ever reads the file mode you are staring at. Run namei -l on the full path: it prints the mode of every component and shows exactly which one denies you.
Is chmod 777 safe?
No, and it usually does not even fix the problem. 777 means every account on the machine can read, modify and execute the file, including whatever daemon or web process an attacker reaches first, and on a script it means an attacker can rewrite the code that later runs. It also does nothing about the far more common cause of a permission error, which is a missing execute bit on a parent directory rather than the file itself.

Written by

Tech Talk News Editorial

Computer engineering background. Writes about software, AI, markets, and real estate, and the places where the three meet.

More about the author
ShareXLinkedInRedditEmail