Senior 6 min · March 06, 2026

Linux File Permissions: Chown Pitfall Breaks Nginx Workers

Nginx workers denied read to /etc/nginx after chown to deploy:deploy with 750 — fix: always check process user's permissions for config files.

N
Naren · Founder
Plain-English first. Then code. Then the interview question.
About
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • Linux permissions control read, write, execute for three scopes: owner, group, other
  • Each scope has three bits, combined into a nine-bit string like rwxr-xr--
  • chmod changes permissions (symbolic: chmod u+x file; octal: chmod 755 file)
  • chown changes owner and group; chgrp changes group only
  • setuid, setgid, and sticky bits are special modes that alter execution behavior
  • Most permission-related incidents stem from missing execute on directories or incorrect group ownership
Plain-English First

Think of every file on your Linux system as a room in a building. File permissions are the lock-and-key rules that decide who can enter (read), rearrange the furniture (write), or actually use the room as an office (execute). There are three groups of people who might want in: the owner of the room, a team the owner belongs to, and everyone else in the building. Linux lets you set different rules for each group — so your boss can edit the room, your colleagues can only look around, and random strangers can't even peek through the window.

Every production outage story that starts with 'well, someone accidentally deleted...' or 'the web server suddenly couldn't read its own config...' has file permissions somewhere near the root cause. Linux file permissions aren't just a theoretical concept buried in a sysadmin handbook — they're the first line of defence between a running application and chaos. Misconfigure them and you've either locked your own service out of its data, or handed a malicious actor a skeleton key to your entire system.

The permission system exists because Linux was designed from day one as a multi-user operating system. Unlike early personal computers where one person owned everything, Unix-descended systems needed a way to let dozens of users share the same machine without stepping on each other's work — or worse, each other's secrets. The read/write/execute model, combined with user/group/other ownership, solves that problem elegantly with just nine bits of information per file.

By the end of this article you'll be able to read any permission string at a glance, write chmod commands in both symbolic and octal notation without guessing, set up group-based access patterns for real team deployments, understand the dangerous-but-useful setuid/setgid/sticky bits, and diagnose permission-related errors before they become production incidents.

What Are Linux File Permissions?

Linux file permissions are a set of rules that determine who can read, write, or execute a file or directory. Every file and directory is owned by a user and a group. Permissions are defined for three categories: the file owner, the owning group, and everyone else (others). The permissions themselves are three bits: read (r), write (w), and execute (x). For directories, read lets you list contents, write lets you create/delete files inside, and execute lets you enter the directory.

The classic permission string looks like this: -rwxr-xr--. The first character is the file type (- for regular file, d for directory, l for symlink). The next nine characters are three triples: owner, group, others. The example above means the owner can read, write, and execute; group members can read and execute; everyone else can only read.

Chances are you've seen Permission denied when trying to access a file you know exists. That's the permission system doing its job — blocking access that wasn't explicitly granted. Understand these nine bits and you'll stop guessing why your app can't read its config.

check-permissions.shBASH
1
2
3
4
5
6
#!/bin/bash
ls -la /var/log/auth.log
# Output example:
# -rw-r----- 1 root adm 12345 Mar 06 10:00 /var/log/auth.log
# Owner: root (rw-), Group: adm (r--), Others: --- (-)
# So only root can write; root and adm group members can read; nobody else can access
Output
-rw-r----- 1 root adm 12345 Mar 06 10:00 /var/log/auth.log
Permission Bits as a Tri-Lock System
  • Owner triple: the file's owner can set different restrictions for themselves.
  • Group triple: files belong to one group; all group members share the second key.
  • Other triple: any user not owner or group member — the public lock.
  • A missing execute bit on a directory is like locking the building door: even with read, you can't step inside.
