chmod sets what actions users can perform on a file. chown sets who owns it. Together they form Linux's two-layer permission model — ownership determines identity, permissions determine capability.
In short: chmod 755 script.sh gives the owner full access and everyone else read+execute. chown deploy:www-data /var/www changes who owns a file and which group it belongs to. Add -R for recursive changes. Use umask to control defaults for new files.
How Linux Permissions Actually Work
Every file and directory on Linux has three metadata components:
- Owner — the user who owns the file
- Group — the group associated with the file
- Permissions — what each category (owner, group, others) can do
Run ls -l and you'll see all three:
ls -l deploy.sh
# -rwxr-xr-- 1 furuya developers 2048 Mar 23 10:00 deploy.sh
Breaking down -rwxr-xr--:
| Position | Characters | Meaning |
|---|---|---|
| 1 | - | File type (- = file, d = directory, l = symlink) |
| 2-4 | rwx | Owner permissions (read, write, execute) |
| 5-7 | r-x | Group permissions (read, execute) |
| 8-10 | r-- | Others permissions (read only) |
The furuya is the owner, developers is the group. Anyone in the developers group gets group permissions. Everyone else gets "others" permissions.
Why permission errors happen
The most common permission error I see on servers:
bash: ./deploy.sh: Permission denied
This usually means one of three things:
- The execute bit isn't set (
chmod +x deploy.sh) - You're not the owner and group/others don't have the right permissions
- A parent directory blocks traversal (missing
xon a directory in the path)
On a 32blog staging server, I spent 20 minutes debugging why Nginx couldn't serve static files. The files were 644 (correct), but the /var/www/32blog directory was 700 — Nginx's www-data user couldn't even enter the directory. Changed it to 755 and everything worked.
chmod: Setting Permissions with Numbers and Symbols
chmod has two syntax styles. Pick whichever clicks for you — they do exactly the same thing.
Octal (numeric) mode
Each permission maps to a number: read = 4, write = 2, execute = 1. Add them up per category.
| Octal | Binary | Permissions | Meaning |
|---|---|---|---|
7 | 111 | rwx | Full access |
6 | 110 | rw- | Read + write |
5 | 101 | r-x | Read + execute |
4 | 100 | r-- | Read only |
0 | 000 | --- | No access |
# Owner: rwx, Group: r-x, Others: r-x
chmod 755 deploy.sh
# Owner: rw-, Group: r--, Others: r--
chmod 644 config.yaml
# Owner: rwx, Group: ---, Others: ---
chmod 700 ~/.ssh
# Owner: rw-, Group: rw-, Others: ---
chmod 660 shared-log.txt
Symbolic mode
Instead of numbers, use letters. The format is [who][operator][permissions]:
- Who:
u(user/owner),g(group),o(others),a(all) - Operator:
+(add),-(remove),=(set exactly) - Permissions:
r(read),w(write),x(execute)
# Add execute permission for everyone
chmod a+x deploy.sh
# Remove write permission for group and others
chmod go-w config.yaml
# Set exact permissions for owner, remove all for others
chmod u=rwx,go=rx deploy.sh # Same as chmod 755
# Add write permission for the group only
chmod g+w shared-project/
Symbolic mode shines when you want to change one thing without affecting the rest. chmod g+w file adds group write without touching anything else. With octal mode you'd need to know the current permissions first.
Recursive changes with -R
# Set directories to 755 and files to 644 — the web server standard
chmod -R 755 /var/www/32blog/
# But wait — that makes files executable too. Better approach:
find /var/www/32blog -type d -exec chmod 755 {} +
find /var/www/32blog -type f -exec chmod 644 {} +
Useful chmod patterns
# Make a script executable
chmod +x scripts/deploy.sh
# Protect your SSH keys (SSH refuses to use keys with loose permissions)
chmod 600 ~/.ssh/id_ed25519
chmod 644 ~/.ssh/id_ed25519.pub
chmod 700 ~/.ssh
# Web application files
find /var/www/app -type d -exec chmod 755 {} +
find /var/www/app -type f -exec chmod 644 {} +
# Shared directory where group members can write
chmod 775 /opt/shared-project/
# Remove all permissions for others
chmod o= sensitive-data/
chown: Changing File Ownership
chown changes who owns a file and which group it belongs to. Unlike chmod, you typically need sudo to run chown — you can't give away your files to another user without root privileges.
Basic syntax
# Change owner only
sudo chown deploy deploy.sh
# Change owner and group
sudo chown deploy:www-data /var/www/32blog/
# Change group only (colon before group name, no owner)
sudo chown :www-data /var/www/32blog/index.html
# Same as above but using chgrp
sudo chgrp www-data /var/www/32blog/index.html
The user:group syntax handles the most common case in one command. When I set up a new 32blog deploy, the first thing I run is:
sudo chown -R deploy:www-data /var/www/32blog
This gives the deploy user ownership (so deployment scripts can write files) and the www-data group access (so Nginx can read them).
Recursive ownership changes
# Change owner and group for an entire directory tree
sudo chown -R deploy:www-data /var/www/32blog/
# Only change the group recursively
sudo chown -R :developers /opt/project/
# Verbose mode — see what's changing
sudo chown -Rv deploy:www-data /var/www/32blog/
Copying ownership from another file
# Make target.conf match the ownership of reference.conf
sudo chown --reference=reference.conf target.conf
This is handy when you're creating config files that need to match existing ones.
Handling symbolic links
By default, chown follows symbolic links and changes the target file. Use -h to change the link itself:
# Changes the symlink target's ownership
sudo chown deploy:www-data /var/www/current
# Changes the symlink itself
sudo chown -h deploy:www-data /var/www/current
Special Permissions: setuid, setgid, and Sticky Bit
Beyond the basic rwx, Linux has three special permission bits. You'll see them in the field and need to understand them for security audits.
setuid (Set User ID)
When set on an executable, it runs as the file owner instead of the user who launched it. The classic example:
ls -l /usr/bin/passwd
# -rwsr-xr-x 1 root root 68208 Mar 23 10:00 /usr/bin/passwd
That s in the owner's execute position means setuid is active. passwd runs as root even when a normal user executes it — because it needs to write to /etc/shadow. The Linux man page for chmod documents these special bits in detail.
# Set the setuid bit
chmod u+s /usr/local/bin/my-tool
# Or with octal (4 prefix)
chmod 4755 /usr/local/bin/my-tool
# Remove setuid
chmod u-s /usr/local/bin/my-tool
setgid (Set Group ID)
On executables, it works like setuid but for the group. More commonly used on directories — files created inside inherit the directory's group:
# Create a shared project directory
sudo mkdir /opt/team-project
sudo chown :developers /opt/team-project
sudo chmod 2775 /opt/team-project
# Now any file created inside gets the "developers" group
touch /opt/team-project/notes.txt
ls -l /opt/team-project/notes.txt
# -rw-rw-r-- 1 furuya developers 0 Mar 23 10:00 notes.txt
Without setgid, the file would get the creating user's primary group, breaking shared access.
# Verify setgid on a directory
ls -ld /opt/team-project
# drwxrwsr-x 2 root developers 4096 Mar 23 10:00 /opt/team-project
# ^ the 's' means setgid is active
Sticky bit
Prevents users from deleting files they don't own, even if they have write permission on the directory. /tmp is the classic example:
ls -ld /tmp
# drwxrwxrwt 15 root root 4096 Mar 23 10:00 /tmp
# ^ the 't' means sticky bit is active
Everyone can write to /tmp, but you can only delete your own files.
# Set sticky bit
chmod +t /opt/shared-uploads/
# Or with octal (1 prefix)
chmod 1777 /tmp
# Remove sticky bit
chmod -t /opt/shared-uploads/
Quick reference table
| Bit | Octal prefix | Symbolic | On files | On directories |
|---|---|---|---|---|
| setuid | 4 | u+s | Runs as file owner | (no effect) |
| setgid | 2 | g+s | Runs as file group | New files inherit directory group |
| sticky | 1 | +t | (no effect) | Only owners can delete their files |
Finding special permission files
Security audits often require finding setuid/setgid binaries:
# Find all setuid files
find / -perm -4000 -type f 2>/dev/null
# Find all setgid files
find / -perm -2000 -type f 2>/dev/null
# Find both
find / -perm /6000 -type f 2>/dev/null
I run these on every server I set up for 32blog infrastructure. Unexpected setuid binaries are a red flag.
umask: Controlling Default Permissions
Every time you create a file or directory, the system applies a umask — a mask that removes permissions from the defaults. The defaults are 666 for files and 777 for directories.
# Check your current umask
umask
# 0022
# With symbolic output
umask -S
# u=rwx,g=rx,o=rx
How umask calculation works
The umask subtracts permissions from the default:
| Default | Umask 022 | Result | |
|---|---|---|---|
| Files | 666 (rw-rw-rw-) | 022 | 644 (rw-r--r--) |
| Directories | 777 (rwxrwxrwx) | 022 | 755 (rwxr-xr-x) |
Common umask values:
| Umask | File result | Dir result | Use case |
|---|---|---|---|
022 | 644 | 755 | Default for most systems |
002 | 664 | 775 | Shared group development |
077 | 600 | 700 | Maximum privacy |
027 | 640 | 750 | Balanced security |
# Set umask for current session
umask 027
# Test it
touch test-file
mkdir test-dir
ls -l test-file # -rw-r----- (640)
ls -ld test-dir # drwxr-x--- (750)
Making umask persistent
The umask resets with each new shell session. To make it permanent, add it to your shell profile:
# For bash
echo "umask 027" >> ~/.bashrc
# For zsh
echo "umask 027" >> ~/.zshrc
# System-wide (all users)
echo "umask 027" >> /etc/profile
For system services, the shell profile umask doesn't apply. Set it in the systemd unit file with UMask=027 in the [Service] section instead.
Real-World Permission Patterns
Here are the patterns I actually use on production servers and development machines.
Web server setup (Nginx + deployment user)
# Create the web root
sudo mkdir -p /var/www/32blog
sudo chown -R deploy:www-data /var/www/32blog
# Set permissions
find /var/www/32blog -type d -exec chmod 2755 {} +
find /var/www/32blog -type f -exec chmod 644 {} +
# Directories where the app needs to write (uploads, cache)
chmod 2775 /var/www/32blog/.next/cache
The 2755 on directories sets the setgid bit — new files created by the deploy process will inherit the www-data group, so Nginx can always read them.
SSH key permissions
SSH is strict about permissions and will refuse to use keys if they're too open:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_ed25519 # Private key
chmod 644 ~/.ssh/id_ed25519.pub # Public key
chmod 600 ~/.ssh/authorized_keys
chmod 600 ~/.ssh/config
If you ever see WARNING: UNPROTECTED PRIVATE KEY FILE!, this is why.
Shared development directory
# Create shared project space
sudo mkdir -p /opt/projects/32blog
sudo chown -R :developers /opt/projects/32blog
sudo chmod -R 2775 /opt/projects/32blog
# Ensure all team members are in the group
sudo usermod -aG developers furuya
sudo usermod -aG developers colleague
The 2775 + setgid combo means: everyone in developers can read and write, and new files automatically belong to the developers group.
Docker and container permissions
# Fix common Docker volume permission issues
# When container runs as UID 1000 but host files are owned by root
sudo chown -R 1000:1000 ./docker-volumes/
# Dockerfile pattern for non-root user
# RUN useradd -r -u 1000 appuser && \
# chown -R appuser:appuser /app
Quick fix script for a Next.js deployment
#!/bin/bash
# fix-permissions.sh — Run after deploying 32blog
set -euo pipefail
WEBROOT="/var/www/32blog"
OWNER="deploy"
GROUP="www-data"
echo "Fixing ownership..."
sudo chown -R "${OWNER}:${GROUP}" "${WEBROOT}"
echo "Setting directory permissions..."
find "${WEBROOT}" -type d -exec chmod 2755 {} +
echo "Setting file permissions..."
find "${WEBROOT}" -type f -exec chmod 644 {} +
echo "Making scripts executable..."
find "${WEBROOT}/scripts" -name "*.sh" -exec chmod 755 {} +
echo "Done. Permissions fixed for ${WEBROOT}"
Auditing permissions
# Find world-writable files (security risk)
find /var/www -perm -o=w -type f 2>/dev/null
# Find files without an owner (orphaned — usually from deleted users)
find / -nouser -o -nogroup 2>/dev/null
# Find files changed in the last 24 hours (incident response)
find /var/www -mtime -1 -type f -ls
# List setuid/setgid binaries
find / -perm /6000 -type f -ls 2>/dev/null
FAQ
What does chmod 777 actually do, and why should I avoid it?
chmod 777 gives every user on the system full read, write, and execute access. It completely bypasses Linux's permission model. On a web server, any compromised process could modify your application code. The only legitimate use is /tmp (protected by the sticky bit). Instead of 777, figure out which user or group needs access and set permissions accordingly — chmod 775 with proper group ownership solves most "permission denied" problems.
What's the difference between chmod and chown?
chmod controls what can be done (read, write, execute). chown controls who owns the file (user and group). They work together: first set ownership with chown to establish identity, then set permissions with chmod to define capabilities. You can use chmod as the file owner, but chown typically requires root.
How do I fix "Permission denied" when running a script?
First check if the execute bit is set: ls -l script.sh. If you see -rw-r--r-- (no x), add it with chmod +x script.sh. If the script still fails, check the parent directories — you need execute permission on every directory in the path from / to the script.
Should I use octal (755) or symbolic (u=rwx,go=rx) mode?
Both produce identical results. Octal is faster to type and more common in documentation. Symbolic is more readable and safer for selective changes — chmod g+w file adds group write without affecting other permissions. Most sysadmins use octal for setting full permissions and symbolic for tweaking individual bits.
How do I check what permissions a file currently has?
ls -l file shows permissions in symbolic format. For octal, use stat -c "%a %n" file on Linux or stat -f "%Lp %N" file on macOS. For ACLs (access control lists beyond basic permissions), use getfacl file.
What does the 's' in permission output mean?
It indicates setuid (in owner position) or setgid (in group position). rwsr-xr-x means the file runs as the owner regardless of who executes it — used by system commands like passwd that need root access. On directories, setgid (rws in group position) makes new files inherit the directory's group.
Why does SSH complain about my key permissions?
SSH enforces strict permissions for security. Private keys must be 600 (owner read/write only), the .ssh directory must be 700, and authorized_keys must be 600. If any of these are too open, SSH refuses to use them. Run chmod 600 ~/.ssh/id_* and chmod 700 ~/.ssh to fix it.
When should I use ACLs instead of chmod?
When you need more than three permission categories (owner, group, others). Standard permissions can only assign one group to a file. ACLs (setfacl/getfacl) let you set different permissions for multiple specific users and groups. Common use case: a file needs read access for group A and read/write for group B. Standard permissions can't do this — ACLs can. Red Hat has a solid introduction to Linux ACLs if you want to dig deeper.
Wrapping Up
Linux permissions come down to two questions: "who owns this?" (chown) and "what can they do?" (chmod). The patterns you'll use 90% of the time:
- Web servers:
chown deploy:www-data, directories2755, files644 - SSH keys:
chmod 600private,644public,700directory - Shared directories:
chown :group,chmod 2775for group write with setgid - Scripts:
chmod +xto make executable
Start with the principle of least privilege — give the minimum permissions needed. When something breaks, ls -l and stat are your diagnostic tools. And when in doubt, find with -perm can hunt down permission problems across your entire filesystem.