Linux File Permissions: A chmod and chown Deep Dive
Understand the Linux permission model from user/group/other to setuid and sticky bits, with practical chmod and chown patterns you can use today.
What you'll learn
- ✓How the user/group/other model works
- ✓How to read rwx and octal notation
- ✓When to use chmod symbolic vs numeric form
- ✓How setuid, setgid, and sticky bits behave
- ✓How to fix permission problems quickly
Prerequisites
- •A terminal you can run commands in
What and Why
Every file and directory on Linux carries an owner, a group, and a 9-bit permission mask describing what the owner, the group, and everyone else may do with it. The kernel checks those bits on every open, exec, or unlink. Get them wrong and your web server returns 403, your cron job cannot write a log, or worse, your private key becomes world-readable.
Permissions are not glamorous, but they are the most common source of the “it works on my machine” class of bugs. A solid grasp pays off forever.
Mental Model
Run ls -l on a file:
-rw-r--r-- 1 alice devs 1240 Jun 27 09:14 notes.txt
The first character is the file type (- regular, d directory, l symlink). The next nine characters split into three groups of three: owner, group, other. Each triplet is read, write, execute.
user group other
r w x | r w x | r w x
4 2 1 | 4 2 1 | 4 2 1
----- ----- -----
7 5 5 -> rwxr-xr-x Octal notation simply sums the bits in each triplet. 755 is rwxr-xr-x, the classic permissions for an executable or directory. 644 is rw-r--r--, the classic for a readable file. 600 is rw-------, used for private keys.
For directories the bits mean something slightly different: r lets you list names, w lets you create or delete entries inside, and x lets you traverse into the directory. You can have x without r (you can cd in but not ls), and you can have r without x (you see names but cannot open them).
Hands-on Example
Create a working area and explore:
mkdir perms-demo && cd perms-demo
echo "hello" > note.txt
ls -l note.txt
Tighten and loosen using symbolic chmod:
chmod u+x note.txt # add execute for user
chmod g-w,o-r note.txt # remove group write and other read
chmod a=r note.txt # set all three to read only
Now the numeric form:
chmod 640 note.txt # rw- r-- ---
chmod 755 some_script.sh
Change ownership (requires root for files you do not own):
sudo chown alice:devs note.txt
sudo chown -R www-data:www-data /var/www/site
Recursive chmod can be too coarse because directories and files need different bits. Use find:
find /var/www/site -type d -exec chmod 755 {} +
find /var/www/site -type f -exec chmod 644 {} +
Now the special bits. Setuid (4) on an executable makes it run as the file owner; you see this on /usr/bin/passwd. Setgid (2) on a directory makes new entries inherit the directory’s group, which is essential for shared team folders. The sticky bit (1) on a directory means only the file’s owner can delete it; this is why /tmp is 1777.
chmod 2775 /srv/shared # setgid directory, group writable
chmod 1777 /srv/upload # sticky, world writable like /tmp
ls -ld /srv/shared /srv/upload
Octal becomes four digits when special bits are present: 2775 means setgid plus rwxrwxr-x.
Default permissions on new files come from umask. Run umask to see it; a value of 022 means “remove write for group and other”, so a new file born 666 ends up 644 and a new directory born 777 ends up 755. Change it with umask 027 for stricter defaults.
Common Pitfalls
Running chmod -R 777 to “fix” a problem. That makes every file world-writable, which is rarely what you want and can be exploited. Always reach for the minimal bits needed.
Confusing ownership with permissions. Changing permissions does not change who owns the file. If a service runs as www-data, it needs to either own the files or belong to a group that does.
Forgetting that you need execute on every parent directory to reach a file. A perfect 644 file inside a 700 directory you do not own is still unreadable.
Editing a file via sudo and ending up with a file owned by root. The original user can no longer write to it. Use sudoedit or remember to chown it back.
Setuid on shell scripts. Most kernels ignore the bit on scripts for security reasons. Use a compiled binary or a tiny wrapper.
Practical Tips
Use stat -c '%a %U %G %n' file to print octal mode and owner in one line; great for diffs and audits.
Use getfacl and setfacl when the user/group/other model is too coarse. ACLs let you grant a specific user access without touching the group.
Group your service users in a shared supplementary group rather than juggling chmod 777. Add the user with usermod -aG groupname username and re-login.
When deploying, set permissions in your provisioning script, not by hand. A drift-free chmod 750 /opt/app in Ansible beats a sleepy sudo chmod at 2am.
For private keys, anything other than 600 will make OpenSSH refuse to use them. That is a feature, not a bug.
Wrap-up
The Linux permission model has three actors, three actions, and a handful of special bits. With ls -l, chmod, chown, and find you can lock down a directory tree, fix a broken web root, or share a folder across a team without leaking files. Build the muscle of reading rwx strings instantly and the rest becomes routine.
Related articles
- Linux Linux File Permissions Explained
Read every rwx string at a glance, change permissions with chmod (symbolic and numeric), change ownership with chown, and understand when to use sudo. With runnable examples.
- Django Django Permissions and Authorization
Move beyond is_authenticated. Learn how to model groups, object-level permissions, and DRF permission classes cleanly.
- Linux Linux cgroups Explained: How Containers Get Their Limits
A practical introduction to Linux control groups. Learn what cgroups do, how v1 and v2 differ, and how Docker and Kubernetes use them to cap CPU and memory.
- Linux Linux Cron and systemd Timers: A Practical Comparison
Run scheduled jobs on Linux with cron or systemd timers. How they differ, when to choose each, and recipes that survive reboots and log rotations.