Production Insight
A common production trap: giving 777 permissions to a directory 'just to make it work.'
That means every user on the system can write to that directory — including compromised accounts.
Rule: never use 777 in production. Use octal modes like 755 for directories that need broad access, 750 if group control is enough.
Key Takeaway
Permissions are nine bits, not magic.
The default umask typically gives 755 for directories and 644 for files — that's secure for most single-user setups.
The rule: if a file's permissions don't match the process user's needs, you get a silent crash.
When to Use Which Permission Level
IfSingle user needs full control (e.g., personal scripts)
Use700 for files (rwx------), 755 for directories (rwxr-xr-x)
IfMultiple users in a team need write access to shared files
UseUse a common group, set ownership to that group, and use 775 (rwxrwxr-x)
IfWeb server needs to read static assets but not write
UseOwner: deployment user, Group: www-data, Permissions: 644 for files, 755 for directories
IfA binary should execute as a different user (e.g., passwd)
UseSet the setuid bit: chmod 4755 (u+s)

Changing Permissions with chmod (Symbolic and Octal)

chmod is your tool to modify permissions. Two modes: symbolic and octal. Symbolic uses letters: u (owner), g (group), o (others), a (all), combined with + (add), - (remove), = (set exactly). Example: chmod u+x script.sh adds execute for the owner. Octal mode uses a three-digit number (0-7) where each digit represents the sum of r=4, w=2, x=1. So 754 means owner=rwx (7), group=rx (5), others=r (4).

Which should you use? Symbolic is clearer for one-off changes like 'add execute for owner.' Octal is more reliable for scripts because it's unambiguous — you set the exact permissions regardless of the current state. Use octal in deployment pipelines.

A common mistake: chmod 777 to 'fix' a permission problem. Don't. It's lazy and dangerous. Always figure out the minimum permissions needed. For directories, note that execute is required to enter — so web apps often need 755 on public directories.

chmod-examples.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/bin/bash
# Symbolic: add execute for owner
chmod u+x deploy.sh

# Symbolic: remove write for group and others
chmod go-w config.yml

# Symbolic: set exactly rwx for owner, rx for group, nothing for others
chmod u=rwx,g=rx,o= config.yml

# Octal: set to 755 (rwxr-xr-x)
chmod 755 app.sh

# Octal: set to 644 (rw-r--r--)
chmod 644 index.html

# Recursive: change all files inside directory (be careful!)
chmod -R 755 /var/www/html
Note: Recursive chmod Can Wreck Your System
Using chmod -R on a large directory tree without checking what's inside can break setuid binaries, change permissions on sensitive system files, or make scripts inaccessible. Always test on a copy or use find with -type d and -type f to set directory vs file permissions separately.
Production Insight
Using symbolic mode in a script can accidentally add or remove bits you didn't intend.
Octal mode is deterministic: chmod 644 always produces the exact same permissions regardless of the current state.
Rule: use octal in automation, symbolic in interactive shells.
Key Takeaway
chmod octal is idempotent.
Symbolic is great for quick one-off changes.
Never use 777 — find the minimal permissions your service needs.

Changing Ownership with chown and chgrp

chown changes the owner and/or group of a file. chgrp changes only the group. The syntax: chown newowner:newgroup file — if you omit the group, the file's group stays unchanged; if you omit the owner, you must include a colon: :newgroup. Common scenario: after deploying code with a build tool (like Jenkins running as jenkins user), the files end up owned by jenkins:jenkins. But your web server runs as www-data. The server can't read the files. The fix: chown -R www-data:www-data /var/www/app or better, chown -R www-data:appgroup /var/www/app and use group permissions.

Important: only root can change file owners. However, the owner of a file can change its group to any group they belong to (with chgrp). So if an app runs as a non-root user but needs to share files with a team, the user can chgrp the files to a common group.

A tricky edge case: symbolic links. By default, chown and chgrp follow symlinks — they change the target, not the link itself. To change the link's ownership, use chown -h. This catches many admins off guard.

chown-examples.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/bin/bash
# Change owner only
chown alice report.txt

# Change owner and group
chown alice:developers report.txt

# Change group only (alternatives)
chown :developers report.txt
chgrp developers report.txt

