Reading and setting permissions

rwx, octal modes, directories and umask.

Beginner14 min · lesson 11 of 29

Every file and directory on a Linux system carries permissions that answer one question: who may read it, change it, or run it? This lesson teaches you to read those permissions at a glance, set them precisely with chmod in both its number and letter forms, understand why execute means something different on a directory, and see where a new file's permissions come from. Reading permissions correctly is most of understanding why a command is refused and why a server is or is not safe.

Reading the nine switches

The command that shows a file's permissions is ls -l (list, long format). The first field it prints is ten characters. The first character is the type: - for an ordinary file, d for a directory, l for a symbolic link. The other nine are three groups of three, always in the same order: the owner, then the group, then everyone else (often called "others" or "the world"). Inside each group the order is always read, write, execute. A letter means the permission is on; a dash means off.

deploy@web01 · Ubuntu 26.04 LTS
$ mkdir -p ~/modes && cd ~/modes printf "%s\n" "#!/bin/bash" "echo backing up" > backup.sh chmod 750 backup.sh ls -l backup.sh
-rwxr-x--- 1 deploy deploy 28 Sep 27 08:33 backup.sh

Read -rwxr-x--- in three blocks. The owner deploy has rwx, all three. The group deploy (the second name on the line) has r-x: read and execute, but not write. Everyone else has ---, nothing. So the owner can read, change and run this script, members of the group can read and run it but not change it, and no other account can touch it.

The nine switches of -rwxr-x---
Owner (deploy) = 7
read (4)
see the contents
write (2)
change the contents
execute (1)
run it as a program
Group (deploy) = 5
read (4)
see the contents
off (0)
cannot change it
execute (1)
run it as a program
Others = 0
off (0)
no read
off (0)
no write
off (0)
no execute
Each group of three becomes one octal digit: read is 4, write is 2, execute is 1, added together.

The same permissions as a number: octal chmod

Writing rwxr-x--- in full is tedious, so the same thing is written as a three-digit octal number (base 8, each digit 0 to 7). Give each switch a value: read is 4, write is 2, execute is 1. Add up the switches that are on in a group to get one digit. rwx is 4+2+1 = 7, r-x is 4+1 = 5, rw- is 4+2 = 6, r-- is 4. So rwxr-x--- is 750 and rw-r----- is 640. The command that sets permissions is chmod (change mode).

deploy@web01 · Ubuntu 26.04 LTS
$ cd ~/modes echo "token=PLACEHOLDER" > db.env ls -l db.env chmod 640 db.env ls -l db.env
-rw-rw-r-- 1 deploy deploy 18 Sep 27 08:33 db.env -rw-r----- 1 deploy deploy 18 Sep 27 08:33 db.env

The file was created -rw-rw-r-- (664, the reason is the umask section below), and chmod 640 closed it to -rw-r-----: the owner reads and writes, the group reads, others get nothing. A few numbers cover most daily work: 644 for a file the world may read, 640 for one only a trusted group may read, 600 for a private file, 755 for a program or directory others may use, 700 for a directory only you may enter. A leading fourth digit, as in 0644, holds the special bits covered in the SUID, SGID and sticky-bit lesson; a plain 0 means none are set, so 0644 and 644 describe the same file.

Changing one switch at a time: symbolic chmod

The number form rewrites all nine switches at once. When you want to flip a single switch and leave the rest alone, use the letter form. Name a class (u for the owner, g for group, o for others, a for all), then + to add, - to remove or = to set exactly, then the switches.

deploy@web01 · Ubuntu 26.04 LTS
$ cd ~/modes echo "notes" > report.txt chmod 664 report.txt ls -l report.txt chmod u+x,go-w report.txt ls -l report.txt
-rw-rw-r-- 1 deploy deploy 6 Sep 27 08:33 report.txt -rwxr--r-- 1 deploy deploy 6 Sep 27 08:33 report.txt

chmod u+x,go-w turned on the owner's execute bit and turned off the write bit for both group and others, in one step, leaving every other switch as it was. Separate several changes with commas.

On a directory, execute is the key to the door

This is where people are caught out. On a file, execute means run it as a program. On a directory it means something different: it is permission to enter the directory and reach the files inside by name. Read on a directory is a separate power: it lets you list the names inside. The two come apart. Make a directory with one file in it, then give the directory read but not execute (mode 600):

