Skip to main content

Permissions and Ownership

Linux has a fine-grained permission system that controls who can read, write, and execute every file and directory on the system. Understanding permissions is not optional - misconfigured permissions cause security vulnerabilities, service failures, and are a frequent source of "Permission denied" errors in production.


The Permission Model​

Every file and directory in Linux has:

  • An owner (a user)
  • An owning group (a group)
  • A set of permission bits that define what the owner, the group, and everyone else can do

Viewing Permissions​

ls -la /opt/myapp/
# -rwxr-x--- 1 deploy www-data 8192 Apr 10 10:00 server
# drwxr-xr-x 3 deploy www-data 4096 Apr 10 10:00 config
# -rw-r--r-- 1 deploy www-data 512 Apr 10 10:00 README.md

The first column of ls -la is 10 characters long:

- r w x r - x - - -
│ │ │ │ │ │ │ │ │ │
│ ╰─────╯ ╰─────╯ ╰─────╯
│ owner group others
│
╰─ file type: - (file), d (directory), l (symlink), c (char device), b (block device)

Permission Types​

SymbolOn a FileOn a Directory
r (read)Can read file contentsCan list directory contents (ls)
w (write)Can modify file contentsCan create/delete files inside
x (execute)Can run as a programCan enter the directory (cd)
-Permission deniedPermission denied

A common confusion: you need execute permission on a directory to cd into it or access files inside, even if you have read permission.

Who the Permissions Apply To​

CategoryMeaning
Owner (user)The user who owns the file
GroupUsers who are members of the owning group
OthersEveryone else

Octal Notation​

Each permission triplet (rwx) can be expressed as a 3-bit binary number, then converted to octal (0–7).

BinaryOctalPermissions
0000---
0011--x
0102-w-
0113-wx
1004r--
1015r-x
1106rw-
1117rwx

A full permission set is three octal digits:

OctalSymbolicMeaning
755rwxr-xr-xOwner: full. Group + others: read + execute. Common for executables.
644rw-r--r--Owner: read + write. Group + others: read only. Common for config files.
600rw-------Owner only. Common for private keys (~/.ssh/id_ed25519).
700rwx------Owner only, full. Common for private directories.
777rwxrwxrwxEveryone full access. Almost always wrong on a server.
000---------No permissions for anyone.

Changing Permissions - chmod​

chmod (change mode) modifies the permission bits.

Symbolic Mode​

# Add execute for owner
chmod u+x script.sh

# Remove write for group and others
chmod go-w important.conf

# Set read+write for owner, read-only for everyone else
chmod u=rw,go=r config.txt

# Add execute for everyone
chmod a+x script.sh

# Remove all permissions for others
chmod o-rwx private.txt
SymbolMeaning
uUser (owner)
gGroup
oOthers
aAll (u+g+o)
+Add permission
-Remove permission
=Set exactly

Octal Mode​

# Set to rwxr-xr-x (755) - standard for executables/directories
chmod 755 script.sh
chmod 755 /opt/myapp/

# Set to rw-r--r-- (644) - standard for config/data files
chmod 644 app.conf

# Set to rw------- (600) - private files
chmod 600 ~/.ssh/id_ed25519

# Recursive: keep directories traversable and existing executables executable
chmod -R u=rwX,go=rX /opt/myapp/

# Recursive with different permissions for files vs directories
# Set dirs to 755, files to 644
find /opt/myapp -type d -exec chmod 755 {} +
find /opt/myapp -type f -exec chmod 644 {} +
find /opt/myapp/bin -type f -exec chmod 755 {} +

Changing Ownership - chown and chgrp​

# Change owner
chown ubuntu file.txt

# Change owner and group
chown ubuntu:www-data file.txt

# Change only group (with chown)
chown :www-data file.txt

# Change only group (with chgrp)
chgrp www-data file.txt

# Recursive
chown -R deploy:deploy /opt/myapp/

# Change owner following symlinks (default behaviour)
# Use -h to change the symlink itself, not the target
chown -h ubuntu symlink.txt

Common Deployment Pattern​

When deploying a web application:

# App files owned by the deploy user
chown -R deploy:deploy /opt/myapp/

# Web server needs to read static files
chown -R deploy:www-data /opt/myapp/public/
find /opt/myapp -type d -exec chmod 750 {} +
find /opt/myapp -type f -exec chmod 640 {} +
find /opt/myapp/public -type d -exec chmod 755 {} +
find /opt/myapp/public -type f -exec chmod 644 {} +

# Logs writable by app, readable by ops
chown -R deploy:ops /var/log/myapp/
find /var/log/myapp -type d -exec chmod 770 {} +
find /var/log/myapp -type f -exec chmod 660 {} +

Default Permissions - umask​

