Reading Permission Strings
Run ls -la and you'll see something like:
-rw-r--r-- 1 deploy deploy 4096 Mar 15 10:30 config.json
drwxr-xr-x 3 deploy deploy 4096 Mar 15 10:30 uploads/
-rwxr-xr-x 1 deploy deploy 8192 Mar 15 10:30 server.py
The first column breaks down as:
- rwx r-x r-x
│ │ │ │
│ │ │ └── Others (everyone else)
│ │ └─────── Group
│ └──────────── Owner
└─────────────── Type (- = file, d = directory, l = symlink)
r(read) = can view file contents / list directoryw(write) = can modify file / create files in directoryx(execute) = can run file / can enter directory-= permission not granted
Numeric (Octal) Permissions
Each permission has a numeric value:
r= 4w= 2x= 1
Add them up for each group (owner, group, others):
| Numeric | Permission | Meaning |
|---------|-----------|─────────|
| 755 | rwxr-xr-x | Owner: full, Others: read+execute |
| 644 | rw-r--r-- | Owner: read+write, Others: read only |
| 700 | rwx------ | Owner: full, Others: nothing |
| 600 | rw------- | Owner: read+write, Others: nothing |
| 777 | rwxrwxrwx | Everyone: full (DANGEROUS) |
Common patterns:chmod 755 script.sh # Executable script
chmod 644 config.json # Config file (readable by all)
chmod 600 .env # Secret file (owner only)
chmod 700 .ssh/ # SSH directory
chmod 755 /var/www/html # Web root directory
Changing Ownership with chown
# Change owner
chown deploy file.txt
# Change owner and group
chown deploy:www-data file.txt
# Recursive (entire directory)
chown -R deploy:deploy /home/deploy/myapp/
# Change group only
chgrp www-data /var/www/html
When to use chown vs chmod:
- Use
chownwhen the wrong user owns the file - Use
chmodwhen the right user owns it but has wrong permissions
Common scenario: you clone a repo as root, then need your app user to access it:
sudo chown -R deploy:deploy /home/deploy/myapp/
Web Server Permission Patterns
For a typical web application deployed with Nginx + a backend:
# Application code — owner can read/write, others read only
chmod -R 644 /home/deploy/myapp/
find /home/deploy/myapp/ -type d -exec chmod 755 {} \;
# Executable files
chmod 755 /home/deploy/myapp/start.sh
# Secret files — owner only
chmod 600 /home/deploy/myapp/.env
chmod 600 /home/deploy/myapp/secrets.json
# Upload directory — app needs to write
chmod 755 /home/deploy/myapp/uploads/
# Logs directory
chmod 755 /var/log/myapp/
# SSL certificates
chmod 600 /etc/letsencrypt/live/domain/privkey.pem
Rule of thumb: Give the minimum permissions needed. Start restrictive (600/700) and loosen only when something doesn't work.Debugging Permission Issues
When something fails with "Permission denied":
# 1. Check who owns the file
ls -la /path/to/file
# 2. Check what user your app runs as
ps aux | grep myapp
# or check the systemd service:
grep User /etc/systemd/system/myapp.service
# 3. Check the entire path (every directory needs execute permission)
namei -l /full/path/to/file
# 4. Common fix: wrong ownership after git pull as root
sudo chown -R deploy:deploy /home/deploy/myapp/
# 5. Common fix: script not executable
chmod +x script.sh
The namei command is the most useful debugging tool — it shows permissions for every directory in the path, making it easy to spot which directory is blocking access.