Linux permissions — theory with chmod in octal and in letters, umask and a group project folder (and the script that refused to start)
Eleven at night, the day before the IC2 lab deadline. Laura, my teammate, sends me the script that builds everything through the group chat. I save it, I run it and…
# The night before the deadline: the group's script won't start cat > build.sh << 'EOF' #!/bin/bash echo "Compiling the IC2 lab exercise..." EOF ./build.sh # I try to run it... and Linux says no ls -l build.sh # the answer is in the first ten characters chmod u+x build.sh # u = the owner (me), + = add, x = execute ls -l build.sh # now there is an x in the first triplet ./build.sh
This is what the terminal answered:
$ cat > build.sh << 'EOF' > #!/bin/bash > echo "Compiling the IC2 lab exercise..." > EOF $ ./build.sh bash: ./build.sh: Permission denied $ ls -l build.sh -rw-r--r-- 1 sergio sergio 53 Oct 25 16:49 build.sh $ chmod u+x build.sh $ ls -l build.sh -rwxr--r-- 1 sergio sergio 53 Oct 25 16:49 build.sh $ ./build.sh Compiling the IC2 lab exercise...
Permission denied. My first thought was that the script was broken. My second, that Laura had sent me the wrong version. I spent twenty minutes checking a two-line file that was perfectly fine, and I very nearly typed chmod 777 “just to make it work”. The problem had been in plain sight all along: in -rw-r--r-- there is no x at all. The file could be read and written, but nobody (not even me, its owner) was allowed to execute it. One chmod u+x later, the same ls -l shows -rwxr--r-- and the script runs.
Today we are going to really understand those ten characters: who “the owner”, “the group” and “others” are, what r, w and x mean (on a folder they don’t mean the same as on a file), how to change them with chmod in octal (754) and in letters (u+x), why everything you create is born with 644 or 755 (the umask), who can change a file’s owner, and what those odd s and t letters that sometimes show up are. If you come from the article on system calls in C, you’ll remember the 0644 we passed to open: today you’ll see what each digit means. And if the terminal still feels intimidating, go over the basic Linux commands first. After the theory you have the practice and the exercises.
The example that runs through all three articles is the folder of an IC2 group project: I (sergio) am the owner, Laura and I are in the ic2 group, and Pablo, from another class, is “the rest of the world”.
Table of Contents
Who is who: owner, group and others
Linux is a multi-user system: several accounts live on the same computer, and every file has to know who can do what with it. Instead of keeping a list of permissions per person, Linux simplifies things and splits the world into three classes:
- Owner (
u, for user): the user the file belongs to. Usually whoever created it. - Group (
g): a group of users attached to the file. All its members share the same permissions. - Others (
o): any user who is neither the owner nor in the group.
And how do you find out who you are and which groups you’re in? With id:
id
On the lab computer, this comes out:
uid=1001(sergio) gid=1001(sergio) groups=1001(sergio),1010(ic2)
It reads like this: my user is sergio (number 1001), my primary group is also called sergio and, on top of that, I belong to the ic2 group, the project one. The numbers (uid, gid) are what Linux really stores; the names are for us.
In almost every distribution, creating a user also creates a group with the same name that only that user belongs to. That’s why, when you create a file, ls -l usually shows sergio sergio: owner sergio, group sergio. To really share something, you have to move the file to a common group, such as ic2.
An idea worth having clear from the start: when you try to open a file, Linux doesn’t add up the permissions of the three classes. It looks at just one: if you’re the owner, it uses the owner triplet; if you aren’t but you’re in the group, the group one; otherwise, the others one. You’ll check it in the practice with a rather surprising result.
Reading permissions with ls -l
Let’s create three different things (a directory, an empty file and the script) and look at them with ls -l:
# Who am I? User, primary group and every group I belong to id mkdir docs # a directory touch report.txt # an empty file cat > build.sh << 'EOF' #!/bin/bash echo "Compiling the IC2 lab exercise..." EOF ls -l # one line per entry: permissions, links, owner, group, size, date, name
The real output:
$ id uid=1001(sergio) gid=1001(sergio) groups=1001(sergio),1010(ic2) $ mkdir docs $ touch report.txt $ cat > build.sh << 'EOF' > #!/bin/bash > echo "Compiling the IC2 lab exercise..." > EOF $ ls -l total 8 -rw-r--r-- 1 sergio sergio 53 Oct 25 16:49 build.sh drwxr-xr-x 2 sergio sergio 4096 Oct 25 16:49 docs -rw-r--r-- 1 sergio sergio 0 Oct 25 16:49 report.txt
Each line has seven columns. The drawing takes the script line apart piece by piece:
Let’s go step by step, following the drawing:
- The first character is the type:
-for a regular file,dfor a directory (docs) andlfor a symbolic link. - The next nine are three
rwxtriplets: the owner’s, the group’s and the others’, in that order and always in that order. Each position has its fixed letter (firstr, thenw, thenx); a-means “not this permission”. - The number after that is the link count (for a directory it also counts its subdirectories; we don’t care about it today).
- Owner and group:
sergio sergio. - The size in bytes: the script takes 53 bytes;
docstakes 4096 because a directory reserves a block for its list of names. - The last modification date and the name.
What does each letter mean on a file?
| Letter | On a file | Example |
|---|---|---|
r (read) | read its content | cat, less, opening it with open(…, O_RDONLY) |
w (write) | change its content | echo … >> file, saving it in an editor |
x (execute) | run it as a program | ./build.sh, ./a.out |
Now you can read the output above: build.sh and report.txt have rw-r--r-- (I read and write; the group and others only read) and docs has rwxr-xr-x. Why is the directory born with x and the files aren’t? It isn’t a coincidence: the umask decides it, and you’ll see it further down.
On your computer you’ll see your own user, your groups and your date. The outputs in this article were generated with the terminal language set to English (LANG=C), which is what you’ll see on almost every server; with another locale, the month or the error messages may come out in your language.
Changing permissions with chmod in octal
chmod (change mode) changes permissions. The fastest way to use it is with three octal digits, one per triplet. The trick is that each letter has a weight:
ris worth 4wis worth 2xis worth 1
You add up the weights of the letters you want and get a digit between 0 and 7. Since 4, 2 and 1 are powers of two, every sum is unique: 6 can only be 4 + 2 (rw-) and 5 can only be 4 + 1 (r-x). That’s why there’s no ambiguity.
touch build.sh submission.txt notes.txt chmod 754 build.sh # 7 = 4+2+1 = rwx (me) · 5 = 4+1 = r-x (group) · 4 = r-- (others) chmod 640 submission.txt # 6 = 4+2 = rw- · 4 = r-- · 0 = --- (others see nothing) chmod 600 notes.txt # only I read and write # %a = octal permissions, %A = in letters, %n = name: both notations side by side stat -c '%a %A %n' build.sh submission.txt notes.txt
With stat -c '%a %A %n' we see the number, the letters and the name at once:
$ touch build.sh submission.txt notes.txt $ chmod 754 build.sh $ chmod 640 submission.txt $ chmod 600 notes.txt $ stat -c '%a %A %n' build.sh submission.txt notes.txt 754 -rwxr-xr-- build.sh 640 -rw-r----- submission.txt 600 -rw------- notes.txt
The drawing breaks the first one, 754, down digit by digit:
Checking all three by hand:
754: 7 = 4 + 2 + 1 =rwx; 5 = 4 + 0 + 1 =r-x; 4 = 4 + 0 + 0 =r--→rwxr-xr--.640: 6 = 4 + 2 =rw-; 4 =r--; 0 =---→rw-r-----.600: 6 =rw-; 0 =---; 0 =---→rw-------.
There’s half a dozen values you’ll use 90% of the time:
| Octal | Letters | What for? |
|---|---|---|
755 | rwxr-xr-x | scripts and programs everyone can use; regular directories |
750 | rwxr-x--- | scripts or folders of a project group |
700 | rwx------ | your private folder, your personal scripts |
644 | rw-r--r-- | regular files anyone can read |
640 | rw-r----- | group documents: the group reads, nobody else does |
600 | rw------- | private files: keys, notes, configs with passwords |
Typing chmod 777 “just to make it work”. It works, sure: now any user on the system can read, change or wipe the content of your file, and run it. If a script doesn’t start, what it’s missing is the owner’s x (chmod u+x or chmod 755), not every permission in the world.
Changing permissions in symbolic mode
The octal form sets all nine bits at once. Sometimes you just want to touch one without worrying about the rest, and that’s what symbolic mode is for. It’s built from three pieces:
- For whom?
u(owner),g(group),o(others) ora(all, the three). They can be combined:ug,go… - Which operation?
+adds,-removes and=sets exactly (whatever you don’t name is cleared). - Which permissions?
r,w,xor a combination.
Several changes are separated with commas and no spaces: chmod u+x,g-w file.
touch submission.txt stat -c '%a %A %n' submission.txt # starting point chmod g+w submission.txt # + adds: now the group can write stat -c '%a %A %n' submission.txt chmod o-r submission.txt # - removes: others can no longer read stat -c '%a %A %n' submission.txt chmod u=rw,g=r,o= submission.txt # = sets it exactly; 'o=' with no letters leaves others with nothing stat -c '%a %A %n' submission.txt chmod a+x submission.txt # a = all: u, g and o at once stat -c '%a %A %n' submission.txt
The real output, step by step:
$ touch submission.txt $ stat -c '%a %A %n' submission.txt 644 -rw-r--r-- submission.txt $ chmod g+w submission.txt $ stat -c '%a %A %n' submission.txt 664 -rw-rw-r-- submission.txt $ chmod o-r submission.txt $ stat -c '%a %A %n' submission.txt 660 -rw-rw---- submission.txt $ chmod u=rw,g=r,o= submission.txt $ stat -c '%a %A %n' submission.txt 640 -rw-r----- submission.txt $ chmod a+x submission.txt $ stat -c '%a %A %n' submission.txt 751 -rwxr-x--x submission.txt
This is the trace of what each command did to submission.txt:
| Command | Before | After | What happened? |
|---|---|---|---|
| (just created) | — | 644 rw-r--r-- | the umask decides it |
chmod g+w | 644 | 664 rw-rw-r-- | the group gets w: 4 + 2 = 6 |
chmod o-r | 664 | 660 rw-rw---- | others lose r: 4 − 4 = 0 |
chmod u=rw,g=r,o= | 660 | 640 rw-r----- | = sets each triplet; o= with no letters leaves it empty |
chmod a+x | 640 | 751 rwxr-x--x | one more x in every triplet: 6 + 1 = 7, 4 + 1 = 5, 0 + 1 = 1 |
Look at the last row: a+x only adds, so it respects what was already there. The group had r-- and ends up with r-x; others had nothing and end up with just x. With octal you’d have had to work out the whole number by hand.
When do I use each form?
| I want to… | Better | Example |
|---|---|---|
| leave a file with exact permissions | octal | chmod 640 submission.txt |
| add or remove one permission without touching the rest | symbolic | chmod u+x build.sh |
| change many files that have different permissions | symbolic | chmod -R o-rwx folder |
| copy the permissions a lab sheet gives me (“set it to 750”) | octal | chmod 750 folder |
If you type chmod +x script.sh without saying for whom, it applies to all three triplets, just like a, but respecting the umask (you’ll see what that is in a moment). With the usual umask, 022, it’s the same as a+x. It’s the shortest and most common way to make a script executable.
Permissions on directories
This is where almost everyone gets it wrong, because the same three letters mean something else on a directory. A directory is just a list of names pointing to files, so:
| Letter | On a file | On a directory |
|---|---|---|
r | read the content | read the list of names (ls) |
w | change the content | create, delete and rename entries inside (together with x) |
x | run it | enter it (cd) and reach what’s inside by its name |
Let’s play with a private folder, taking permissions away and giving them back on purpose:
mkdir private echo "unit 3: rwx bits" > private/notes.txt chmod 600 private # rw- on a DIRECTORY: read the list and write to it, but WITHOUT x ls private # the names are visible (r)... ls -l private # ...but not each file's details: you need x to get in cd private # and without x you cannot get in either chmod 300 private # -wx: you can get in, but not list ls private cat private/notes.txt # if you know the exact name, you can open it chmod 755 private # the usual for a directory: rwx for me, r-x for everyone else ls -l private
What really happened:
$ mkdir private $ echo "unit 3: rwx bits" > private/notes.txt $ chmod 600 private $ ls private notes.txt $ ls -l private ls: cannot access 'private/notes.txt': Permission denied total 0 -????????? ? ? ? ? ? notes.txt $ cd private bash: cd: private: Permission denied $ chmod 300 private $ ls private ls: cannot open directory 'private': Permission denied $ cat private/notes.txt unit 3: rwx bits $ chmod 755 private $ ls -l private total 4 -rw-r--r-- 1 sergio sergio 17 Oct 25 16:49 notes.txt
Let’s read it bit by bit:
- With
600(rw-, nox),ls privatedoes show the namenotes.txt:ris enough to read the list. Butls -lalso needs to look up each file’s details (permissions, size, date), and for that it has to get into the directory, which is exactly whatxgives. That’s why you get-?????????with question marks in every column. Andcd privatefails for the same reason. - With
300(-wx, nor) it’s the other way round: you can’t list (ls: cannot open directory), but if you know the exact file name, you can reach it and open it (cat private/notes.txtworks). It’s like a room with the light off: you can walk in, but you don’t see what’s there; if you know where the switch is, you’ll find it. - With
755everything is back to normal.
Thinking that deleting a file requires write permission on the file. It doesn’t: deleting means removing a name from the directory’s list, so what counts is the w (and the x) of the folder. You can delete an r-------- file if the folder is yours and has w, and you can’t delete an rw-rw-rw- file if the folder doesn’t let you write.
For a directory, the practical rule is: r and x almost always go together. A directory with r and no x (like 644) is a trap: you see the names but can’t do anything with them. You’ll see it in the exercises.
umask: the permissions everything is born with
Why does touch create files with 644 and mkdir create directories with 755? Because programs ask for some “maximum” permissions and the system applies a mask, the umask, that switches off some bits:
touch, an editor oropen(…, O_CREAT, 0666)ask for 666 (rw-rw-rw-) for a file. They never ask forx: a freshly created text file has no reason to be executable.mkdirasks for 777 (rwxrwxrwx) for a directory, because a directory withoutxis useless.
umask # the current mask, in octal umask -S # the same mask, as the permissions that ARE let through touch new.txt mkdir new-folder stat -c '%a %A %n' new.txt new-folder umask 077 # from here on, nothing for the group or for others touch secret.txt mkdir secret-dir stat -c '%a %A %n' secret.txt secret-dir
The real output:
$ umask 0022 $ umask -S u=rwx,g=rx,o=rx $ touch new.txt $ mkdir new-folder $ stat -c '%a %A %n' new.txt new-folder 644 -rw-r--r-- new.txt 755 drwxr-xr-x new-folder $ umask 077 $ touch secret.txt $ mkdir secret-dir $ stat -c '%a %A %n' secret.txt secret-dir 600 -rw------- secret.txt 700 drwx------ secret-dir
umask shows 0022 (the leading 0 belongs to the special bits you’ll see at the end) and umask -S shows it the other way round: as the permissions it does let through (u=rwx,g=rx,o=rx). The drawing explains where 644 comes from:
The bit-by-bit maths:
- File: 666 =
rw-rw-rw-. Mask 022 =----w--w-switches off the group’swand the others’w. You getrw-r--r--= 644. - Directory: 777 =
rwxrwxrwx, minus the same twows =rwxr-xr-x= 755. - With
umask 077everything for the group and others is switched off: the file ends uprw-------(600) and the directoryrwx------(700).
Reading the umask as “the permissions files will get”. It’s the other way round: it says what gets removed. If you type umask 644 expecting 644 files, what you get are 022 files (-----w--w-) that not even you can read. You’ll see it live in the exercises.
Technically it isn’t a subtraction, it’s a bit operation: permissions = requested AND NOT mask. It almost always matches subtracting digit by digit, but not always. With umask 033 and a file, 6 − 3 = 3 would make you think of 633, but 666 has no x to remove, so the result is 644. Always think of boxes being switched off, as in the drawing.
Remember the 0644 from the system calls article? That third argument of open also goes through the umask: the file is created with 0644 & ~umask. With the usual 022 umask it stays 0644; with 077 it would come out 0600. And the umask you see here only affects the terminal where you change it: to make it permanent, the umask 027 line goes in your ~/.bashrc.
Owner and group: chown and chgrp
Permissions say what each class can do, but who is the owner and which is the group? Two commands change that:
chgrp group filechanges the group.chown user filechanges the owner (andchown user:group file, both at once).
touch submission.txt ls -l submission.txt # just created: owner sergio, group sergio chgrp ic2 submission.txt # I'm in group ic2, so I can give the file to that group ls -l submission.txt chown laura submission.txt # but only root can give it away to someone else
The real output:
$ touch submission.txt $ ls -l submission.txt -rw-r--r-- 1 sergio sergio 0 Oct 25 16:49 submission.txt $ chgrp ic2 submission.txt $ ls -l submission.txt -rw-r--r-- 1 sergio ic2 0 Oct 25 16:49 submission.txt $ chown laura submission.txt chown: changing ownership of 'submission.txt': Operation not permitted
chgrp works because I’m the owner of the file and I belong to the ic2 group: a regular user can move their files to any of their own groups. But chown fails with Operation not permitted. Why can’t I give my file to Laura? Because, if you could, anyone could “plant” files on someone else (for example, to eat up their disk quota or to pass something off as theirs). That’s why only root can change the owner. With admin rights it would be sudo chown laura submission.txt; I didn’t run that command here because the lab user doesn’t have sudo.
Both commands accept -R to apply to a whole folder and everything inside it: chgrp -R ic2 ic2-lab. You’ll use it in the practice to set up the group folder.
The special bits: setuid, setgid and sticky
There are three more permissions that don’t fit in rwx and live in a fourth octal digit, the leading one:
| Bit | Value | Where you see it | What it does |
|---|---|---|---|
| setuid | 4 | s in the owner’s x | the program runs with its owner’s permissions, not those of whoever launches it |
| setgid | 2 | s in the group’s x | on a folder: whatever is created inside inherits the folder’s group |
| sticky | 1 | t in the others’ x | on a folder: each user can only delete their own files |
ls -l /usr/bin/passwd # setuid: an s where the owner's x would be ls -ld /tmp # sticky: a t where the others' x would be touch launch.sh chmod 4755 launch.sh # 4 = setuid in front of 755 mkdir shared team chmod 1777 shared # 1 = sticky: everyone writes, each user deletes only their own chmod 2770 team # 2 = setgid: whatever is created inside inherits the folder group stat -c '%a %A %n' launch.sh shared team
The real output:
$ ls -l /usr/bin/passwd -rwsr-xr-x 1 root root 64152 May 30 2024 /usr/bin/passwd $ ls -ld /tmp drwxrwxrwt 10 root root 4096 Oct 25 16:49 /tmp $ touch launch.sh $ chmod 4755 launch.sh $ mkdir shared team $ chmod 1777 shared $ chmod 2770 team $ stat -c '%a %A %n' launch.sh shared team 4755 -rwsr-xr-x launch.sh 1777 drwxrwxrwt shared 2770 drwxrws--- team
/usr/bin/passwdhasrwsr-xr-x. This is the classic example from the IC2 notes: to change your password,passwdhas to write to/etc/shadow, which only root can touch. Thanks to setuid, while it runs, the process has the program’s owner, root, as its effective user (the EUID from the processes lab), even though you launched it./tmphasrwxrwxrwt: everybody can write, but thetstops you from deleting someone else’s temporary files.- In
launch.sh,sharedandteamyou see the three bits set by us:4755,1777and2770. The fourth digit adds up just like the others: 4 + 2 = 6 would be setuid and setgid at once.
A detail about the letters: a lowercase s or t means the x is also there underneath; an uppercase one (S or T) means the special bit is set but the x isn’t. You’ll see it in level 3 of the exercises, with a drwxrws--T folder.
Linux ignores setuid on scripts (.sh): it only works on compiled programs. It’s a security measure, because a root setuid script would be very easy to trick. So launch.sh shows the s, but it wouldn’t gain any permission. The bit you’ll really use in your projects is setgid on shared folders.
Common mistakes
Mixing up the order of the triplets. It’s always owner, group, others. chmod 466 isn’t “I read and write, the rest read”: it’s r--rw-rw-, and you, the owner, can no longer write.
Thinking o means owner. o is others; the owner is u. chmod o-r takes reading away from everyone else, not from you.
Running chmod -R 644 folder “so everyone can read it”. It also removes the x from the folders inside, and nobody can get into them anymore. That’s what capital X is for (you’ll see it in the exercises).
Testing permissions as root (or with sudo). Root skips almost all of them, so “it works for me” means nothing. Always test with a regular user.
Try it in your terminal
There’s no Python Tutor here: your own terminal does the step by step. Open one (on Linux, on WSL or on the lab virtual machine), create a folder so you don’t touch anything important (mkdir ~/permissions-test && cd ~/permissions-test) and type the commands after each $. This is what you should see:
$ touch test.txt $ stat -c '%a %A %n' test.txt 644 -rw-r--r-- test.txt $ chmod 600 test.txt $ stat -c '%a %A %n' test.txt 600 -rw------- test.txt $ chmod u+x,g+r test.txt $ stat -c '%a %A %n' test.txt 740 -rwxr----- test.txt $ umask 0022
What to look at in each step:
- The first
stat: the file is born with644. If you get something else, your umask isn’t022(check it at the end withumask). - After
chmod 600: the group and others are left with---. Count it: 6 = 4 + 2 =rw-, and two zeros. - After
chmod u+x,g+r: two changes in a single command, separated by a comma with no spaces. The owner goes from 6 to 7 (6 + 1) and the group from 0 to 4. Result:740. umask: the mask that explains the644at the start.
If you feel brave, add one more step: chmod 000 test.txt and then cat test.txt. What do you think will happen, given that you’re the owner?
Summary
| Concept | In one line | Example |
|---|---|---|
| Three classes | owner (u), group (g) and others (o); Linux only looks at yours | id to see your groups |
| Ten characters | type + three rwx triplets | drwxr-x--- |
| Octal | r = 4, w = 2, x = 1; one digit per triplet | chmod 640 submission.txt |
| Symbolic | for whom + operation + permissions | chmod u+x,g-w build.sh |
| Directories | r = list, w = create and delete, x = enter | chmod 750 folder |
| umask | what gets switched off on creation: 666 and 777 minus the mask | umask 022 → 644 and 755 |
| chown / chgrp | owner only with root; group, to one of yours | chgrp ic2 submission.txt |
| Special bits | setuid 4, setgid 2, sticky 1 in the leading digit | chmod 2770 folder |
Three ideas to take away
- Ten characters, four pieces: type, owner, group and others. Linux only looks at the triplet that applies to you, in that order.
- On a folder,
xis the door key. Without it you can’t get in, even if you can see the names. - The umask takes away, it doesn’t give: it says which permissions are removed from everything that’s born.
With this you can already read and change the permissions of anything. In the practice we’ll set up the group project folder with real permissions and put it to the test with three users: Laura, Pablo and me. And in the next Linux topic we’ll leave permissions behind to look at the advanced shell: redirections, pipes and how to chain commands.
Next step: practiceLinux permissions — practice with a group project folder, three users and a permission checker (and the teammate who couldn’t build)Did this article help you? Here is how you can support SergioLearns →




What is an operating system — theory with your laptop on a study afternoon, a write() call and a CPU that never rests (and the three programs that weren’t running at the same time)