# Recursively change owner and group (common deployment)
chown -R www-data:www-data /var/www/app

# Change symlink ownership directly (not target)
chown -h bob link_to_report.txt

# Find files owned by a specific user and fix them
find /var/www -user jenkins -exec chown www-data: {} +
Tip: Check User and Group Before Changing
Before fixing permissions, identify the users and groups involved: id, getent passwd, getent group. Know your service's runtime user (check systemd service file or process table with ps aux) – that's who needs access.
Production Insight
A CI/CD script that runs chown -R as root can accidentally change ownership of system files if the path is wrong.
Always isolate deployment directories and use explicit paths.
Rule: if you need to change ownership, understand the minimal scope and use find to target only the application files.
Key Takeaway
Only root can chown a file.
File owners can chgrp to groups they belong to.
Watch symlinks — they follow the target by default.

Special Modes: setuid, setgid, and the Sticky Bit

Beyond the basic nine bits, Linux provides three special permission modes that change how executables and directories behave:

  • setuid (octal 4000 or u+s): When set on an executable, the process runs with the file owner's privileges, not the user who launched it. Classic example: /usr/bin/passwd — it's owned by root and has setuid, so normal users can change their own password (which writes to /etc/shadow that only root can write to).
  • setgid (octal 2000 or g+s): On executables, the process runs with the group of the file. On directories, new files created inside inherit the directory's group, not the creating user's primary group — incredibly useful for shared team directories.
  • Sticky bit (octal 1000 or o+t): On directories, only the owner of a file can delete or rename that file. Most commonly used on /tmp — everyone can write to /tmp, but only the file owner can remove what they created.

Setuid is powerful and dangerous. A bug in a setuid binary can give an attacker root privileges. That's why Linux ignores setuid on scripts (shebangs) for security reasons — but it still works on compiled binaries. Use setuid sparingly and audit it regularly.

Setgid on directories is a safe and effective pattern for collaborative workspaces. Example: chmod g+s /shared/project; chown :projectteam /shared/project.

special-modes.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/bin/bash
# View special bits in permission string
ls -l /usr/bin/passwd
# -rwsr-xr-x  — the s in owner's execute slot indicates setuid

# Set setuid on a compiled binary (must be root or owner)
chmod u+s mybinary
# Alternative octal: chmod 4755 mybinary

# Set setgid on a directory for team collaboration
chmod g+s /shared/teamdata
chown :developers /shared/teamdata
# Now new files get group 'developers' automatically

# Set sticky bit on a shared directory
chmod +t /shared/upload
# Only file owners can delete their files

# Check all setuid binaries on the system (security audit)
find / -type f -perm -4000 2>/dev/null
Output
-rwsr-xr-x 1 root root 68208 Mar 15 2024 /usr/bin/passwd
Security: Setuid on Shell Scripts Is Ignored
Linux kernel ignores the setuid bit on scripts with a shebang because of race conditions. If you need a privileged helper, write it in C or a compiled language and set setuid on the binary. Do NOT rely on setuid on Python or Bash scripts — it simply won't work.
Production Insight
Setgid on directories is one of the most underused production patterns.
It solves the 'different users create files with wrong group' problem without complex ACLs.
Setuid binaries are prime targets for privilege escalation — monitor them with tools like aide or tripwire.
Sticky bit on /tmp prevents a common attack: a user overwriting another's temporary file.
Key Takeaway
Setuid elevates privileges — use only when necessary.
Setgid on directories enforces group inheritance.
Sticky bit prevents unauthorised file deletion in shared spaces.

Real-World Patterns for Team Access

In production, it's rare to find a machine with only one user. You have deployment users, application users, monitoring agents, and human admins. The trick is to set up permissions so that every process gets exactly what it needs and nothing more.

Pattern 1: The Deploy User + Web Server User - Code is delivered by a deploy user (e.g., jenkins or deploy). The web server runs as www-data. - Best approach: make both users part of a common group like appgroup. Set files to 750 and directories to 755, owned by deploy:appgroup. The deploy user writes, the web server reads.

