Anyone who has spent more than an afternoon in a Linux terminal has run into Permission denied at some point — trying to execute a script, edit a config file owned by root, or write to a directory that isn’t theirs. Behind that error sits one of the oldest and most consistently effective security models in computing: the UNIX permission system, inherited almost unchanged by Linux from its UNIX ancestors in the 1970s, and still the primary access control mechanism on every Linux system running today, from Raspberry Pis to hyperscale cloud infrastructure.
This article explains that model in depth — from the basic owner/group/other structure everyone eventually memorizes, to special permission bits, ACLs, and how it all maps onto the underlying file system data structures.
The Core Model: Owner, Group, Other
Every file and directory on a Linux system has three categories of “who,” each of which can independently be granted three categories of “what”:
Who:
- Owner (user) — the individual account that owns the file.
- Group — a group of users associated with the file.
- Other — everyone else on the system.
What:
- Read (r) — view file contents, or list a directory’s contents.
- Write (w) — modify file contents, or create/delete/rename entries within a directory.
- Execute (x) — run a file as a program/script, or “enter” a directory (traverse into it) if it’s a directory.
Running ls -l on a file shows this encoded in a ten-character string:
-rwxr-xr-- 1 alice developers 4096 Aug 12 10:15 deploy.sh
Breaking that down:
- rwx r-x r--
│ │ │ │
│ │ │ └── other: read only
│ │ └───────── group: read + execute
│ └──────────────── owner: read + write + execute
└───────────────────── file type (- = regular file, d = directory, l = symlink)
Numeric (Octal) Notation
Permissions are also commonly expressed as a three-digit octal number, where each digit represents a category (owner, group, other) and is the sum of read (4), write (2), and execute (1):
| Permission | Value |
|---|---|
| read | 4 |
| write | 2 |
| execute | 1 |
So rwxr-xr-- becomes:
- owner: rwx = 4+2+1 = 7
- group: r-x = 4+0+1 = 5
- other: r– = 4+0+0 = 4
Giving chmod 754 deploy.sh as the equivalent command to set that exact permission set. This numeric shorthand is ubiquitous in scripts, documentation, and Dockerfiles because it’s compact and unambiguous.
The Core Commands
chmod— change permissions.chmod 750 script.sh # numeric modechmod u+x script.sh # symbolic mode: add execute for ownerchmod go-w file.txt # remove write for group and otherchmod -R 755 /var/www/html # recursive, common for web rootschown— change file owner (and optionally group).chown alice file.txtchown alice:developers file.txtchown -R www-data:www-data /var/www/htmlchgrp— change only the group.chgrp developers file.txt
Only the file’s owner or a privileged user (root, or someone with CAP_CHOWN) can change ownership; a file’s owner can change its own permissions with chmod even without being root, which is a subtlety that trips people up — you don’t need root to loosen or tighten permissions on files you own.
Directories Are Special
Permission bits mean something slightly different on directories than on regular files, and this is one of the most common sources of confusion:
- Read on a directory lets you list its contents (
ls), but not access details of the files inside unless you also have execute. - Execute on a directory lets you
cdinto it and access files inside by name, even without read — this is why you sometimes see directories with--xpermission bits, used deliberately to allow access to a specific known file inside without allowing a full directory listing. - Write on a directory controls whether you can create, delete, or rename entries within that directory — critically, deleting a file is governed by the directory’s write permission, not the file’s own permissions. This is why you can sometimes delete a read-only file: if you own the containing directory and have write access to it, the file’s own permission bits don’t protect it from deletion.
Special Permission Bits: SUID, SGID, and the Sticky Bit
Beyond the basic rwx triad, Linux supports three special bits that modify default behavior:
SUID (Set User ID) — octal 4000 When set on an executable, the program runs with the privileges of the file’s owner, not the user who launched it. The classic example is /usr/bin/passwd, owned by root with the SUID bit set, so that any regular user can run it to change their own password (which requires writing to the root-owned /etc/shadow) without being root themselves.
ls -l /usr/bin/passwd
-rwsr-xr-x 1 root root 68208 ... /usr/bin/passwd
The lowercase s in the owner execute position signals SUID is set (and execute is also set); an uppercase S would mean SUID is set but execute is not, which is unusual and often a misconfiguration.
SGID (Set Group ID) — octal 2000 On an executable, similarly runs with the group privileges of the file’s group. On a directory, SGID has a different and very useful effect: new files and subdirectories created inside inherit the directory’s group rather than the creating user’s primary group — commonly used for shared team directories so that everyone’s files automatically belong to the right collaborative group.
Sticky Bit — octal 1000 On a directory, restricts deletion so that only the file’s owner (or root, or the directory’s owner) can delete or rename files within it, even if other users have write access to the directory. The canonical example is /tmp, which is world-writable but uses the sticky bit to prevent users from deleting each other’s temporary files:
ls -ld /tmp
drwxrwxrwt 15 root root 4096 Aug 15 09:00 /tmp
The trailing t shows the sticky bit is active.
Setting these: chmod 4755 file (SUID), chmod 2775 dir (SGID), chmod 1777 dir (sticky), or symbolically chmod u+s, chmod g+s, chmod +t.
Under the Hood: Inodes and Permission Storage
Every file on a Linux (ext4, XFS, Btrfs, etc.) file system is represented by an inode — a data structure holding metadata about the file: owner UID, group GID, permission bits, timestamps, size, and pointers to the data blocks on disk. The filename itself lives separately, in a directory entry that maps a name to an inode number. Permission bits are stored directly in the inode’s mode field, alongside the file-type bits.
This separation explains some otherwise-surprising behavior: hard links to the same inode always share the exact same permissions, owner, and group, because they are the same inode viewed through different directory entries — changing permissions via one hard-linked path changes it for all of them, instantly, because there’s only one inode being modified.
Directory Entry Inode Table
"report.txt" -----> inode #4521 { mode: 0644, uid: 1000, gid: 1000, ... }
"report_link" ----> inode #4521 (same inode — hard link)
Access Control Lists (ACLs): Beyond Owner/Group/Other
The classic owner/group/other model has a real limitation: what if you need to grant a fourth user read access without changing the group, or without exposing the file to everyone in that group? POSIX ACLs solve this by allowing arbitrary additional user and group entries beyond the basic three.
setfacl -m u:bob:rw file.txt # give user bob read+write, independent of owner/group
setfacl -m g:auditors:r file.txt # give group auditors read access
getfacl file.txt # view the full ACL
When a file has an ACL, ls -l shows a + after the permission string to signal there’s more going on than the basic bits show:
-rw-rwx---+ 1 alice developers 1024 Aug 15 09:00 file.txt
ACLs are the mechanism used, for example, when a company needs one specific external contractor to read a file inside a project directory without joining the project’s primary group.
Comparisons with Other Operating Systems
- Windows/NTFS uses a fundamentally different, more granular model: discretionary access control lists (DACLs) attached to every file and folder, with fine-grained permissions (read, write, execute, delete, modify, take ownership, and more) assignable to any number of individual users or groups, plus inheritance flags controlling how permissions propagate to child objects. NTFS ACLs are more expressive out of the box than the classic UNIX rwx model, though Linux ACLs close much of that gap when needed.
- macOS, being UNIX-derived (Darwin/BSD), uses the same owner/group/other rwx model as Linux at its core, layered with POSIX ACLs and additionally with Apple’s extended attributes and sandboxing entitlements for app-level restrictions beyond plain file permissions.
- Android, built on the Linux kernel, actually uses standard UNIX permissions at the kernel level, but layers a much stronger process-level sandbox on top — each app runs as its own dedicated UID, so one app’s files are inaccessible to another app’s process even though both nominally run “as different users” under the same familiar Linux permission model, which is a clever repurposing of a 1970s design for modern app isolation.
- iOS doesn’t expose a user-facing permission model at all; app sandboxing at the OS/kernel level (built on a Darwin/BSD-derived kernel similar in lineage to macOS) enforces isolation without the user ever seeing
chmod-style semantics.
Real-World Troubleshooting Scenarios
“Permission denied” running a script you just wrote
chmod +x myscript.sh
./myscript.sh
Scripts aren’t executable by default when created with a text editor; you must explicitly grant execute permission.
A web server returning 403 Forbidden for static files Usually a directory-execute or file-read issue: the web server’s user (often www-data or nginx) needs execute permission on every directory in the path to the file, and read permission on the file itself.
chmod 755 /var/www/html
chmod 644 /var/www/html/index.html
chown -R www-data:www-data /var/www/html
Cannot delete a file you don’t own, in a directory you do own This works precisely because directory write permission governs deletion — even though you might expect the file’s own permissions to be the deciding factor.
SUID binaries as a security audit target Since SUID root binaries run with elevated privilege, they’re a classic privilege-escalation vector if misconfigured or vulnerable. A standard hardening/audit step:
find / -perm -4000 -type f 2>/dev/null
This lists every SUID binary on the system, which a security review should compare against an expected baseline.
Default Permissions: umask Explained
New files and directories don’t appear with arbitrary permissions — they’re governed by the umask (user file-creation mask), a value that subtracts permission bits from a theoretical maximum default whenever a new file or directory is created. The default maximum is 666 (rw-rw-rw-) for regular files and 777 (rwxrwxrwx) for directories; the umask value is subtracted from these to produce the actual default permissions.
umask
# 0022
# For a new file: 666 - 022 = 644 (rw-r--r--)
# For a new directory: 777 - 022 = 755 (rwxr-xr-x)
A umask of 022 (a very common default) removes write permission for group and other on newly created files, which is why freshly created files typically appear as 644 rather than the theoretical 666 maximum — nobody but the owner gets write access by default unless something explicitly changes that afterward. Administrators managing shared multi-user systems often tighten this further (a umask of 077, for instance, removes all group/other access entirely) for environments where user files should be private by default rather than merely non-writable by others.
Immutable and Append-Only Attributes
Beyond the standard permission model, Linux file systems support additional extended attributes that constrain what even the owner (including root, in some configurations) can do to a file — a layer of protection distinct from and stronger than ordinary permission bits:
chattr +i important-config.conf # immutable: cannot be modified or deleted by anyone, including root, until the flag is removed
chattr +a audit.log # append-only: data can be added but existing content cannot be altered or removed
lsattr important-config.conf # view current attributes
The immutable attribute is particularly useful for hardening critical configuration files against tampering — even a fully compromised root account cannot simply overwrite an immutable file without first explicitly clearing the attribute (chattr -i), which adds a meaningful speed bump against certain classes of attack or accidental damage, though it’s not an absolute guarantee against a sufficiently privileged and determined attacker who understands the mechanism.
Permissions in Containerized Environments
Container platforms like Docker introduce an additional wrinkle to the traditional permission model worth understanding, since containers share the host’s kernel rather than running a fully separate OS instance. A process running as UID 0 (root) inside a container is, by default, the same root as UID 0 on the underlying host from the kernel’s perspective — a fact that has real security implications if a container is compromised, since a container-escape vulnerability could translate directly into host-level root access. This is why user namespaces (remapping container UIDs to unprivileged host UIDs) and running containers as non-root users internally (USER directive in a Dockerfile) are widely recommended container security practices — they ensure that even “root” inside a container corresponds to an unprivileged, low-value account on the actual host system, limiting the damage a container escape can do.
Best Practices
- Apply the principle of least privilege: grant the minimum permission bits necessary, and prefer group-based access over widening “other” permissions.
- Avoid
chmod 777as a quick fix — it grants full read/write/execute to literally everyone on the system and is almost always the wrong long-term answer, even when it makes an immediate error disappear. - Use SGID on shared team directories so collaborative files consistently inherit the right group.
- Regularly audit SUID/SGID binaries, especially on internet-facing servers.
- Prefer ACLs over loosening group/other permissions when you need to grant access to just one additional user or group.
- Combine file permissions with other layers — SELinux or AppArmor mandatory access control, firewall rules, and process sandboxing — rather than treating rwx bits as a complete security boundary on their own.
Summary
Linux file permissions are built on a straightforward but powerful owner/group/other model, each with independent read/write/execute bits, stored directly in a file’s inode. Special bits — SUID, SGID, and the sticky bit — extend this model to handle privileged execution and shared-directory scenarios, while POSIX ACLs fill in the gaps when the basic three-category model isn’t granular enough. Directories interpret these bits distinctly from regular files, which explains behaviors (like being able to delete a “read-only” file) that often surprise newcomers. Understanding this system is foundational not just for day-to-day Linux administration, but for reasoning about security on any UNIX-derived platform, including macOS and, at the kernel level, Android.
FAQs
What does chmod 644 mean in practice? Owner gets read+write (6 = 4+2), group gets read-only (4), and other gets read-only (4) — the standard permission set for most regular data or configuration files.
Why can I delete a file I don’t own? Because deletion is controlled by write permission on the containing directory, not the file’s own permission bits — if you own or have write access to the directory, you can typically remove files inside it.
What’s the difference between chmod and chown? chmod changes what actions (read/write/execute) are allowed; chown changes who owns the file (user and/or group), which is a completely separate axis from what permissions are granted.
Is SUID dangerous? It can be, if the SUID binary has an exploitable bug, since the bug would then execute with the file owner’s (often root’s) privileges. It’s not inherently dangerous, but it’s a common target in privilege escalation research and should be minimized and audited.
Do symbolic links have their own permissions? Symlinks themselves typically show rwxrwxrwx (Linux effectively ignores their own permission bits), but access is actually governed by the permissions of the target file they point to.
References
- Linux man pages —
chmod(1),chown(1),stat(2),acl(5) - The Linux Documentation Project — File System Permissions
- Red Hat Enterprise Linux Documentation — Access Control Lists
- POSIX.1e Draft Standard — Access Control List Specification
