Linux File Permissions Explained: chmod, chown, and ACLs
Build the mental model for Linux file permissions from scratch. Learn chmod octal and symbolic notation, chown, umask, setuid/setgid/sticky bits, and POSIX ACLs with real-world scenarios.
Infrastructure engineer with 10+ years building production systems on AWS, GCP,…

The Commands You Type Without Thinking
Every developer has typed chmod 755 or chown www-data at some point, usually while frantically debugging a "permission denied" error at 11 PM. The command works, the error goes away, and nobody stops to think about what actually happened. Until the next time.
Linux file permissions are one of those foundational concepts that sit beneath everything else — web servers, Docker containers, SSH, deployment scripts. A shaky understanding leads to overly permissive files, security vulnerabilities, and mysterious failures that waste hours. This guide builds the mental model from scratch, covers the bits most tutorials skip (setuid, sticky bit, ACLs), and walks through the real-world scenarios where permissions trip people up.
What Are Linux File Permissions?
Definition: Linux file permissions are a set of rules that determine which users can read, write, or execute a file or directory. Every file has an owner, a group, and a set of permission bits that control access for the owner, the group members, and everyone else.
Every file and directory on a Linux system has three properties that control access:
- Owner — the user who owns the file (usually whoever created it)
- Group — a group of users who share access
- Others — everyone else on the system
For each of these three categories, three permissions can be set:
- Read (r) — view the file's contents (or list a directory's entries)
- Write (w) — modify the file (or create/delete files in a directory)
- Execute (x) — run the file as a program (or enter a directory with
cd)
Reading Permission Output
When you run ls -l, the first column shows the permission string:
$ ls -l index.html
-rw-r--r-- 1 abhi developers 4096 Apr 06 10:30 index.html
Breaking down -rw-r--r--:
| Position | Characters | Meaning |
|---|---|---|
| 1 | - | File type (- = regular file, d = directory, l = symlink) |
| 2-4 | rw- | Owner permissions: read + write, no execute |
| 5-7 | r-- | Group permissions: read only |
| 8-10 | r-- | Others permissions: read only |
Octal vs Symbolic Notation
There are two ways to express permissions: octal numbers and symbolic letters. Both do the same thing.
Octal Notation
Each permission has a numeric value: read = 4, write = 2, execute = 1. Add them up for each category:
| Octal | Binary | Permissions | Meaning |
|---|---|---|---|
7 | 111 | rwx | Read + write + execute |
6 | 110 | rw- | Read + write |
5 | 101 | r-x | Read + execute |
4 | 100 | r-- | Read only |
0 | 000 | --- | No permissions |
So chmod 755 means: owner gets 7 (rwx), group gets 5 (r-x), others get 5 (r-x). The file is readable and executable by everyone, but only the owner can modify it.
Common Octal Patterns
| Octal | Symbolic | Typical Use |
|---|---|---|
644 | rw-r--r-- | Regular files (HTML, CSS, configs) |
755 | rwxr-xr-x | Executable scripts, directories |
600 | rw------- | Private files (SSH keys, .env) |
700 | rwx------ | Private directories, ~/.ssh |
777 | rwxrwxrwx | Almost never correct. A code smell. |
Watch out: If you find yourself typing
chmod 777, stop. It means every user on the system can read, write, and execute the file. In almost every case, there's a more specific permission that solves the actual problem without opening a security hole.
Symbolic Notation
Symbolic notation uses letters and operators to add or remove specific permissions:
# Add execute for the owner
chmod u+x script.sh
# Remove write for group and others
chmod go-w config.yml
# Set exact permissions: owner=rwx, group=rx, others=rx
chmod u=rwx,go=rx deploy.sh
# Add read for everyone
chmod a+r readme.txt
The letters: u = owner (user), g = group, o = others, a = all three. The operators: + adds, - removes, = sets exactly.
chmod: Changing Permissions
Definition:
chmod(change mode) modifies the permission bits on a file or directory. It accepts either octal notation (chmod 644 file) or symbolic notation (chmod u+x file) and can operate recursively on directory trees.
# Set a file to owner-read-write, everyone-else read
chmod 644 index.html
# Make a script executable
chmod +x deploy.sh
# Recursively set directories to 755 and files to 644
find /var/www -type d -exec chmod 755 {} \;
find /var/www -type f -exec chmod 644 {} \;
Pro tip: The
find+chmodcombo above is the standard way to fix web server permissions. Directories need execute (to be entered withcd), but regular files don't. Applying 755 to everything is a common mistake that makes every file executable.
chown: Changing Ownership
chown changes who owns a file and which group it belongs to:
# Change owner to www-data
chown www-data index.html
# Change owner and group
chown www-data:www-data index.html
# Recursively change ownership of a directory
chown -R www-data:www-data /var/www/html
# Change only the group
chgrp developers project/
Ownership matters because permission bits are evaluated in order: if you're the owner, owner permissions apply and group/others permissions are ignored. If you're in the group (but not the owner), group permissions apply. Otherwise, "others" permissions apply.
umask: Default Permissions for New Files
When you create a new file, it doesn't get 777 permissions. The umask value is subtracted from the maximum to determine the default. The default umask on most systems is 022:
| Created | Max Permission | umask 022 | Result |
|---|---|---|---|
| File | 666 (no execute) | Subtract 022 | 644 (rw-r--r--) |
| Directory | 777 | Subtract 022 | 755 (rwxr-xr-x) |
Files start with a max of 666 (not 777) because the kernel intentionally skips execute permission for new files. You have to explicitly add it with chmod +x.
# Check current umask
umask # shows 0022
umask -S # shows u=rwx,g=rx,o=rx
# Set a restrictive umask (new files: 600, new dirs: 700)
umask 077
Special Bits: setuid, setgid, and Sticky
Beyond the standard nine permission bits, three special bits change how files and directories behave:
setuid (Set User ID)
When set on an executable, the process runs as the file's owner instead of the user who launched it. The classic example: /usr/bin/passwd is owned by root and has setuid set, so any user can run it and it has the privileges needed to modify /etc/shadow.
$ ls -l /usr/bin/passwd
-rwsr-xr-x 1 root root 68208 Apr 06 10:30 /usr/bin/passwd
# ^ the 's' in the owner execute position = setuid
setgid (Set Group ID)
On an executable, the process runs with the file's group. On a directory, new files created inside inherit the directory's group instead of the creator's primary group. This is useful for shared project directories where everyone in a team needs group access to each other's files.
# Set setgid on a shared directory
chmod g+s /opt/project
# New files created inside will inherit the directory's group
touch /opt/project/newfile.txt
ls -l /opt/project/newfile.txt
# Group will be the directory's group, not the creator's
Sticky Bit
On a directory, the sticky bit prevents users from deleting files they don't own, even if they have write permission on the directory. The canonical example is /tmp — everyone can create files there, but you can only delete your own.
$ ls -ld /tmp
drwxrwxrwt 15 root root 4096 Apr 06 10:30 /tmp
# ^ the 't' in the others execute position = sticky bit
# Set sticky bit
chmod +t /shared-uploads
Special Bits in Octal
Special bits are a fourth octal digit prepended to the standard three:
| Octal | Meaning | Example |
|---|---|---|
4xxx | setuid | chmod 4755 binary |
2xxx | setgid | chmod 2775 shared-dir/ |
1xxx | sticky bit | chmod 1777 /tmp |
POSIX ACLs: Beyond the Nine-Bit Model
Standard Unix permissions only let you set access for one owner, one group, and everyone else. What if you need to give read access to a specific user who isn't in the file's group, without changing group membership? That's where POSIX ACLs (Access Control Lists) come in.
# Grant read access to user 'deploy' on a specific file
setfacl -m u:deploy:r config.yml
# Grant read-write to the 'ci' group on a directory (recursive)
setfacl -R -m g:ci:rw /opt/app/
# View ACLs on a file
getfacl config.yml
# Remove a specific ACL entry
setfacl -x u:deploy config.yml
# Remove all ACLs, reverting to standard permissions
setfacl -b config.yml
When ACLs are present, ls -l shows a + after the permission string:
-rw-r--r--+ 1 abhi developers 4096 Apr 06 10:30 config.yml
# ^ indicates ACLs are set
Pro tip: ACLs are stored in the filesystem's extended attributes. Not all filesystems support them (ext4 and XFS do, FAT32 doesn't). If you're copying files between systems, use
cp -aorrsync -Ato preserve ACLs. A plaincpdrops them silently.
Real-World Permission Scenarios
Web Server Files (Nginx/Apache)
The web server process runs as a specific user (typically www-data on Debian/Ubuntu or nginx on RHEL). Files it serves need to be readable by that user:
# Standard web server permissions
chown -R www-data:www-data /var/www/html
find /var/www/html -type d -exec chmod 755 {} \;
find /var/www/html -type f -exec chmod 644 {} \;
# Upload directory needs write permission
chmod 775 /var/www/html/uploads
SSH Key Permissions
SSH is paranoid about permissions and will refuse to use keys that are too permissive:
# SSH requires these exact permissions
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_rsa # private key: owner-only
chmod 644 ~/.ssh/id_rsa.pub # public key: readable
chmod 600 ~/.ssh/authorized_keys
chmod 644 ~/.ssh/config
Docker Container Permission Issues
Docker containers are one of the most common sources of permission headaches. The root cause: the user inside the container (often UID 1000 or root) doesn't match the user on the host who owns the mounted files.
# Common problem: container runs as UID 1000 but host files owned by UID 501
# Fix option 1: match the container user to the host user
docker run --user $(id -u):$(id -g) myapp
# Fix option 2: set permissions to be group-readable
chmod -R g+rw ./app-data
# Then ensure the container user is in the right group
Watch out: Running containers as root and then using
chmod 777on mounted volumes is the most common shortcut. It works but creates files on the host owned by root that your normal user can't modify. Use--useror build your Dockerfile with a non-root user matching your host UID.
Linux Server and Hosting Costs
Understanding permissions is essential for anyone managing Linux servers. Here's what the major providers charge for basic instances:
| Provider | Smallest Instance | Monthly Cost | Specs |
|---|---|---|---|
| AWS EC2 | t4g.micro | ~$6.10 | 2 vCPU, 1 GB RAM (ARM) |
| DigitalOcean | Basic Droplet | $4.00 | 1 vCPU, 512 MB RAM |
| Hetzner | CX22 | $4.35 (EUR 3.99) | 2 vCPU, 4 GB RAM |
| Vultr | Cloud Compute | $2.50 | 1 vCPU, 512 MB RAM |
| Linode (Akamai) | Nanode | $5.00 | 1 vCPU, 1 GB RAM |
Frequently Asked Questions
What does chmod 755 actually mean?
chmod 755 sets the permission bits so the owner has read, write, and execute access (7), while the group and others have read and execute access (5). In symbolic notation, that's rwxr-xr-x. This is the standard permission for directories and executable scripts that need to be accessible by all users but only modifiable by the owner.
What is the difference between chmod and chown?
chmod changes what actions (read, write, execute) are allowed on a file. chown changes who owns the file and which group it belongs to. They solve different problems: chmod controls the permission bits, chown controls which user and group those bits apply to. You often need both to fix access issues.
Why does SSH refuse my key with "Permissions too open"?
SSH requires private keys to have 600 permissions (readable only by the owner). If the key file is readable by group or others, SSH refuses to use it because anyone who can read the file could impersonate you. Fix it with chmod 600 ~/.ssh/id_rsa. The .ssh directory itself needs 700.
What is umask and how does it affect new files?
umask is a mask that determines the default permissions for newly created files and directories. It's subtracted from the maximum permission (666 for files, 777 for directories). The default umask of 022 results in new files getting 644 and new directories getting 755. A more restrictive umask of 077 gives 600 and 700.
When should I use POSIX ACLs instead of standard permissions?
Use ACLs when standard owner/group/others permissions aren't granular enough. Common cases: granting a specific user access to a file without adding them to the file's group, giving a CI/CD service account read access to deployment configs, or setting different permissions for multiple groups on the same directory. If standard permissions solve your problem, prefer them for simplicity.
How do I fix "permission denied" errors in Docker containers?
The most common cause is a UID mismatch between the container user and the host file owner. Fix it by running the container with --user $(id -u):$(id -g) to match the host user, or by building your Dockerfile with a non-root user whose UID matches your host. Avoid chmod 777 on mounted volumes as a workaround.
Conclusion
Linux file permissions are a small system with a big surface area. The nine basic bits (read, write, execute for owner, group, others) cover 90% of cases. Octal notation (644, 755) is fastest once you memorize the patterns. Special bits (setuid, setgid, sticky) handle the edge cases around shared directories and privileged executables. And ACLs are there when the standard model isn't granular enough.
The next time you type chmod 755, you'll know exactly what those three digits mean and why. More importantly, you'll know when 755 is wrong and what the right answer actually is.
Written by
Abhishek Patel
Infrastructure engineer with 10+ years building production systems on AWS, GCP, and bare metal. Writes practical guides on cloud architecture, containers, networking, and Linux for developers who want to understand how things actually work under the hood.
Enjoyed this article?
Get more like this in your inbox. No spam, unsubscribe anytime.