Linux File Permissions Explained — Who Can Do What?
Your deployment script fails at 2 AM with Permission denied. The container can't write to the log directory. A junior developer accidentally made a config file world-writable. Sound familiar? Understanding Linux file permissions is the difference between a secure, working system and a nightmare.
The Permission Model — rwx Decoded
Every file and directory in Linux has three sets of permissions for three types of users:
| Symbol | Permission | On Files | On Directories |
|---|---|---|---|
r (4) | Read | View contents | List contents |
w (2) | Write | Modify contents | Create/delete files inside |
x (1) | Execute | Run as program | Enter (cd into) directory |
# Let's see permissions in action
ls -la /opt/myapp/
# drwxr-xr-x 2 deploy apps 4096 Feb 25 10:00 .
# -rwxr-x--- 1 deploy apps 2048 Feb 25 10:00 deploy.sh
# -rw-r--r-- 1 deploy apps 512 Feb 25 10:00 config.yaml
# -rw------- 1 deploy apps 256 Feb 25 10:00 secrets.env
Reading this output: -rwxr-x--- breaks down as:
-= regular file (d = directory, l = symlink)rwx= owner can read, write, executer-x= group can read and execute---= others have zero access
chmod — Changing Permissions
Two ways to use chmod: numeric (octal) and symbolic. Both do the same thing.
# Numeric mode — most common in scripts
chmod 755 deploy.sh # rwxr-xr-x — executable script
chmod 644 config.yaml # rw-r--r-- — readable config
chmod 600 secrets.env # rw------- — owner-only secrets
chmod 700 /opt/myapp/bin # rwx------ — private directory
# Symbolic mode — easier to read
chmod u+x script.sh # Add execute for owner
chmod g-w config.yaml # Remove write from group
chmod o-rwx secrets.env # Remove all permissions from others
chmod a+r README.md # Add read for all (a = all)
# Recursive — apply to directory and everything inside
chmod -R 755 /var/www/html/
Here's the quick reference for common permission sets:
| Octal | Symbolic | Use Case |
|---|---|---|
755 | rwxr-xr-x | Executable scripts, public directories |
644 | rw-r--r-- | Config files, HTML, public read |
600 | rw------- | Private keys, secrets, credentials |
700 | rwx------ | Private directories, .ssh/ |
775 | rwxrwxr-x | Shared group directories |
666 | rw-rw-rw- | Never use this in production |
chown and chgrp — Changing Ownership
Permissions don't matter if the wrong user owns the file. This is the most common cause of "Permission denied" in deployments.
# Change owner
sudo chown deploy deploy.sh
# Change owner and group
sudo chown deploy:apps /opt/myapp/config.yaml
# Recursive ownership change — critical for deployments
sudo chown -R www-data:www-data /var/www/html/
# Change group only
sudo chgrp docker /var/run/docker.sock
# Real scenario: Fix Nginx permission issues
sudo chown -R www-data:www-data /usr/share/nginx/html/
sudo find /usr/share/nginx/html/ -type d -exec chmod 755 {} \;
sudo find /usr/share/nginx/html/ -type f -exec chmod 644 {} \;
That last example is a pattern you'll use constantly: directories get 755 (need execute to enter), files get 644 (no execute needed).
Special Permissions — SUID, SGID, and Sticky Bit
These are the permissions most engineers don't understand but absolutely should.
SUID (Set User ID) — Octal 4000
When set on an executable, it runs as the file owner, not the user who launched it.
# Check which system binaries have SUID
find /usr/bin -perm -4000 -ls
# You'll see passwd, sudo, ping — they need root privileges
# The passwd command is owned by root with SUID
ls -la /usr/bin/passwd
# -rwsr-xr-x 1 root root 68208 Feb 25 10:00 /usr/bin/passwd
# Notice the 's' instead of 'x' in owner permissions
# Set SUID (be very careful with this)
sudo chmod u+s /opt/myapp/special-binary
sudo chmod 4755 /opt/myapp/special-binary
SGID (Set Group ID) — Octal 2000
On directories, new files inherit the directory's group instead of the creator's primary group. This is essential for shared project directories.
# Create a shared project directory
sudo mkdir /opt/shared-project
sudo chown root:developers /opt/shared-project
sudo chmod 2775 /opt/shared-project
# Now any file created inside inherits the 'developers' group
touch /opt/shared-project/newfile.txt
ls -la /opt/shared-project/newfile.txt
# -rw-r--r-- 1 vivek developers 0 Feb 25 10:00 newfile.txt
Sticky Bit — Octal 1000
On directories, only the file owner can delete their files — even if others have write access. The classic example is /tmp.
# Check /tmp — notice the 't' at the end
ls -ld /tmp
# drwxrwxrwt 15 root root 4096 Feb 25 10:00 /tmp
# Set sticky bit on a shared directory
sudo chmod 1777 /opt/shared-uploads
sudo chmod +t /opt/shared-uploads
# Users can create files but can't delete each other's files
umask — Default Permission Control
When you create a file, it doesn't get 777 by default. The umask subtracts permissions from the maximum.
# Check current umask
umask
# 0022 — typical default
# How it works:
# Files: 666 - 022 = 644 (rw-r--r--)
# Dirs: 777 - 022 = 755 (rwxr-xr-x)
# Test it
umask 0022
touch testfile && mkdir testdir
ls -la testfile testdir
# -rw-r--r-- testfile
# drwxr-xr-x testdir
# Set a stricter umask for security
umask 0077
touch securefile
ls -la securefile
# -rw------- securefile
# Make it permanent — add to ~/.bashrc or /etc/profile
echo "umask 0027" >> ~/.bashrc
| umask | File Result | Dir Result | Use Case |
|---|---|---|---|
0022 | 644 | 755 | Default, public readable |
0027 | 640 | 750 | Group readable, no others |
0077 | 600 | 700 | Owner only, maximum security |
ACLs — When Basic Permissions Aren't Enough
Standard permissions give you owner, group, and others. But what if you need to give a specific user access without changing the group? That's where Access Control Lists (ACLs) come in.
# Install ACL tools (usually pre-installed)
sudo apt install acl
# Grant a specific user read access to a file
setfacl -m u:jenkins:rx /opt/myapp/deploy.sh
# Grant a group write access
setfacl -m g:qa-team:rw /opt/myapp/config.yaml
# View ACLs — notice the '+' in ls output
ls -la /opt/myapp/deploy.sh
# -rwxr-x---+ 1 deploy apps 2048 Feb 25 10:00 deploy.sh
getfacl /opt/myapp/deploy.sh
# Set default ACLs on a directory (inherited by new files)
setfacl -d -m g:developers:rwx /opt/shared-project/
# Remove ACLs
setfacl -x u:jenkins /opt/myapp/deploy.sh
setfacl -b /opt/myapp/deploy.sh # Remove all ACLs
Real-World Scenario: Fixing Deployment Permissions
Here's a complete script to set up proper permissions for a web application deployment:
#!/bin/bash
# fix-permissions.sh — Run after every deployment
APP_DIR="/var/www/myapp"
APP_USER="www-data"
APP_GROUP="www-data"
DEPLOY_GROUP="deploy"
# Set ownership
sudo chown -R ${APP_USER}:${APP_GROUP} ${APP_DIR}
# Directories: owner+group can enter, others can read
sudo find ${APP_DIR} -type d -exec chmod 750 {} \;
# Files: owner can read/write, group read only
sudo find ${APP_DIR} -type f -exec chmod 640 {} \;
# Scripts need execute permission
sudo find ${APP_DIR}/bin -type f -exec chmod 750 {} \;
# Secrets — owner only
sudo chmod 600 ${APP_DIR}/.env
sudo chmod 600 ${APP_DIR}/config/secrets.yaml
# Logs directory — writable by app
sudo chmod 770 ${APP_DIR}/logs/
# Allow deploy group to update files
sudo setfacl -R -m g:${DEPLOY_GROUP}:rwx ${APP_DIR}
sudo setfacl -R -d -m g:${DEPLOY_GROUP}:rwx ${APP_DIR}
echo "Permissions fixed successfully."
Quick Troubleshooting Checklist
When you hit "Permission denied," check these in order:
# 1. Who am I running as?
whoami
id
# 2. Who owns the file?
ls -la /path/to/file
# 3. What groups am I in?
groups
# 4. Are there ACLs?
getfacl /path/to/file
# 5. Is the parent directory traversable?
namei -l /path/to/file
The namei command is a hidden gem — it shows permissions for every directory in the path. If any directory along the way lacks execute permission for your user, you can't reach the file, even if the file itself has the right permissions.
Next up: Linux Process Management — learn how to find and fix that runaway process eating 100% CPU at 3 AM.
