Ubuntu & Linux

Linux File Permissions Explained: chmod, chown, and Beyond

Master Linux file permissions — understand rwx notation, numeric modes, ownership, special permissions, and common permission patterns for web servers.

March 17, 20263 min read

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 directory
  • w (write) = can modify file / create files in directory
  • x (execute) = can run file / can enter directory
  • - = permission not granted

Numeric (Octal) Permissions

Each permission has a numeric value:

  • r = 4
  • w = 2
  • x = 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 chown when the wrong user owns the file
  • Use chmod when 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.

linuxpermissionschmodchownsecurityubuntufile system

Related Articles