Linux file permissions tend to catch your eye in the worst way: a web host says “set it to 755,” you change the number, the error goes away, and you still don’t know why. The system has a consistent logic based on owner, group, and other, and chmod is just how you change those bits.
Reading a Permission String
Run ls -l in any terminal and you’ll see a column of strings on the left:
-rw-r--r-- 1 alice www-data 2048 Sep 1 14:22 index.html
drwxr-xr-x 2 alice www-data 4096 Sep 1 14:20 assets
The first character tells you the file type:
-: regular filed: directoryl: symbolic link
After that, nine characters describe permissions for three classes in order: user (the file’s owner), group (other users in the file’s assigned group), and other (everyone else). Each class gets three characters: r (read), w (write), x (execute), with a dash where a permission isn’t set.
So -rw-r--r-- reads as: regular file, owner has read and write, group has read only, everyone else has read only.
What each permission bit actually does
r, w, and x mean slightly different things depending on whether you’re looking at a file or a directory:
Files:
r: read the contentsw: modify the contentsx: execute it as a program or script
Directories:
r: list the entries insidew: create or delete entries insidex: enter or traverse the directory
That x bit on directories is where things get confusing. A directory without execute permission can’t be entered, not even by the owner if x is absent for that class. This is exactly why web servers throw errors when the document root or a subdirectory is missing execute permission: the server process can’t traverse into it to serve files, even if the files themselves look readable.
Octal Notation: What the Numbers Mean
Each permission maps to a number:
| Symbol | Name | Value |
|---|---|---|
r | read | 4 |
w | write | 2 |
x | execute | 1 |
- | none | 0 |
Add the values for each class to get one digit. Three classes, three digits. That’s the octal mode.
| Octal digit | Symbolic | Permissions |
|---|---|---|
7 | rwx | read, write, execute |
6 | rw- | read, write |
5 | r-x | read, execute |
4 | r-- | read only |
0 | --- | none |
So 755 breaks down as: owner=rwx (7), group=r-x (5), other=r-x (5). The owner can do everything; group and other can read and traverse but can’t write. That’s why 755 is the standard for web directories because the web server process needs execute permission to enter subdirectories at all.
Three modes worth memorizing:
755:rwxr-xr-x, directories and executable scripts644:rw-r--r--, static files: HTML, CSS, images600:rw-------, sensitive files like SSH private keys (if group or other can read your private key, SSH refuses to use it)
Using chmod: Symbolic Mode
Symbolic mode lets you add or remove specific bits without resetting everything else. The syntax:
chmod [class][operator][permission] file
Classes: u (user/owner), g (group), o (other), a (all three)
Operators: + add, - remove, = set exactly
Permissions: r, w, x
Some practical examples:
chmod u+x deploy.sh # add execute for the owner
chmod go-rw secrets.txt # remove read/write from group and other
chmod a+r styles.css # make the file readable by everyone
chmod u=rw,go=r index.html # set exactly: owner rw, group and other r only
Symbolic mode is the right choice when you want one targeted change. Use = when you want to set bits exactly and clear whatever was there before; use + or - when you want to adjust without touching the rest.
Using chmod: Octal Mode
Octal sets all three classes at once. It’s faster when you already know the exact mode you want:
chmod 755 /var/www/my-site # directory: owner full, group/other read+traverse
chmod 644 index.html # static file: owner rw, others r
chmod 600 ~/.ssh/id_rsa # SSH key: owner rw only, nobody else
chmod 640 config.php # app config: owner rw, group r, others nothing
Octal is what you’ll see in most web server and DevOps documentation because it’s compact and unambiguous. Symbolic is better for interactive edits when you don’t want to recalculate the full mode from scratch.
Recursive chmod
The -R flag applies changes to a directory and everything inside it:
chmod -R 755 /var/www/my-site
Keep the scope tight. Never run recursive chmod on /, /etc, /usr, or any system directory. The consequences are subtle and annoying to reverse.
There’s also a common problem with using the same octal value on a mixed directory: chmod -R 755 gives regular files execute permission they don’t need. Separate the two with find:
find /var/www/my-site -type d -exec chmod 755 {} \;
find /var/www/my-site -type f -exec chmod 644 {} \;
This sets directories to 755 and files to 644 in one pass, without over-permissioning anything.
Default Permissions: What umask Does
New files don’t get arbitrary permissions. The system shapes them with umask. Check yours:
umask
The umask value is subtracted from the system maximum (666 for files, 777 for directories). A common user umask of 022 means new files get 644 and new directories get 755. A stricter server umask of 027 gives new files 640 and directories 750.
If files on a shared web hosting plan keep showing up with permissions you didn’t set, umask is usually the explanation, not a bug in your upload process. Set a stricter default for the current session:
umask 027
Add that line to ~/.bashrc or ~/.profile to make it permanent.
Beyond Basic Permissions: ACLs
The user/group/other model has a hard limit: one owner, one group, and “everyone else.” If you need two different teams to have different access to the same directory, POSIX ACLs handle it cleanly without reshuffling your Unix group structure.
getfacl file.txt # view the current ACL
setfacl -m u:alice:rwx file.txt # give alice read, write, execute
setfacl -m g:developers:rw project/ # give the developers group read/write
setfacl -x u:alice file.txt # remove alice's ACL entry
ACLs extend the basic model. They don’t replace it. They’re the right answer for shared directories on a network-attached storage drive, or any environment where forcing everyone into a single Unix group is the wrong structural fix.
When chmod Alone Doesn’t Fix It
Permissions look correct but access is still denied? These are the usual suspects:
Wrong ownership. Web servers run as www-data, apache, or nginx, not your personal account. chmod alone won’t help if the server process isn’t the owner or isn’t in the file’s group. Fix it with:
chown -R www-data:www-data /var/www/my-site
Verify ownership with ls -l before reaching for chmod.
SELinux or AppArmor. These mandatory access control frameworks enforce policies on top of standard permissions. A correctly permissioned file can still be blocked at this layer. Check /var/log/audit/audit.log for SELinux denials, or dmesg//var/log/syslog for AppArmor. On RHEL, Fedora, and CentOS, this comes up constantly.
Mounted filesystems. Files on a network-attached storage drive or USB drive can map UIDs and GIDs differently than the host expects. A file “owned” by UID 1000 on one machine may appear as a different user on another.
WSL (Windows Subsystem for Linux). On NTFS-backed paths inside WSL, chmod changes may not persist or may not map cleanly to the underlying Windows ACLs.
Avoid chmod 777
chmod 777 makes a file or directory readable, writable, and executable by every user and process on the system. On a public-facing web server, that means any compromised process can modify or inject content.
When someone suggests 777 to fix a web server error, the actual problem is almost always wrong ownership (chown) or a missing group membership. Fix those. They’re the real fix, not a workaround.
Conclusion
For web server permission problems, chmod 755 on directories and chmod 644 on files, paired with correct ownership from chown, clears things up most of the time. The symbolic form is a genuine gem for surgical edits: chmod u+x, chmod go-r, and chmod u=rw,go=r are patterns worth bookmarking. If everything looks right and access is still blocked, check ownership first, then SELinux or AppArmor logs. chmod alone only covers part of what Linux permissions actually enforce.