File permissions are the foundation of Linux security. They control who can read, write, and execute every file and directory on your system. Misconfigured permissions are one of the most common causes of security vulnerabilities, "Permission denied" errors, and website malfunctions on Linux servers.
If you have ever wondered why your web server returns a 403 Forbidden error, why a script will not execute, or what those mysterious numbers like 755 and 644 mean, this guide will give you a thorough understanding of Linux file permissions with practical, real-world examples.
The Permission Model: Users, Groups, and Others
Every file and directory in Linux has three sets of permissions assigned to three categories of users:
The user who created the file
Users in the file's group
Everyone else on the system
Each category can have three types of permissions:
| Permission | Symbol | Numeric | For Files | For Directories |
|---|---|---|---|---|
| Read | r | 4 | View file contents | List directory contents (ls) |
| Write | w | 2 | Modify file contents | Create/delete files in directory |
| Execute | x | 1 | Run as a program | Enter the directory (cd) |
| None | - | 0 | No access | No access |
Reading Permission Output
When you run ls -la, the first column shows the permission string:
drwxr-xr-x 5 deploy deploy 4096 Mar 17 10:00 .
drwxr-xr-x 4 root root 4096 Mar 17 09:00 ..
-rw------- 1 deploy deploy 220 Mar 17 09:00 .bash_logout
-rw-r--r-- 1 deploy deploy 3771 Mar 17 09:00 .bashrc
drwx------ 2 deploy deploy 4096 Mar 17 10:00 .ssh
-rwxr-xr-x 1 deploy deploy 512 Mar 17 10:00 script.sh
-rw-rw---- 1 deploy www 1024 Mar 17 10:00 shared.txt
Let us break down the permission string -rwxr-xr-x:
| | | |
| | | +-- Others: read + execute (5)
| | +------- Group: read + execute (5)
| +------------ Owner: read + write + execute (7)
+--------------- Type: - = file, d = directory, l = link
Numeric: 755
Numeric (Octal) Notation
The numeric system is the most common way to set permissions. Each permission has a value (r=4, w=2, x=1), and you add them up for each category:
| Number | Permission | Binary | Meaning |
|---|---|---|---|
| 0 | --- | 000 | No permissions |
| 1 | --x | 001 | Execute only |
| 2 | -w- | 010 | Write only |
| 3 | -wx | 011 | Write + execute |
| 4 | r-- | 100 | Read only |
| 5 | r-x | 101 | Read + execute |
| 6 | rw- | 110 | Read + write |
| 7 | rwx | 111 | Full permissions |
chmod: Changing Permissions
The chmod command changes file permissions. You can use either numeric or symbolic notation:
Numeric Mode
$ chmod 644 index.html # rw-r--r-- (owner reads/writes, everyone reads)
# Set standard web directory permissions
$ chmod 755 public_html/ # rwxr-xr-x (owner full, everyone reads/enters)
# Set private file (SSH keys, config files)
$ chmod 600 .ssh/id_ed25519 # rw------- (owner only)
# Set executable script
$ chmod 755 deploy.sh # rwxr-xr-x (owner full, everyone executes)
# Set private directory
$ chmod 700 .ssh/ # rwx------ (owner only)
Symbolic Mode
$ chmod u+x script.sh
# Remove write permission from group and others
$ chmod go-w config.php
# Set exact permissions for all
$ chmod u=rwx,g=rx,o=rx directory/
# Add read permission for everyone
$ chmod a+r readme.txt
# Recursive: set all files in a directory
$ chmod -R 755 public_html/
chown: Changing Ownership
The chown command changes the owner and/or group of a file:
$ sudo chown deploy index.html
# Change owner and group
$ sudo chown deploy:www-data index.html
# Change only the group
$ sudo chgrp www-data uploads/
# Recursive: change ownership of entire directory tree
$ sudo chown -R deploy:www-data /home/deploy/public_html/
# Verify the change
$ ls -la index.html
-rw-r--r-- 1 deploy www-data 5120 Mar 17 10:00 index.html
Directory vs File Permissions
Permissions behave differently for files and directories. This distinction trips up many administrators:
| Permission | On a File | On a Directory |
|---|---|---|
| r (read) | Can view file contents | Can list files in the directory |
| w (write) | Can modify the file | Can create, rename, or delete files inside |
| x (execute) | Can run as a program | Can enter the directory (cd into it) |
A directory needs
x (execute) permission for anyone to enter it. Without x, even if you have r (read), you can list filenames but cannot access the files inside. Without r, you can enter the directory if you know the exact filename, but you cannot list its contents. This is why directories typically use 755 or 700.
Web Server Permission Patterns
Getting permissions right for web servers is critical. The web server process (Nginx/Apache) typically runs as a specific user (like www-data or a per-user PHP-FPM pool user), and it needs to read your files to serve them.
Standard Web Hosting Permissions
$ sudo chown -R deploy:www-data /home/deploy/public_html/
# Directories: owner full, group read+enter, others read+enter
$ find /home/deploy/public_html/ -type d -exec chmod 755 {} \;
# Files: owner read+write, group read, others read
$ find /home/deploy/public_html/ -type f -exec chmod 644 {} \;
# Upload directories: web server needs write access
$ chmod 775 /home/deploy/public_html/uploads/
# Config files: owner only (contains database passwords!)
$ chmod 600 /home/deploy/public_html/wp-config.php
$ chmod 600 /home/deploy/public_html/.env
Here is a complete reference for common web server file types:
| File Type | Permission | Reason |
|---|---|---|
| HTML, CSS, JS, images | 644 | Everyone needs to read, only owner edits |
| PHP files | 644 | PHP-FPM reads them, owner edits |
| Directories | 755 | Everyone needs to traverse, only owner creates files |
| Upload directories | 775 | Web server group needs to write uploaded files |
| Config files (.env, wp-config) | 600 | Contains passwords, owner only |
| SSH keys | 600 | SSH refuses to use keys with loose permissions |
| .ssh directory | 700 | SSH requires strict directory permissions |
| Cron scripts | 755 | Need execute permission to run |
| Log files | 640 | Owner writes, group (admin) reads, no others |
Special Permissions: SUID, SGID, Sticky Bit
Beyond the basic rwx permissions, Linux has three special permission bits that provide additional security and functionality:
SUID (Set User ID) — 4xxx
When a file with SUID is executed, it runs with the file owner's permissions, not the executor's. This is how regular users can change their passwords — the passwd command has SUID set to run as root.
-rwsr-xr-x 1 root root 68208 passwd
# Note the 's' instead of 'x' in owner execute
$ chmod 4755 special_program
SGID (Set Group ID) — 2xxx
On a file, SGID makes it run with the file's group permissions. On a directory, new files created inside inherit the directory's group, not the creator's default group.
$ chmod 2775 /shared/project/
# New files inherit the 'project' group
Sticky Bit — 1xxx
On a directory, the sticky bit prevents users from deleting files they do not own, even if they have write permission to the directory. The classic example is /tmp.
drwxrwxrwt 18 root root tmp
# Note the 't' — sticky bit is set
$ chmod 1777 /tmp
Finding Special Permissions
$ find / -perm -4000 -type f 2>/dev/null
/usr/bin/passwd
/usr/bin/sudo
/usr/bin/chfn
# Find all SGID files
$ find / -perm -2000 -type f 2>/dev/null
umask: Default Permission Control
The umask determines the default permissions for newly created files and directories. It works by subtracting permissions from the maximum:
$ umask
0022
# With umask 0022:
# New files: 666 - 022 = 644 (rw-r--r--)
# New directories: 777 - 022 = 755 (rwxr-xr-x)
$ touch newfile.txt
$ ls -la newfile.txt
-rw-r--r-- 1 deploy deploy 0 Mar 17 10:00 newfile.txt
$ mkdir newdir
$ ls -la | grep newdir
drwxr-xr-x 2 deploy deploy 4096 Mar 17 10:00 newdir
| umask | New File Default | New Dir Default | Use Case |
|---|---|---|---|
| 0022 | 644 (rw-r--r--) | 755 (rwxr-xr-x) | Standard default |
| 0027 | 640 (rw-r-----) | 750 (rwxr-x---) | More restrictive |
| 0077 | 600 (rw-------) | 700 (rwx------) | Private files |
| 0002 | 664 (rw-rw-r--) | 775 (rwxrwxr-x) | Group collaboration |
Troubleshooting Common Permission Problems
Problem 1: "Permission denied" for Web Files
$ namei -l /home/deploy/public_html/index.php
f: /home/deploy/public_html/index.php
drwxr-xr-x root root /
drwxr-xr-x root root home
drwx------ deploy deploy deploy <-- Problem! Others cannot enter
drwxr-xr-x deploy deploy public_html
-rw-r--r-- deploy deploy index.php
# Fix: allow others to enter the home directory
$ chmod 711 /home/deploy/ # rwx--x--x (enter but not list)
Problem 2: SSH Refuses Key Authentication
$ chmod 700 ~/.ssh
$ chmod 600 ~/.ssh/authorized_keys
$ chmod 600 ~/.ssh/id_ed25519 # Private key
$ chmod 644 ~/.ssh/id_ed25519.pub # Public key
$ chmod 644 ~/.ssh/known_hosts
# Home directory must not be writable by group or others
$ chmod 755 ~ # or 750, or 700
Problem 3: Cannot Upload Files via Web Application
$ ps aux | grep php-fpm
deploy 1234 ... php-fpm: pool deploy
# The upload directory needs write access for the web server
$ sudo chown -R deploy:www-data /home/deploy/public_html/uploads/
$ chmod 775 /home/deploy/public_html/uploads/
Permission Audit: Finding Security Issues
$ find /home/ -perm -0002 -type f 2>/dev/null
# Find world-writable directories (excluding tmp and proc)
$ find / -perm -0002 -type d ! -path "/proc/*" ! -path "/tmp/*" 2>/dev/null
# Find files with no owner (orphaned files)
$ find / -nouser -o -nogroup 2>/dev/null
# Find SUID/SGID executables (potential privilege escalation)
$ find / -perm /6000 -type f 2>/dev/null
# Find files readable by everyone containing potential secrets
$ find /home/ -name ".env" -perm -004 2>/dev/null
$ find /home/ -name "wp-config.php" -perm -004 2>/dev/null
Quick Reference: Permission Cheat Sheet
| Scenario | chmod | What It Means |
|---|---|---|
| Web file (HTML, CSS, JS, PHP) | 644 | Owner reads/writes, everyone reads |
| Web directory | 755 | Owner full, everyone reads/enters |
| Upload directory | 775 | Owner and group full, others read/enter |
| Config file (.env, wp-config) | 600 | Owner only, no one else |
| SSH private key | 600 | Owner only (SSH enforces this) |
| .ssh directory | 700 | Owner only (SSH enforces this) |
| Executable script | 755 | Owner full, everyone executes |
| Private home directory | 700 | Owner only |
| Shared home (web accessible) | 711 | Owner full, others can enter (not list) |
| Log file | 640 | Owner writes, group reads |
Panelica: Automatic Permission Management
Panelica enforces correct file permissions automatically through its 5-layer isolation architecture, eliminating the entire category of permission-related security issues:
- Dedicated UID/GID per user — each user gets a unique system user and group, preventing cross-user file access
- Home directories set to 700 — users cannot see or access each other's files by default
- UserContextService — all file operations go through a service that enforces correct ownership. Root writes a temporary file, then chowns it to the correct user, preventing privilege escalation
- Per-user PHP-FPM pools — each user's PHP processes run under their own UID, so even if a PHP application is compromised, it can only access that user's files
- open_basedir enforcement — PHP is restricted to the user's home directory, preventing directory traversal attacks at the PHP level
- Namespace isolation — users operate in isolated PID and mount namespaces, making other users' processes and files invisible at the kernel level
With Panelica, you never need to manually run
chmod or chown on web files. The panel creates directories with correct ownership, sets proper permissions on configuration files, and isolates users from each other through five independent security layers — from Unix permissions to kernel namespaces.