deploy@web01 · Ubuntu 26.04 LTS
$ cd ~/modes mkdir project echo "row 40 has the number" > project/data.csv
$ cd ~/modes chmod 600 project ls project
data.csv
$ cd ~/modes/project
-bash: line 1: cd: /home/deploy/modes/project: Permission denied

ls lists the name data.csv because you can read the directory, but cd into it is denied because you cannot enter it. Now the opposite, execute but not read (mode 100):

deploy@web01 · Ubuntu 26.04 LTS
$ cd ~/modes chmod 100 project ls project
ls: cannot open directory 'project': Permission denied
$ cd ~/modes/project && cat data.csv
row 40 has the number

Now ls cannot list the contents (cannot open directory, and it exits with status 2), but you can still enter the directory and open a file whose name you already know. This split is common: a directory set to 701 or 711 lets programs reach a file by its exact path without letting anyone list what else is there. Give the practice directory an ordinary mode again before you move on; a directory you cannot list is also one rm -r cannot empty:

deploy@web01 · Ubuntu 26.04 LTS
$ cd ~/modes chmod 755 project ls -ld project
drwxr-xr-x 2 deploy deploy 4096 Sep 27 08:33 project
Deleting a file is the directory's decision, not the file's
Whether you can delete a file depends on the directory that holds it, not on the file's own permissions. Anyone who can write to a directory and enter it (w and x) can remove files inside it, even files they do not own or cannot read. That is why a directory writable by everyone is dangerous, and why /tmp carries the sticky bit, which the lesson on SUID, SGID and the sticky bit covers.

Where new files get their permissions: the umask

You never set the permissions of every file you create by hand, so where do the defaults come from? From the umask (user file-creation mask), a set of switches to withhold from every new file. A program asks the kernel for a file with a starting mode, usually 666 for a file (read and write for all, never execute) or 777 for a directory; the umask then clears the bits it names. On an Ubuntu 26.04 login the umask is 0002, which withholds only write from others. umask on its own prints the current value; then watch what new files get:

deploy@web01 · Ubuntu 26.04 LTS
$ cd ~/modes umask touch upg.txt mkdir upg.dir ls -ld upg.txt upg.dir
0002 drwxrwxr-x 2 deploy deploy 4096 Sep 27 08:33 upg.dir -rw-rw-r-- 1 deploy deploy 0 Sep 27 08:33 upg.txt

The file is 664 and the directory 775: the owner and group may write, others may only read. This is safe on Ubuntu because of user private groups. When useradd makes an account, it also makes a group of the same name whose only member is that user, and that becomes the primary group. A umask of 0002 therefore opens a new file to a group that contains only you, unless you deliberately place the file in a shared group's directory.

Root uses 0022, and so does a normal login on RHEL 10, which does not apply the user-private-group rule to the umask. (On Ubuntu the login umask comes from /etc/login.defs and the PAM login stack; the hardening course shows where to change it.) Setting the umask in the shell shows the difference:

deploy@web01 · Ubuntu 26.04 LTS
$ cd ~/modes umask 022 touch root-style.txt mkdir root-style.dir ls -ld root-style.txt root-style.dir
drwxr-xr-x 2 deploy deploy 4096 Sep 27 08:33 root-style.dir -rw-r--r-- 1 deploy deploy 0 Sep 27 08:33 root-style.txt
$ cd ~/modes umask 027 touch secret.log ls -l secret.log
-rw-r----- 1 deploy deploy 0 Sep 27 08:33 secret.log

A umask of 0022 gives files 644 and directories 755, the classic default that makes new files world-readable. On a server holding other people's data, 0027 is a quiet, real improvement: it withholds write from the group and every permission from others, so new files come out 640 and nothing is world-readable by accident. A umask set in a shell lasts only for that shell and the programs it starts.

Services are a separate case. A service started by systemd does not inherit anyone's login umask: it gets the UMask= setting of its unit, which is 0022 unless the unit says otherwise.

deploy@web01 · Ubuntu 26.04 LTS
$ systemctl show -p UMask cron.service
UMask=0022

So setting 0027 in your ~/.profile makes the files you create private, but not the files a web server or a backup service writes. The hardening course sets UMask= per service.

The failures permissions produce

Two kinds of refusal come straight from permissions. The first is a file you may not read:

