Skip to content
beginner Phase 2 · Linux Fundamentals

Linux Permissions

Understand Unix file permissions, ownership, chmod, chown, and special permission bits like SUID and SGID.

50m
0 problems
Topic Progress 0%

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 file
  • rw- = user: read + write (no execute)
  • r-- = group: read only
  • r-- = other: read only

Breakdown of drwxr-xr-x:

  • d = directory
  • rwx = user: full access
  • r-x = group: read + enter
  • r-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 777 on production files — find out which permissions are actually needed
  • Use chmod 750 not chmod 777 for directories
  • Use chmod 640 not chmod 666 for config files
  • Always check with namei -l to trace path permissions
  • Test cron jobs — wrong permissions cause silent failures