When you create a new file or directory, Linux assigns default permissions. The umask (user file-creation mask) controls this by specifying which permission bits to remove from the defaults.

Base defaults before umask:

  • Files: 666 (rw-rw-rw-)
  • Directories: 777 (rwxrwxrwx)

The umask value is subtracted:

# View current umask
umask
# 0022

# With umask 022:
# Files: 666 - 022 = 644 (rw-r--r--)
# Directories: 777 - 022 = 755 (rwxr-xr-x)
umaskNew filesNew directories
022644755
027640750
077600700
# Set a stricter umask for a session
umask 027

# Set permanently in ~/.bashrc or ~/.zshrc
echo "umask 027" >> ~/.bashrc

For sensitive applications (private key storage, secrets directories), use umask 077 to ensure new files are accessible only to the owner.


Special Permission Bits​

Beyond the standard rwx bits, Linux has three special bits.

Setuid (SUID) - 4000​

When set on an executable, it runs with the owner's permissions regardless of who executes it.

# Classic example: /usr/bin/passwd
ls -la /usr/bin/passwd
# -rwsr-xr-x 1 root root 59976 ... /usr/bin/passwd

# The 's' in owner's execute position means SUID is set
# Regular users can run passwd, and it runs as root to modify /etc/shadow

# Set SUID (numeric)
chmod 4755 executable

# Set SUID (symbolic)
chmod u+s executable

SUID on scripts is generally ignored by the kernel. It only works reliably on compiled binaries.

Setgid (SGID) - 2000​

When set on an executable, it runs with the group's permissions. When set on a directory, new files created inside inherit the directory's group instead of the creator's primary group.

# Set SGID on a directory (useful for shared project directories)
chmod 2775 /opt/shared-project/
# Files created in here will be owned by the shared-project group

# Set SGID (numeric)
chmod 2755 executable

# Set SGID (symbolic)
chmod g+s directory/

Sticky Bit - 1000​

When set on a directory, only the file's owner, the directory's owner, or root can delete files inside it - even if other users have write access to the directory.

# /tmp always has sticky bit set
ls -ld /tmp
# drwxrwxrwt 20 root root 4096 Apr 10 /tmp
# The 't' at the end indicates sticky bit

# Set sticky bit
chmod 1777 /opt/shared-uploads/
chmod +t /opt/shared-uploads/

Privilege Escalation - sudo​

sudo (superuser do) allows a permitted user to run commands as root (or another user) without logging in as root.

# Run a command as root
sudo apt update

# Edit a root-owned file
sudo nano /etc/nginx/nginx.conf

# Run as a specific user
sudo -u postgres psql

# Open an interactive root shell
sudo -i

# Check which sudo commands you are allowed to run
sudo -l

/etc/sudoers Basics​

The sudoers file controls who can use sudo and what they can do. Always edit it with visudo - it validates syntax before saving, preventing you from locking yourself out.

sudo visudo

Sudoers syntax:

# Format: user host=(runas) command

# Allow ubuntu to run anything as root
ubuntu ALL=(ALL) ALL

# Allow ubuntu without a password prompt
ubuntu ALL=(ALL) NOPASSWD: ALL

# Allow a group to run specific commands
%ops ALL=(ALL) /bin/systemctl restart nginx, /bin/systemctl reload nginx

# Allow deploy user to run specific scripts
deploy ALL=(ALL) NOPASSWD: /opt/scripts/deploy.sh, /opt/scripts/rollback.sh

Drop-In Sudoers Files​

Rather than editing the main file, add files to /etc/sudoers.d/:

# Create a file for the deploy user
sudo visudo -f /etc/sudoers.d/deploy
deploy ALL=(ALL) NOPASSWD: /bin/systemctl restart myapp, /bin/systemctl status myapp

su - Switch User​

# Switch to root (requires root password)
su -

# Switch to another user
su - ubuntu

# The hyphen (-) starts a login shell (loads the user's environment)
# Without it, you switch user but keep the current environment

Practical Security Checklist​

After setting up a new server or deploying an application, verify these:

# SSH private keys: owner-only read
ls -la ~/.ssh/
# ~/.ssh should be 700; id_ed25519 and authorized_keys should be 600

# Config files: not world-writable
find /etc/myapp -type f -perm /002
# Should return nothing

# Find world-writable files (security risk)
find /opt/myapp -type f -perm /002

# Find SUID/SGID files (audit regularly)
find / -type f \( -perm /4000 -o -perm /2000 \) 2>/dev/null

# Application log directory: app writes, ops can read
ls -ld /var/log/myapp/
# drwxrwxr-x deploy ops - or drwxr-x--- deploy adm

# Web root: web server reads, not writes
ls -ld /var/www/html/
# drwxr-xr-x root root - nginx/apache serves as its own user