Linux Basic — chmod & chown in Scripts
chmod & chown in Scripts
chmod (change mode) and chown (change owner) are how you set file permissions and ownership from a script. Both have two surface idioms — symbolic and numeric — and both have a more polished alternative (install) when you’re deploying a fresh file.
Numeric (Octal) Modes
Each digit is the sum of read (4), write (2), and execute (1) bits for owner, group, others:
| Digit | Means |
|---|---|
| 7 | rwx |
| 6 | rw- |
| 5 | r-x |
| 4 | r– |
| 3 | -wx |
| 2 | -w- |
| 1 | –x |
| 0 | — |
chmod 0644 file.txt # rw-r--r-- (typical regular file)
chmod 0755 script.sh # rwxr-xr-x (typical executable)
chmod 0600 secret.key # rw------- (only owner can read)
chmod 0700 mydir # rwx------ (private directory)
The leading 0 is the setuid/setgid/sticky digit:
| Digit | Means |
|---|---|
| 4 | setuid |
| 2 | setgid |
| 1 | sticky |
chmod 2755 /usr/local/bin/tool # setgid + rwxr-xr-x
chmod 1777 /tmp # sticky + rwxrwxrwx (only owners can delete their files)
In scripts, prefer numeric — it’s unambiguous and matches the output of ls -l when you read it back.
Symbolic Modes
chmod u+x script.sh # add execute for user (owner)
chmod g-w file.txt # remove write for group
chmod o= secret.key # remove ALL bits for others (note the trailing =)
chmod a+r doc.txt # add read for all (a = u+g+o)
chmod u=rwx,go=rx dir # SET (not add) user=rwx, group/other=rx
Use symbolic when:
- You want to add or remove one bit without touching the others
- The change is conditional or partial
# Make every .sh file executable, without disturbing other bits
chmod a+x ./*.sh
# Strip world-write on the whole tree
chmod -R o-w /var/data
Numeric vs Symbolic — Quick Reference
| Operation | Numeric | Symbolic |
|---|---|---|
| rw-r–r– (typical file) | 0644 |
u=rw,go=r |
| rwxr-xr-x (typical exec) | 0755 |
u=rwx,go=rx |
| rw——- (private) | 0600 |
u=rw,go= |
add +x to user only |
chmod $(stat -c%a f)… (ugly) |
u+x |
remove w from group |
(ugly) | g-w |
In short: numeric for SET, symbolic for ADD/REMOVE.
Recursive Operations — -R
chmod -R 0644 /var/www/docs # set all to 0644
chmod -R u+rw,g+r /var/www/docs # add user rw, group r — keep existing executes
The classic foot-gun:
# ❌ Strips execute from directories — you can no longer enter them!
chmod -R 0644 /var/www/site
ls /var/www/site
# Permission denied (cannot search the directory)
# ✅ Treat files and directories differently with find
find /var/www/site -type d -exec chmod 0755 {} \;
find /var/www/site -type f -exec chmod 0644 {} \;
There’s also the symbolic capital-X which means “execute, but only for dirs and files that already have any execute bit”:
chmod -R u=rwX,go=rX /var/www/site
# capital X — dirs get x, files stay non-executable
chown — Ownership
chown alice file.txt # change user only
chown alice:devs file.txt # change user AND group
chown :devs file.txt # change group only
chown -R alice:devs /srv/app # recursive
Important: chown typically requires root (only root can change a file’s owner to someone else). Group changes are allowed for the owner if they belong to the new group.
chgrp is a sibling that only changes the group — but chown :group does the same thing in fewer commands.
umask — Default Permissions for New Files
umask is the bits to subtract from the default mode of any file you create:
| umask | New file mode | New dir mode | Meaning |
|---|---|---|---|
022 (typical) |
0644 (rw-r–r–) | 0755 (rwxr-xr-x) | Group + other can read |
027 (stricter) |
0640 (rw-r—–) | 0750 (rwxr-x—) | Group can read; others nothing |
077 (private) |
0600 (rw——-) | 0700 (rwx——) | Only owner |
umask 077 # set restrictive umask for this script
touch private.txt
ls -l private.txt
# -rw------- 1 alice alice 0 ... private.txt
Set umask near the top of any script that creates files with sensitive content.
install — The Right Tool for Deploying a File
When you’re putting a file into a final location with specific mode and owner, install(1) does the whole job in one atomic step (copy + chmod + chown):
sudo install -o root -g root -m 0644 ./myapp.conf /etc/myapp.conf
sudo install -o root -g root -m 0755 ./myapp /usr/local/bin/myapp
Compared to cp + chmod + chown separately:
installis atomic per file (file appears with the right perms, never briefly wrong)- One command, less to forget
- Creates parent dirs with
install -d
sudo install -d -m 0755 /etc/myapp.d
sudo install -m 0644 ./*.conf /etc/myapp.d/
Use install in any deploy script — it’s what make install actually calls.
Patterns: Creating an Executable Script From Within a Script
out=/usr/local/bin/myhelper
cat > "$out" <<'EOF'
#!/usr/bin/env bash
echo "I am a generated script"
EOF
chmod 0755 "$out"
Or, atomically:
tmp=$(mktemp)
cat > "$tmp" <<'EOF'
#!/usr/bin/env bash
echo "hi"
EOF
sudo install -m 0755 "$tmp" /usr/local/bin/myhelper
rm -- "$tmp"
When You Need sudo
Both chmod and chown will fail silently with a confusing error if you don’t have permission. Check upfront:
if [[ $EUID -ne 0 ]]; then
echo "This script must run as root (use sudo)" >&2
exit 1
fi
Covered in the next page.
Common Mistakes
| ❌ Wrong | ✅ Right | Issue |
|---|---|---|
chmod -R 0644 site/ |
find ... -type d -exec chmod 0755 + -type f -exec chmod 0644 |
strips exec from dirs |
chmod 644 file (no leading 0) |
chmod 0644 file |
works but loses ability to use the setuid digit; be consistent |
chown user.group (with dot) |
chown user:group |
dot syntax is deprecated and breaks with usernames containing dots |
cp file /etc/ && chmod 0644 /etc/file |
install -m 0644 file /etc/ |
non-atomic — brief wrong-mode window |
umask 022 in a script that writes secrets |
umask 077 |
leaks readable secrets to group/other |
Next → Checking for Root