Linux file permissions: chmod, chown and umask explained
Read rwxr-xr-x at a glance, set it with chmod and chown, and fix the classic permission mistakes.
Part 5 of 12 in Linux basics for self-hosters. Last updated
In short: Every file has an owner, a group and three sets of rights (read, write, execute) for the owner, the group and everyone else. chmod 644 file gives the owner read and write and everyone else read; chmod 755 adds execute, which scripts and directories need. chown alice:media file changes the owner and group. Never fix a permission error with chmod 777; find out which user needs access and give exactly that user or group the right.
Reading ls -l
ls -l shows the permissions in the first column:
-rw-r--r-- 1 alice alice 220 Sep 27 10:12 notes.txt
-rwxr-xr-x 1 alice alice 1408 Sep 27 10:15 backup.sh
drwxr-x--- 2 alice media 4096 Sep 27 10:20 photos Split the first ten characters into four parts:
| Part | Example | Meaning |
|---|---|---|
| Type | d | - file, d directory, l symbolic link |
| Owner | rwx | The user in column 3 (alice) may read, write and execute |
| Group | r-x | Members of the group in column 4 (media) may read and enter |
| Others | --- | Everyone else gets nothing |
On a file, r reads the content, w changes it and x runs
it. On a directory, r lists the names, w creates and deletes files
in it, and x lets you enter it at all. Deleting a file is a right on the
directory, not on the file. stat -c '%a %A %U:%G' file prints the octal mode,
the symbolic mode and the owner in one line.
chmod: octal and symbolic
Octal sets all three classes at once. Add up read (4), write (2) and execute (1) per class: 6 is read and write, 5 is read and execute, 7 is everything.
| Mode | Symbolic | Typical use |
|---|---|---|
644 | rw-r--r-- | Normal files, config files that are not secret |
600 | rw------- | Secrets: private keys, credential files |
755 | rwxr-xr-x | Scripts, programs and ordinary directories |
750 | rwxr-x--- | Directories shared with one group only |
700 | rwx------ | Private directories such as ~/.ssh |
Symbolic changes only what you name: u (user, the owner),
g (group), o (others), a (all), with +,
- or =. Separate several changes with a comma and no space:
chmod 600 ~/.config/rclone/rclone.conf # owner only
chmod u+x backup.sh # add execute for the owner
chmod g+w,o-rwx shared.txt # group may write, others get nothing
chmod u=rwx,go=r script.sh # exactly rwxr--r--
chmod -R u+rwX,go-rwx ~/private # capital X: execute only on directories The chmod calculator converts between the two forms and explains every bit. The reference is chmod(1).
chown and groups
Only root may give a file to another user, so chown usually needs
sudo. The form is user:group; leave out either side to keep it.
sudo chown alice notes.txt # new owner, group unchanged
sudo chown alice:media /srv/media # owner and group
sudo chown :media /srv/media/film.mkv # group only (same as chgrp media)
sudo chown -R alice:media /srv/media # the whole tree Groups are how you share without opening things to everyone: put the users that need access in one group, give the directory to that group, and set 750 or 770. Adding users to groups is covered in users, groups and sudo.
umask: the default for new files
New files start at 666 and new directories at 777, minus the bits in your umask. The common
default umask 022 removes write for group and others, so new files are 644 and new
directories 755. Run umask to see yours.
umask # 0022
umask 027 # this shell only: new files 640, new directories 750
touch a.txt; mkdir d; stat -c '%a %n' a.txt d
# 640 a.txt
# 750 d The same with the default umask, then chmod in both notations, run in an empty directory:
$ umask; touch notes.txt; mkdir share; ls -ld notes.txt share
0022
-rw-r--r-- 1 user user 0 Sep 28 06:29 notes.txt
drwxr-xr-x 1 user user 0 Sep 28 06:29 share
$ chmod 640 notes.txt; chmod u=rwx,g=rx,o= share; stat -c '%A %a %n' notes.txt share
-rw-r----- 640 notes.txt
drwxr-x--- 750 share Real output on openSUSE Tumbleweed, 2026-09-28; user and host names and addresses redacted.
For a systemd service, set UMask=0027 in its unit instead of changing the shell.
setgid and the sticky bit
- setgid on a directory (
chmod g+s dir, or a leading 2 as in2775): new files inside get the directory's group instead of the creator's. Ideal for a shared/srv/media. - Sticky bit (
chmod +t dir, a leading 1): in a directory everyone may write to, only the owner of a file may delete it./tmpisdrwxrwxrwt, which is 1777. - setuid (a leading 4) runs a program as its owner. It belongs to a handful of
system programs such as
passwd; never set it on a script.
Both on the share directory from above (mode 750). The group's x
turns into s. Others have no x, so the sticky bit shows as a capital
T instead of t, and the leading digit becomes 3 (2 for setgid plus 1
for sticky):
$ chmod g+s share; chmod +t share; ls -ld share; stat -c '%A %a' share
drwxr-s--T 1 user user 0 Sep 28 06:29 share
drwxr-s--T 3750 Real output on openSUSE Tumbleweed, 2026-09-28; user and host names and addresses redacted.
Permissions and rootless containers
Rootless podman maps users inside the container to other numbers outside it. Root in the
container is your own user on the host; user 1000 in the container becomes a number from your
range in /etc/subuid, such as 100999. That is why files in a bind-mounted directory
sometimes show an owner you have never heard of.
To fix ownership from the container's point of view, run the command inside your user
namespace, podman unshare chown 1000:1000 ~/appdata, or add the :U
option to the volume (Volume=%h/appdata:/data:U in a quadlet) and podman chowns it
for you.
Here is the mapping in action: a file handed to user 1000 inside the namespace belongs to 100999 on the host (the subuid range starts at 100000, and container UID 1 maps to 100000):
$ touch owned-by-container
$ podman unshare chown 1000:1000 owned-by-container
$ ls -ln owned-by-container
-rw-r--r-- 1 100999 100999 0 Sep 28 06:37 owned-by-container
$ podman unshare ls -ln owned-by-container
-rw-r--r-- 1 1000 1000 0 Sep 28 06:37 owned-by-container Real output on openSUSE Tumbleweed, podman 6.0.2, 2026-09-28; user and host names and addresses redacted.
Common mistakes
Fixing "Permission denied" with chmod 777
777 lets every user and every compromised service on the machine change or replace the file.
Find out which user runs the program (ps -o user= -p PID or the
User= of the unit), then give that user or its group the one right it lacks.
chmod -R 755 on a whole tree
It marks every photo and text file as executable. Use capital X
(chmod -R u=rwX,go=rX dir) or set directories and files separately:
find dir -type d -exec chmod 755 {} + and
find dir -type f -exec chmod 644 {} +.
chmod: invalid mode: 'u=rwx,'
There is a space after the comma, as in chmod u=rwx, go=r file. chmod reads
go=r as a file name. Write chmod u=rwx,go=r file.
WARNING: UNPROTECTED PRIVATE KEY FILE!
ssh refuses a private key that group or others can read. Run
chmod 600 ~/.ssh/id_ed25519 and chmod 700 ~/.ssh. The
shell command explainer is handy when a
permission fix someone posted looks suspiciously long.
Frequently asked questions
What does chmod 755 mean?
Each digit is one class: owner, group, others. 7 is read (4) + write (2) + execute (1), 5 is read + execute. So 755 is rwxr-xr-x: the owner may do everything, the group and everyone else may read and run the file or enter the directory, but not change it.
What permissions should ~/.ssh have?
The directory 700 (drwx------), private keys and authorized_keys 600 (-rw-------), public keys 644. OpenSSH refuses to use a private key that others can read and ignores authorized_keys when the directory is writable by others.
Why does execute permission matter on a directory?
On a directory, x means you may enter it and reach the files inside, and r means you may list the names. Without x you cannot open a file in it even if the file itself is readable, so directories almost always need x wherever they have r.
How do I make a script executable?
chmod +x script.sh adds execute for everyone your umask allows, or chmod u+x script.sh for the owner only. Then run it as ./script.sh. The first line should be a shebang such as #!/usr/bin/env bash.