Linux file permissions are the foundation of system security. Every file and directory on a Linux system has an owner, a group, and a set of permission flags that determine who can read, write, or execute it. Mastering chmod, chown, and chgrp is essential for any developer, sysadmin, or DevOps engineer working with servers, containers, or CI/CD pipelines. This comprehensive guide covers everything from basic permission concepts to advanced ACLs and real-world troubleshooting.
1. The Linux Permission Model
Linux uses a discretionary access control (DAC) model. Every file and directory has three categories of users and three types of permissions:
User Categories
- User (u) โ The file owner. Usually the user who created the file.
- Group (g) โ Users belonging to the file's group. Useful for team collaboration.
- Other (o) โ Everyone else on the system.
- All (a) โ A shorthand for user + group + other.
Permission Types
- Read (r = 4) โ View file contents; list directory entries.
- Write (w = 2) โ Modify file contents; create, rename, or delete files in a directory.
- Execute (x = 1) โ Run a file as a program; enter (cd into) a directory.
Note: For directories, read means you can list contents, write means you can create/delete entries, and execute means you can traverse (cd into) the directory. A directory with read but no execute allows listing names but not accessing file metadata.
2. Reading Permissions โ ls -la Output Explained
The ls -la command displays detailed file information including permissions. Here is how to read the output:
$ ls -la
total 32
drwxr-xr-x 5 alice developers 4096 Feb 10 09:00 .
drwxr-xr-x 3 root root 4096 Jan 15 12:00 ..
-rw-r--r-- 1 alice developers 1234 Feb 10 08:55 README.md
-rwxr-xr-x 1 alice developers 2048 Feb 10 08:50 deploy.sh
drwxrwx--- 2 alice developers 4096 Feb 09 14:30 shared/
lrwxrwxrwx 1 alice developers 11 Feb 08 10:00 config -> config.yamlLet's break down a typical permission string:
-rwxr-xr-x 1 alice developers 2048 Feb 10 deploy.sh
โโโฌโโโฌโโโฌโ โ โ โ โ โ โ
โ โ โ โ โ โ โ โ โ โโ filename
โ โ โ โ โ โ โ โ โโ modification date
โ โ โ โ โ โ โ โโ file size (bytes)
โ โ โ โ โ โ โโ group owner
โ โ โ โ โ โโ file owner
โ โ โ โ โโ hard link count
โ โ โ โโ other permissions: r-x (read + execute = 5)
โ โ โโ group permissions: r-x (read + execute = 5)
โ โโ owner permissions: rwx (read + write + execute = 7)
โโ file type: - (regular file)The first character indicates the file type: - for regular file, d for directory, l for symbolic link, b for block device, c for character device.
The next 9 characters are three groups of three: owner permissions, group permissions, and other permissions. Each group shows read (r), write (w), and execute (x) โ or a dash (-) if the permission is not set.
3. chmod Numeric (Octal) Mode
In numeric mode, permissions are expressed as a three-digit (or four-digit for special permissions) octal number. Each digit is the sum of its permission values:
Read (r) = 4
Write (w) = 2
Exec (x) = 1
None = 0
rwx = 4 + 2 + 1 = 7
rw- = 4 + 2 + 0 = 6
r-x = 4 + 0 + 1 = 5
r-- = 4 + 0 + 0 = 4
--- = 0 + 0 + 0 = 0To calculate: add Read (4) + Write (2) + Execute (1) for each category. For example, rwxr-xr-- = 7 (4+2+1), 5 (4+0+1), 4 (4+0+0) = 754.
# Set permissions using numeric mode
chmod 755 script.sh # rwxr-xr-x
chmod 644 config.json # rw-r--r--
chmod 600 id_rsa # rw-------
chmod 400 server.crt # r--------
chmod 750 /opt/project # rwxr-x---
chmod -R 755 /var/www/html # Recursive for directory treeCommon Permission Values
777777 (rwxrwxrwx) โ Full access for everyone. DANGEROUS โ avoid in production!755755 (rwxr-xr-x) โ Owner full access; group and others can read and execute. Standard for directories and scripts.750750 (rwxr-x---) โ Owner full; group read/execute; others no access. Good for shared project directories.700700 (rwx------) โ Owner full access only. Use for ~/.ssh directory.644644 (rw-r--r--) โ Owner can read/write; others read only. Standard for regular files.600600 (rw-------) โ Owner read/write only. Use for private configs, SSH keys.400400 (r--------) โ Owner read only. Use for certificates and sensitive read-only data.4. chmod Symbolic Mode
Symbolic mode lets you modify specific permissions without setting all of them at once. The syntax is chmod [who][operator][permissions] where:
- Who: u (user/owner), g (group), o (others), a (all)
- Operator: + (add), - (remove), = (set exactly)
- Permissions: r (read), w (write), x (execute), X (execute only if directory or already executable)
# Add execute permission for owner
chmod u+x script.sh
# Remove write permission from group
chmod g-w config.json
# Set others to read-only
chmod o=r public.html
# Add read permission for all users
chmod a+r README.md
# Add execute for owner, remove write from group and others
chmod u+x,go-w deploy.sh
# Set owner=rwx, group=rx, others=nothing
chmod u=rwx,g=rx,o= project/
# Add execute only to directories (not files) recursively
chmod -R a+X /var/www/html
# Copy permissions from owner to group
chmod g=u shared-file.txtThe advantage of symbolic mode is that you can change one permission without affecting others. This is especially useful in scripts and automation.
5. chown โ Change File Owner and Group
The chown command changes the owner and/or group of files and directories. Only root (or sudo) can change file ownership. This is essential for setting up web servers, Docker volumes, and shared project directories.
Syntax and Examples
chown accepts several formats:
chown user fileโ Change owner onlychown user:group fileโ Change owner and groupchown :group fileโ Change group only (same as chgrp)chown -R user:group dir/โ Recursive change for directory and all contents
# Change owner to 'alice'
sudo chown alice report.pdf
# Change owner and group
sudo chown alice:developers project/
# Change only the group (equivalent to chgrp)
sudo chown :www-data /var/www/html
# Recursive change for an entire directory tree
sudo chown -R www-data:www-data /var/www/html
# Change owner only if currently owned by 'bob'
sudo chown --from=bob alice important-file.txt
# Match ownership of another file
sudo chown --reference=/var/www/html/index.html newfile.html
# Change ownership of symlink itself (not the target)
sudo chown -h alice symlink.txt
# Verbose mode โ see what changes
sudo chown -Rv alice:developers /opt/project/Useful chown Options
-v(verbose) โ Print each file as it is changed--reference=reffileโ Set ownership to match another file-hโ Change symlink ownership instead of the target file--from=olduserโ Only change if current owner matches
6. chgrp โ Change Group Ownership
The chgrp command changes the group ownership of files. While chown :group can do the same thing, chgrp has a key advantage: regular users can use it to change a file's group to any group they belong to, without requiring root.
# Change group to 'developers'
chgrp developers project-file.txt
# Recursive group change
chgrp -R www-data /var/www/html
# Match group of another file
chgrp --reference=existing-file.txt new-file.txt
# Check your groups
groups
# Output: alice developers docker sudo
# Verbose mode
chgrp -Rv developers /opt/project/Use chgrp when:
- You only need to change the group (not the owner)
- You are a regular user changing group to one of your groups
- In scripts where clarity matters โ chgrp clearly communicates intent
7. Special Permissions โ SUID, SGID, Sticky Bit
Beyond the standard rwx permissions, Linux has three special permission bits that modify how files and directories behave. These are represented as a fourth (leading) octal digit.
When set on an executable file, the process runs with the permissions of the file owner, not the user who runs it. The classic example is /usr/bin/passwd, which needs root privileges to modify /etc/shadow but can be run by any user.
In ls -l output, SUID shows as s in the owner execute position: -rwsr-xr-x. If the underlying execute bit is not set, it shows as S (uppercase).
# Set SUID
chmod 4755 /usr/local/bin/my-tool
chmod u+s /usr/local/bin/my-tool
# Verify โ shows 's' in owner execute position
ls -l /usr/bin/passwd
-rwsr-xr-x 1 root root 68208 Mar 14 /usr/bin/passwdOn an executable, the process runs with the file's group. On a directory, new files created inside automatically inherit the directory's group instead of the creator's primary group. This is essential for shared project directories.
# Set SGID on a directory โ new files inherit group
chmod 2775 /opt/shared-project
chmod g+s /opt/shared-project
# Verify โ shows 's' in group execute position
ls -ld /opt/shared-project
drwxrwsr-x 2 root developers 4096 Feb 10 /opt/shared-project
# Now any file created inside inherits 'developers' group
touch /opt/shared-project/newfile.txt
ls -l /opt/shared-project/newfile.txt
-rw-r--r-- 1 alice developers 0 Feb 10 newfile.txtWhen set on a directory, only the file owner, the directory owner, or root can delete or rename files within it โ even if others have write permission. The classic example is <code>/tmp</code> (permissions 1777).
# Set sticky bit
chmod 1777 /tmp
chmod +t /shared-uploads
# Verify โ shows 't' in others execute position
ls -ld /tmp
drwxrwxrwt 15 root root 4096 Feb 10 /tmpFinding Files with Special Permissions
# Find all SUID files on the system
find / -perm -4000 -type f 2>/dev/null
# Find all SGID files
find / -perm -2000 -type f 2>/dev/null
# Find all files with sticky bit
find / -perm -1000 -type d 2>/dev/null
# Find SUID or SGID files (security audit)
find / -type f \( -perm -4000 -o -perm -2000 \) -exec ls -la {} \; 2>/dev/null8. umask โ Default Permission Mask
The umask command sets the default permission mask for newly created files and directories. It works by subtracting permissions from the maximum defaults (666 for files, 777 for directories).
Calculation: Effective permissions = Maximum defaults - umask
# Check current umask
umask
0022
# Display in symbolic notation
umask -S
u=rwx,g=rx,o=rx
# Set umask for current session
umask 022 # Files: 644, Dirs: 755
umask 027 # Files: 640, Dirs: 750
umask 077 # Files: 600, Dirs: 700
umask 002 # Files: 664, Dirs: 775
# Example: umask 022
# New file: 666 - 022 = 644 (rw-r--r--)
# New directory: 777 - 022 = 755 (rwxr-xr-x)
# Example: umask 077
# New file: 666 - 077 = 600 (rw-------)
# New directory: 777 - 077 = 700 (rwx------)Common umask values:
- 022 โ Files get 644, directories get 755. The most common default.
- 027 โ Files get 640, directories get 750. More restrictive; others get no access.
- 077 โ Files get 600, directories get 700. Very restrictive; only owner has access.
- 002 โ Files get 664, directories get 775. Group-friendly for shared development.
To make umask persistent, add it to ~/.bashrc, ~/.profile, or /etc/profile for system-wide settings.
# Make umask persistent โ add to ~/.bashrc or ~/.profile
echo 'umask 022' >> ~/.bashrc
# System-wide default โ add to /etc/profile
echo 'umask 027' | sudo tee -a /etc/profile9. ACL โ Access Control Lists
Standard Linux permissions (user/group/other) only allow one owner and one group per file. When you need finer-grained access control โ such as giving a specific user read access without changing the file's group โ you need ACLs (Access Control Lists).
Reading ACLs with getfacl
# View ACL of a file
getfacl report.txt
# file: report.txt
# owner: alice
# group: developers
user::rw-
user:bob:r--
group::r--
group:designers:rw-
mask::rw-
other::---
# View ACL of a directory (includes default ACLs)
getfacl /opt/shared-project/Setting ACLs with setfacl
# Grant read access to a specific user
setfacl -m u:bob:r report.txt
# Grant read+write to a specific group
setfacl -m g:designers:rw report.txt
# Remove ACL for a specific user
setfacl -x u:bob report.txt
# Remove all ACLs
setfacl -b report.txt
# Recursive ACL change
setfacl -Rm u:bob:rx /opt/project/Default ACLs apply to new files and subdirectories created inside a directory. They act as an ACL template:
# Set default ACL on a directory โ new files inherit these
setfacl -d -m u:bob:rx /opt/shared-project/
setfacl -d -m g:designers:rw /opt/shared-project/
# View default ACLs
getfacl /opt/shared-project/
# default:user:bob:r-x
# default:group:designers:rw-Use ACLs when:
- Multiple users/groups need different access levels to the same file
- You cannot or do not want to create new system groups
- A web application needs access to files owned by a different user
Files with ACLs show a + at the end of the permission string in ls -l: -rw-r--r--+
10. Common Permission Setups
Here are production-ready permission configurations for common scenarios:
Web Server Files (Nginx / Apache)
# Nginx / Apache web root
sudo chown -R www-data:www-data /var/www/html
sudo find /var/www/html -type d -exec chmod 755 {} \;
sudo find /var/www/html -type f -exec chmod 644 {} \;
# Upload directory (needs write for web server)
sudo chmod 775 /var/www/html/uploads
sudo chown www-data:www-data /var/www/html/uploads
# PHP/Python application with write-protected code
sudo chown -R deploy:www-data /var/www/app
sudo find /var/www/app -type d -exec chmod 750 {} \;
sudo find /var/www/app -type f -exec chmod 640 {} \;
sudo chmod 770 /var/www/app/storage
sudo chmod 770 /var/www/app/cacheSSH Key Permissions
# SSH directory and key permissions (STRICT โ SSH refuses otherwise)
chmod 700 ~/.ssh # drwx------
chmod 600 ~/.ssh/id_rsa # -rw------- (private key)
chmod 644 ~/.ssh/id_rsa.pub # -rw-r--r-- (public key)
chmod 600 ~/.ssh/authorized_keys # -rw-------
chmod 600 ~/.ssh/config # -rw-------
chmod 644 ~/.ssh/known_hosts # -rw-r--r--Executable Scripts
# Make a script executable
chmod +x deploy.sh
chmod 755 /usr/local/bin/my-tool
# Cron scripts
chmod 750 /etc/cron.daily/backup-script
# Remove execute bit for safety when not needed
chmod -x data-file.csvLog Files
# Application logs โ readable by group, not others
chmod 640 /var/log/myapp/*.log
chown root:adm /var/log/myapp/*.log
# Log directory
chmod 750 /var/log/myapp
chown root:adm /var/log/myapp
# Logrotate creates new files with these permissions
# In /etc/logrotate.d/myapp:
# create 640 root admShared Project Directory
# Create a shared project directory with SGID
sudo mkdir /opt/project
sudo chown root:developers /opt/project
sudo chmod 2775 /opt/project
# SGID (2) ensures new files inherit 'developers' group
# All members of 'developers' group can read/write
# Files created by any team member are group-accessible
# Add a user to the group
sudo usermod -aG developers alice
# User must log out and back in for group change to take effect11. Security Best Practices
File permissions are your first line of defense. Follow these principles to keep your system secure:
- Principle of Least Privilege: Grant the minimum permissions needed. Start restrictive and loosen only if necessary.
- Never use 777: There is almost never a valid reason for 777 in production. If you think you need it, you probably need to fix ownership instead.
- Audit SUID/SGID files regularly: These are privilege escalation vectors. Use
findto discover them. - Use groups wisely: Instead of opening permissions to "others", add users to appropriate groups.
- Protect sensitive files: SSH keys (600), config files with passwords (600), TLS certificates private keys (600).
- Be careful with recursive chmod: Always double-check the path before running
chmod -R. A typo can break your system. - Monitor permission changes: Use auditd to log file permission changes in production environments.
# Security audit commands
# Find all world-writable files
find / -type f -perm -002 -not -path '/proc/*' 2>/dev/null
# Find all SUID executables (potential privilege escalation)
find / -perm -4000 -type f 2>/dev/null
# Find files with no owner (orphaned files)
find / -nouser -o -nogroup 2>/dev/null
# Find files writable by others in sensitive directories
find /etc /usr -type f -perm -002 2>/dev/null
# Check for files with 777 permissions
find / -type f -perm 0777 2>/dev/null | head -2012. Troubleshooting Common Permission Issues
Permission errors are among the most common issues on Linux systems. Here are the most frequent problems and their solutions:
"Permission denied" Errors
The most common permission error. Check these in order:
- 1. Check file permissions:
ls -la /path/to/file - 2. Check directory permissions (you need execute on every parent directory)
- 3. Check file ownership: does the running user/group match?
- 4. Check for ACLs:
getfacl /path/to/file - 5. Check SELinux or AppArmor:
ls -Z /path/to/file
# Systematic debugging of "Permission denied"
# Step 1: Check file permissions and ownership
ls -la /path/to/file
# Step 2: Check every parent directory (need 'x' on all)
namei -l /path/to/file
# Step 3: Check who you are running as
whoami
id
# uid=1000(alice) gid=1000(alice) groups=1000(alice),27(sudo),33(www-data)
# Step 4: Check ACLs
getfacl /path/to/file
# Step 5: Check SELinux context (if enabled)
ls -Z /path/to/file
getenforcesudo vs Fixing Ownership
Using sudo to bypass permission errors is a bad habit. Instead, fix the root cause:
- Bad:
sudo vim /var/www/html/index.html(creates root-owned files) - Good:
sudo chown -R www-data:www-data /var/www/htmlthen edit normally
# Fix web files created with wrong ownership
sudo chown -R www-data:www-data /var/www/html
sudo find /var/www/html -type d -exec chmod 755 {} \;
sudo find /var/www/html -type f -exec chmod 644 {} \;
# Fix home directory permissions
sudo chown -R $USER:$USER ~/
chmod 700 ~/Docker File Permission Issues
Docker containers often run as root inside the container, creating files with root ownership on the host. Common fixes:
# Problem: container creates root-owned files on host
docker run -v $(pwd)/data:/app/data myimage
ls -la data/
# -rw-r--r-- 1 root root 1234 Feb 10 output.txt <-- root owned!
# Solution 1: Run container as current user
docker run --user $(id -u):$(id -g) -v $(pwd)/data:/app/data myimage
# Solution 2: In Dockerfile, create and use non-root user
# FROM node:20
# RUN groupadd -g 1000 appuser && useradd -u 1000 -g appuser appuser
# USER appuser
# Solution 3: In docker-compose.yml
# services:
# app:
# user: "1000:1000"
# volumes:
# - ./data:/app/data
# Solution 4: Fix permissions in entrypoint
# ENTRYPOINT ["sh", "-c", "chown -R appuser:appuser /app/data && exec su-exec appuser $@"]Try our interactive Chmod Calculator tool
Chmod Calculator โFAQ
What is the difference between chmod, chown, and chgrp?
chmod changes file permissions (read, write, execute). chown changes the file owner and optionally the group. chgrp changes only the group ownership. chmod controls what actions are allowed, while chown and chgrp control who the permission categories apply to.
Why can't I chown a file without sudo?
Only root can change file ownership. This is a security measure โ if regular users could give away file ownership, they could bypass disk quotas and create files that appear to belong to other users. However, you can use chgrp without sudo to change a file's group to any group you belong to.
What does the "s" in -rwsr-xr-x mean?
The lowercase "s" in the owner execute position indicates the SUID (Set User ID) bit is set. This means the program runs with the file owner's privileges regardless of who executes it. For example, /usr/bin/passwd has SUID set so regular users can update /etc/shadow (which is owned by root).
How do I find all files with 777 permissions?
Run: find / -type f -perm 0777 2>/dev/null. To find and fix them: find /var/www -type f -perm 0777 -exec chmod 644 {} \;. For directories: find /var/www -type d -perm 0777 -exec chmod 755 {} \;. Regularly auditing for 777 permissions is a security best practice.
How do I fix "Permission denied" for Docker mounted volumes?
This happens because the container user UID differs from the host user UID. Solutions: 1) Use --user flag: docker run --user $(id -u):$(id -g). 2) Set ownership in Dockerfile: RUN chown -R appuser:appuser /app. 3) Use named volumes instead of bind mounts. 4) Match UIDs between container and host user.