deploy@web01 · Ubuntu 26.04 LTS
$ cd ~/modes echo "secret" > locked.txt chmod 000 locked.txt cat locked.txt
cat: locked.txt: Permission denied

With mode 000 even the owner is refused, and cat reports Permission denied and exits with status 1. The owner is not special here: the kernel checks the owner bits, and they are empty. As the owner you can always run chmod to give yourself access back, which is the usual fix. The second is a script without its execute bit:

deploy@web01 · Ubuntu 26.04 LTS
$ cd ~/modes printf "%s\n" "#!/bin/bash" "echo it ran" > run-me.sh chmod 644 run-me.sh ./run-me.sh
-bash: line 4: ./run-me.sh: Permission denied
$ cd ~/modes chmod +x run-me.sh ./run-me.sh
it ran

The file exists and holds a valid script, but without execute permission the kernel refuses to run it, and bash reports Permission denied with status 126 (the status a shell uses for a command that was found but could not run). Adding the execute bit with chmod +x lets it run. This is the daily reason a freshly written script "does nothing".

The mirror image of a readable secret is a writable file the world can rewrite. A world-writable file that a privileged job later runs or trusts is a real risk. You can sweep for them, and the sweep should come back empty on a healthy tree:

deploy@web01 · Ubuntu 26.04 LTS
$ cd ~/modes chmod 666 db.env find ~/modes -xdev -type f -perm -0002
/home/deploy/modes/db.env

-perm -0002 matches any file whose "others" write bit is on. The leading - means "at least these bits": other bits may be set as well. Without it, -perm 0002 would match only a file whose mode is exactly 0002. Set sensitive files to 600 or 640, and never leave a file that a service reads at a mode that lets others write it.

Try this

In a practice directory, create a file and set it to 640 with chmod, then confirm the string reads -rw-r----- with ls -l. Make a directory, put a file in it, and set the directory to 600: check that ls on it still prints the name while cd into it is refused, then set it back to 755. In a new shell (a umask you set earlier lasts until that shell exits), run umask to see your login value (0002 on Ubuntu, 0022 on RHEL), then set it to 0027, create a new file, and confirm it comes out 640. Finally write a one-line script, run it and read the Permission denied and status 126, then chmod +x it and run it again. Remove the practice directory with rm -r when you are done, and open a new shell so the umask is back to normal.

Takeaway

Read the ten characters as type plus three blocks of owner, group, others, and remember that execute on a directory is permission to enter it, not to list it. Set permissions with the narrowest number that still works (640 and 600 for anything sensitive), and let the umask keep new files off the world-readable list rather than fixing each one by hand.

Quick check
01You can run ls on a directory and see the file names inside, but cd into it fails with "Permission denied". Which permission are you missing?
Incorrect — Read is what lets you list the names, and that is working. cd does not check the read bit.
Correct — On a directory, execute is permission to enter it and reach the files by name; without it cd is refused even when you can list the names.
Incorrect — Write controls creating and deleting entries inside the directory, not entering it. Entering changes nothing.
Incorrect — A file's own bits do not decide whether you can enter the directory that holds it, and cd opens no file.
02A teammate suggests running with umask 0002 on a shared Ubuntu server so colleagues can edit each other's files. On a normal Ubuntu 26.04 install, what does 0002 actually do to a new file's exposure?
Incorrect — 0002 withholds write from others, it does not grant it. Others get read at most, never write.
Incorrect — Root already bypasses these checks. The umask changes what the owner's group and others get, not root.
Incorrect — New files take the creator's primary group, which under user private groups is a group of just that user, not a shared staff group.
Correct — Each user's primary group contains only them, so 0002 opens a file to a group of one; sharing needs the file placed in a directory owned by a shared group.
03You wrote deploy.sh, and running ./deploy.sh prints "Permission denied" with exit status 126, though cat shows the script is correct. What is wrong?
Correct — The file was created without execute permission; 126 is the shell's status for a command that was found but could not be executed, and chmod +x fixes it.
Incorrect — A syntax error is reported by bash with a message about the line, not as "Permission denied", and the script would have started.
Incorrect — A name not found gives status 127 and "command not found". Here the file was found; it just could not be executed.
Incorrect — Scripts run as any user once they are executable. The missing piece is the execute bit, not privilege.

Related