Pattern 2: Shared Project Directory - Team members need to edit files in /home/projects/foo. Each member has their own login. - Create a group fooproj, add all members. Set ownership nobody:fooproj (or any user, doesn't matter as long as group exists). Use setgid on the directory: chmod g+s /home/projects/foo; chmod 2775 /home/projects/foo. Now every file created inside automatically gets group fooproj and group-write permission.

Pattern 3: Locking Down Sensitive Config - Configuration files for a service (e.g., database credentials) should be readable only by the service user and root. Set chmod 600 or 640 if group needs read. Use chown root:servicegroup. - For log files, use 640 with owner being the service user and group being a monitoring group. Add the monitoring tool's user to that group.

These patterns avoid the 'chmod 777 everything' anti-pattern and keep your system secure.

production-pattern.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/bin/bash
# Pattern: Deploy + Web Server
mkdir -p /var/www/app
chown deploy:www-data /var/www/app
chmod 2775 /var/www/app   # setgid ensures new files inherit group
# Files: deploy writes, www-data reads
find /var/www/app -type f -exec chmod 640 {} \;
find /var/www/app -type d -exec chmod 2755 {} \;

# Pattern: Shared project with setgid
mkdir -p /shared/project
chown :projectgroup /shared/project
chmod 2775 /shared/project
# New files created by any team member will be group-writable

# Pattern: Config lockdown
# Presume service runs as 'myservice', monitoring as 'monitor'
cp config.prod.yml /etc/myapp/
chown root:myservice /etc/myapp/config.prod.yml
chmod 640 /etc/myapp/config.prod.yml
usermod -aG myservice monitor  # add monitor user to group
Use ACLs for Complex Scenarios
For cases where multiple groups need different permissions, Linux Access Control Lists (ACLs) give you fine-grained control beyond user/group/other. Use setfacl -m g:deploy:rwx,g:www-data:rx /path. But be aware: ACLs add complexity and are not visible in 'ls -l' — you need getfacl to see them.
Production Insight
Team access patterns often fail because someone forgets the setgid on a shared directory.
Without setgid, each user's files end up in the user's primary group, breaking shared write access.
Rule: if a directory is shared, use setgid (chmod g+s) and 2775 to maintain group consistency.
Key Takeaway
Design permissions around user and group relationships, not broad access.
Setgid on shared directories is your friend.
Minimal permissions = fewer security incidents.

Debugging Permission Errors in Practice

When you hit a permission error, don't guess. Follow a systematic approach:

  1. Identify the process user: ps aux | grep service-name – that user is who needs access.
  2. Check the file's permissions and ownership: ls -la /path, stat -c '%a %U %G' /path.
  3. Check the path: A directory in the path may lack execute permission. Use namei -l /full/path.
  4. Check groups: id $service_user — is the service user in the group that owns the file?
  5. Check for ACLs: getfacl /path — ACLs override basic permissions.
  6. Check for mount options: mount | grep /path — the filesystem may be mounted with noexec or nosuid.

Most permission bugs are simple: missing execute on a directory, wrong group membership, or an accidental chmod 000 from a script. But sometimes the problem is subtle — like a SELinux or AppArmor policy that denies even though file permissions look correct. Always check dmesg or ausearch for AVC denials if you're on an SELinux-enabled system.

debug-permissions.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/bin/bash
# Step 1: Find which user is running the process
SERVICE=$(systemctl show -p MainPID nginx.service | cut -d= -f2)
USER=$(ps -o user= -p $SERVICE)
echo "Process runs as: $USER"

# Step 2: Check file and path
FILE="/var/www/app/index.php"
ls -la "$FILE"
namei -l "$FILE"

# Step 3: Check user's groups
echo "User groups:"
id "$USER"

# Step 4: Check if ACLs are involved
getfacl "$FILE"

# Step 5: Check mount options for noexec
echo "Mount options for parent directory:"
stat -f -c '%T %O' "$FILE"
Remember: Strace Shows Real-Time Access Checks
If you need to see exactly what permissions are being checked, run strace -e trace=access,openat -p $PID. It shows every file access attempt and the return code. This is the nuclear option — always try namei first.
Production Insight
The most misleading permission error is a 403 from a web server that actually comes from a missing index file or a configuration reload. Always check the web server error log, not just the HTTP status.
SELinux blocks can look identical to file permission errors. If you see 'Permission denied' but file permissions seem correct, check /var/log/audit/audit.log or run ausearch -m avc.
Rule: use namei -l to check the entire path before anything else — 80% of permission bugs are a directory in the path lacking execute.
Key Takeaway
Don't guess — trace the actual path with namei.
Check SELinux if file permissions look right.
The process user is who matters, not the user running your debug commands.
● Production incidentPOST-MORTEMseverity: high

The Web Server That Couldn't Read Its Own Config

Symptom
Nginx returned 500 Internal Server Error for all requests. The application logs showed 'Permission denied' when trying to read /etc/nginx/sites-enabled/app.conf.
Assumption
“We use a shared deployment user — it must be able to read all config files.” The deployment script ran chown deploy:deploy on the entire /etc/nginx directory.
Root cause
The nginx master process runs as root, but the worker processes run as the 'www-data' user. After chown, /etc/nginx was owned by deploy:deploy with 750 permissions, meaning www-data had no read access. The worker couldn't open the config file.
Fix
Re-run chown -R root:root /etc/nginx and set permissions to 755. Then add the deploy user to the www-data group, and ensure config files have group-read (chmod 640 or 644).
Key lesson
  • Always check which system user actually reads files your service depends on.
  • Never chown entire directories without understanding the process ownership.
  • Document the required ownership and permissions for every component in your deployment playbook.
Production debug guideTrack down who owns what and which permissions block access4 entries
Symptom · 01
Application logs show 'Permission denied' when reading a file
Fix
Run ls -la on the file to check owner, group, and permissions. Use stat to see exact numeric permissions: stat -c '%a %n' filename
Symptom · 02
Web server returns 403 Forbidden on a directory
Fix
Check directory permissions — read (r) allows listing files, execute (x) allows entering the directory. Run namei -l /path/to/dir to trace permissions along the full path
Symptom · 03
A service fails silently after a permission change by a deployment script
Fix
Compare before and after permissions using a Git-based permission tracking tool like etckeeper, or use acl getfacl on the directory
Symptom · 04
sudo commands work interactively but fail in systemd or cron
Fix
Ensure the service user has direct access. Run su -s /bin/bash -c 'cat /path/to/file' serviceuser to simulate the service environment
★ Permission Debugging Quick ReferenceCommon permission errors and the exact commands to diagnose and fix them.
Cannot read file: Permission denied
Immediate action
Check current user id and group membership
Commands
id; ls -la /path/to/file; stat -c '%a %n' /path/to/file
getfacl /path/to/file (if ACLs are enabled)
Fix now
chmod u+r /path/to/file (if you are the owner) or chown $(whoami) /path/to/file
Cannot execute directory traversal: Permission denied+
Immediate action
Check path each directory's execute permission
Commands
namei -l /path/with/multiple/dirs/to/file
ls -ld /path/with/multiple/dirs/to/file /path/with/multiple/dirs/to /path/with/multiple/dirs
Fix now
chmod +x /path/to/parent for each directory lacking execute
setuid binary runs as root, not as owner+
Immediate action
Check that the setuid bit is actually set
Commands
ls -l /usr/bin/somebinary | grep '^...s'
stat -c '%a' /usr/bin/somebinary (look for 4 in first digit, e.g., 4755)
Fix now
chmod u+s /usr/bin/somebinary (only if you own the file and have root)
Permission Modes at a Glance
OctalSymbolicPermission StringTypical Use Case
755rwxr-xr-x-rwxr-xr-x (dir: drwxr-xr-x)Executables, public directories
644rw-r--r---rw-r--r--Static files, configs (read-only for group/other)
600rw--------rw-------Sensitive files (SSH keys, passwords)
700rwx-------rwx------Private scripts, user home directories
2775rwxrwxr-x (setgid)drwxrwsr-xShared team directories (setgid)
4755rwxr-xr-x (setuid)-rwsr-xr-xPrivileged binaries (passwd, ping)
1777rwxrwxrwtdrwxrwxrwtWorld-writable sticky (like /tmp)

Key takeaways

1
Linux permissions consist of three triples (owner/group/other) for read, write, execute.
2
Use octal chmod in automation for idempotent results; symbolic for interactive work.
3
Only root can chown a file; non-root users can chgrp to groups they belong to.
4
setuid elevates privileges (use sparingly); setgid on directories enforces group inheritance; sticky bit protects files in shared writable directories.
5
Always debug permission errors by tracing the full path with namei and checking SELinux/AppArmor if file permissions appear correct.
6
Never use 777 in production—opt for 755/644 and group-based access patterns.

Common mistakes to avoid

5 patterns
×

Using chmod 777 as a quick fix

Symptom
Files become world-writable; any compromised user can modify them, leading to potential data loss or injection attacks.
Fix
Never use 777. Determine the minimum permissions needed: e.g., 755 for executable directories, 644 for readonly files. Use groups and setgid to share access safely.
×

Forgetting the execute bit on directories

Symptom
Users can list files in a directory but cannot access them (e.g., 'cd' fails) because the directory lacks execute permission. Web servers may return 403 even if individual files are readable.
Fix
Ensure directories have at least 755 (rwxr-xr-x). For shared directories, use 2755 (setgid) to maintain group consistency.
×

Changing ownership recursively without understanding the scope

Symptom
A deployment script running chown -R on /var/www also chowns files in unintended subdirectories, breaking other services or creating security holes.
Fix
Always test on a small subset. Use find with -type to target only files or directories. Consider using setgid on the root directory instead of recursive chown.
×

Assuming setuid works on interpreted scripts

Symptom
A developer sets setuid on a Python script expecting it to run as root, but the script still runs as the calling user. Permissions that appear correct do not work as expected.
Fix
Linux ignores setuid on scripts with shebang lines. Use a compiled binary with setuid, or use sudoers rules for specific commands.
×

Ignoring path permissions when troubleshooting

Symptom
The file has 644 permissions and the user is in the correct group, but still gets 'Permission denied'. The missing execute bit on a parent directory is blocking access.
Fix
Use namei -l /full/path to check each component. Ensure all directories in the path have execute (x) set at least for the relevant scope.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
What does the permission string '-rwsr-xr--' mean? Who can execute the f...
Q02SENIOR
Explain the difference between chmod 755 and chmod 755 + setgid (chmod 2...
Q03SENIOR
A web server returns 403 Forbidden for a specific file. The file shows 6...
Q04SENIOR
What is the sticky bit and where is it commonly used?
Q05SENIOR
How would you set up a shared directory where a team of developers can a...
Q01 of 05SENIOR

What does the permission string '-rwsr-xr--' mean? Who can execute the file and with what privileges?

ANSWER
The first character '-' indicates a regular file. Owner (rwx + setuid 's') has read, write, execute, and the setuid bit set – meaning the file runs with the owner's privileges. Group (r-x) has read and execute. Others (r--) have only read. The setuid bit means the file will execute as the owner (likely root), not as the calling user. Typical example: /usr/bin/passwd.
FAQ · 5 QUESTIONS

Frequently Asked Questions

01
What does chmod 755 mean?
02
How do I make a file executable?
03
What is the difference between chown and chgrp?
04
Why does my web server return 403 even though the file has 644 permissions?
05
Can I set the setuid bit on a Python script?
🔥

That's Linux. Mark it forged?

6 min read · try the examples if you haven't

Previous
Linux File System
3 / 12 · Linux
Next
Linux Process Management