Understanding Linux Permissions
Understanding Linux Permissions
Every file and directory in Linux has an owner, a group, and a set of permissions that control read, write, and execute access.
Permission Classes
| Class | Who | Applies To |
|---|---|---|
| User (u) | File owner | The user who created or owns the file |
| Group (g) | Members of the file's group | All users in the assigned group |
| Other (o) | Everyone else | Any user not in owner or group |
Permission Bits
| Symbol | Octal | Meaning (File) | Meaning (Directory) |
|---|---|---|---|
| r | 4 | Read content | List contents (ls) |
| w | 2 | Modify content | Create/delete files inside |
| x | 1 | Execute (run) | Enter directory (cd) |
Reading ls -l Output
$ ls -la /var/www/html/
-rw-r--r-- 1 www-data www-data 4096 Mar 10 14:23 index.html
drwxr-xr-x 2 www-data www-data 4096 Mar 10 14:23 css/
-rwxr-xr-x 1 root root 1024 Mar 10 14:23 deploy.sh
Breakdown of -rw-r--r--:
-= regular filerw-= user: read + write (no execute)r--= group: read onlyr--= other: read only
Breakdown of drwxr-xr-x:
d= directoryrwx= user: full accessr-x= group: read + enterr-x= other: read + enter
Numeric (Octal) Representation
Permissions are expressed as three octal digits:
chmod 755 script.sh # rwxr-xr-x (owner: rwx, group: r-x, other: r-x)
chmod 644 config.txt # rw-r--r-- (owner: rw-, group: r--, other: r--)
chmod 700 private/ # rwx------ (owner: rwx, group: ---, other: ---)
chmod 600 secret.key # rw------- (owner: rw-, group: ---, other: ---)
The math: r=4, w=2, x=1. Add them: 7 = 4+2+1 = rwx, 5 = 4+0+1 = r-x, 4 = 4+0+0 = r--.
Changing Permissions with chmod
Changing Permissions with chmod
Symbolic Mode
# Add execute permission for owner
chmod u+x script.sh
# Remove write for group and other
chmod go-w file.txt
# Set permissions for everyone at once
chmod a+r file.txt # Add read for all
chmod u=rwx,g=rx,o=r file # Explicit for each class
# Copy permissions from another file
chmod --reference=template.txt newfile.txt
Numeric Mode
# Common permission sets
chmod 755 /usr/local/bin/myapp # Executables
chmod 644 /etc/nginx/nginx.conf # Config files
chmod 600 ~/.ssh/id_rsa # SSH private keys
chmod 644 ~/.ssh/authorized_keys # SSH public keys
chmod 700 ~/.ssh # SSH directory
chmod 750 /var/log/myapp # Log directories
chmod 640 /var/log/myapp/audit.log # Log files
# Recursive permission change
chmod -R 755 /var/www/html/
chmod -R u=rwX,g=rX,o=rX /var/www/html/ # Capital X: execute only for dirs
The Capital X Explained
# Capital X adds execute ONLY for directories (not files)
chmod -R u=rwX,g=rX,o=rX /var/www/
# This makes:
# - Directories: rwxr-xr-x (755)
# - Files: rw-r--r-- (644)
# Perfect for web directories!
Practical Examples
# Web server directory setup
sudo chown -R www-data:www-data /var/www/html
sudo find /var/www/html -type d -exec chmod 750 {} ;
sudo find /var/www/html -type f -exec chmod 640 {} ;
# Secure a backup script
chmod 700 /usr/local/bin/backup.sh
chown root:root /usr/local/bin/backup.sh
# Lock down a shared directory
chmod 1777 /tmp/shared # Sticky bit (explained later)
# Make a script executable without changing other permissions
chmod +x deploy.sh # Preserves existing permissions, adds x
Ownership and chown
Ownership and chown
Changing Ownership
# Change owner
sudo chown ubuntu /var/www/html
# Change owner and group
sudo chown ubuntu:ubuntu /var/www/html
# Change group only
sudo chgrp developers /var/www/html
# Recursive ownership change
sudo chown -R www-data:www-data /var/www/html
# Change group to match parent directory
sudo chgrp -R :www-data /var/www/html
# Preserve ownership while copying
cp -a --preserve=ownership source/ dest/
Viewing Ownership
# Detailed view
ls -la /var/www/
# drwxr-xr-x 3 www-data www-data 4096 Mar 10 14:23 html/
# Show group membership
groups www-data
# www-data : www-data
# List all users in a group
getent group www-data
Practical Ownership Scenarios
# Deploy a web application
sudo chown -R www-data:www-data /var/www/myapp
sudo find /var/www/myapp -type d -exec chmod 750 {} ;
sudo find /var/www/myapp -type f -exec chmod 640 {} ;
# Application running as systemd service
sudo useradd -r -s /bin/false myapp
sudo chown -R myapp:myapp /opt/myapp
sudo chown -R myapp:myapp /var/log/myapp
sudo chown -R myapp:myapp /var/lib/myapp
# Shared development directory
groupadd developers
sudo usermod -aG developers ubuntu
sudo usermod -aG developers john
sudo chown -R root:developers /opt/shared-project
sudo chmod -R 2775 /opt/shared-project # SGID + rwxrwsr-x
Finding Files by Ownership
# Find files owned by a specific user
find / -user ubuntu -type f 2>/dev/null | head -20
# Find files owned by a specific group
find /var -group www-data -type f 2>/dev/null
# Find files with no owner (orphaned)
find / -nouser -o -nogroup 2>/dev/null
# Find files by specific UID
find / -uid 1001 -type f 2>/dev/null | head -10
Special Permissions: SUID, SGID, Sticky Bit
Special Permissions: SUID, SGID, Sticky Bit
SUID (Set User ID)
When set on an executable, it runs with the file owner's privileges, not the invoking user's.
# Classic example: /usr/bin/passwd
ls -la /usr/bin/passwd
# -rwsr-xr-x 1 root root 68208 Mar 14 2024 /usr/bin/passwd
# ^-- 's' in the user execute position
# The 's' means any user can run passwd with root privileges
# (only to change their own password, passwd enforces this)
# Set SUID
chmod u+s /usr/local/bin/myapp
chmod 4755 /usr/local/bin/myapp # 4 = SUID
# Remove SUID
chmod u-s /usr/local/bin/myapp
SGID (Set Group ID)
On executables: runs with the file group's privileges.
On directories: new files inherit the directory's group.
# Directory with SGID
chmod g+s /shared/dev/
chmod 2775 /shared/dev/
# drwxrwsr-x 2 root developers 4096 Mar 10 14:23 /shared/dev/
# ^-- 's' in group execute position
# Now any file created inside /shared/dev/ gets 'developers' group
sudo -u ubuntu touch /shared/dev/newfile.txt
ls -la /shared/dev/newfile.txt
# -rw-r--r-- 1 ubuntu developers 0 Mar 10 14:24 newfile.txt
# ^^^^^^^^^^^ inherits 'developers'
# Remove SGID
chmod g-s /shared/dev/
Sticky Bit
On directories: only the file owner (or root) can delete files inside.
# /tmp has sticky bit set
ls -ld /tmp
# drwxrwxrwt 15 root root 4096 Mar 10 14:23 /tmp
# ^-- 't' in the other execute position
# Set sticky bitchmod +t /shared/upload/
chmod 1777 /shared/upload/
# Users can create files but can't delete each other's files
# Only root or the file owner can delete
# Remove sticky bit
chmod -t /shared/upload/
Security Audit Commands
# Find all SUID binaries
find / -perm -4000 -type f -ls 2>/dev/null
# Find all SGID binaries
find / -perm -2000 -type f -ls 2>/dev/null
# Find all files with sticky bit
find / -perm -1000 -type d -ls 2>/dev/null
# Remove unnecessary SUID from a binary
chmod u-s /usr/local/bin/unnecessary-suid-tool
Access Control Lists (ACLs)
Access Control Lists (ACLs)
ACLs provide fine-grained permissions beyond the traditional owner/group/other model. Useful when you need to grant access to multiple specific users.
Viewing ACLs
# View ACL on a file
getfacl /var/www/html/index.html
# # file: var/www/html/index.html
# # owner: www-data
# # group: www-data
# user::rw-
# group::r--
# other::r--
# View with ls (ACL indicator: '+')
ls -la /var/www/html/index.html
# -rw-r--r-- 1 www-data www-data 4096 Mar 10 14:23 index.html
# ^-- no ACL
# When ACL is present:
ls -la file.txt
# -rw-r--r-- 2 root staff 4096 Mar 10 14:23 file.txt
# ^-- '+' means ACL
Setting ACLs
# Grant read access to a specific user
setfacl -m u:ubuntu:r /var/www/html/config.json
# Grant read-write to a specific group
setfacl -m g:developers:rw /shared/project/
# Grant full access to a specific user
setfacl -m u:deploy:rwX /var/www/
# Set default ACL (inherited by new files in directory)
setfacl -d -m g:developers:rwx /shared/project/
# Recursive ACL set
setfacl -R -m g:developers:rwX /shared/project/
# Remove a specific ACL entry
setfacl -x u:ubuntu /var/www/html/config.json
# Remove all ACLs
setfacl -b /var/www/html/config.json
Practical Example: Multi-User Web Project
# Setup: ubuntu deploys, john develops, ci-runner tests
sudo mkdir -p /var/www/myapp
sudo chown root:root /var/www/myapp
# Ubuntu can write (deploy)
sudo setfacl -m u:ubuntu:rwX /var/www/myapp
# Developers can read/write
sudo setfacl -m g:developers:rwX /var/www/myapp
# CI runner can read
sudo setfacl -m u:ci-runner:rX /var/www/myapp
# Default ACL: new files inherit developer permissions
sudo setfacl -d -m g:developers:rwX /var/www/myapp
# Verify
getfacl /var/www/myapp
Backup and Restore ACLs
# Backup ACLs
getfacl -R /var/www/myapp > /backup/acl_backup.txt
# Restore ACLs
setfacl --restore=/backup/acl_backup.txt
Production Permission Patterns
Production Permission Patterns
Web Server Permissions
# Nginx/Apache web directory
sudo useradd -r -s /bin/false www-data
sudo chown -R www-data:www-data /var/www/html
# Directories: 750 (owner can enter and list, group can enter and list)
sudo find /var/www/html -type d -exec chmod 750 {} ;
# Files: 640 (owner can read/write, group can read)
sudo find /var/www/html -type f -exec chmod 640 {} ;
# Upload directory: writable by web server
sudo mkdir -p /var/www/html/uploads
sudo chown www-data:www-data /var/www/html/uploads
sudo chmod 770 /var/www/html/uploads
# Uploads subdirectories: sticky bit for multi-process writes
sudo find /var/www/html/uploads -type d -exec chmod 1770 {} ;
SSH Key Permissions
# SSH directory (must be 700 or stricter)
chmod 700 ~/.ssh
# Private keys (must be 600)
chmod 600 ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_ed25519
# Public keys (644 is fine)
chmod 644 ~/.ssh/authorized_keys
chmod 644 ~/.ssh/*.pub
# Config file
chmod 600 ~/.ssh/config
# SSH will REFUSE to use keys with wrong permissions:
# @WARNING: UNPROTECTED PRIVATE KEY FILE!
Cron Job Permissions
# Cron files should be owned by root and not world-writable
chmod 600 /var/spool/cron/crontabs/root
chmod 600 /etc/cron.d/myjob
chmod 700 /etc/cron.daily/
chmod 700 /etc/cron.hourly/
# Cron scripts should not be writable by others
chmod 755 /usr/local/bin/cron-script.sh
Database File Permissions
# PostgreSQL data directory
sudo chown -R postgres:postgres /var/lib/postgresql/
sudo chmod 700 /var/lib/postgresql/data/
# MySQL data directory
sudo chown -R mysql:mysql /var/lib/mysql/
sudo chmod 700 /var/lib/mysql/
# Database config files
sudo chmod 600 /etc/mysql/my.cnf
sudo chmod 600 /etc/postgresql/*/main/pg_hba.conf
Security Audit Script
#!/bin/bash
echo "=== World-writable files ==="
find / -path /proc -prune -o -path /sys -prune -o -perm -0002 -type f -print 2>/dev/null
echo "=== SUID binaries ==="
find / -perm -4000 -type f -ls 2>/dev/null
echo "=== Files with no owner ==="
find / -path /proc -prune -o -path /sys -prune -o -nouser -print 2>/dev/null
echo "=== Large files in /tmp ==="
find /tmp -size +10M -type f -ls 2>/dev/null
Common Mistakes
- Never
chmod 777on production files — find out which permissions are actually needed - Use
chmod 750notchmod 777for directories - Use
chmod 640notchmod 666for config files - Always check with
namei -lto trace path permissions - Test cron jobs — wrong permissions cause silent failures