Linux Basic — chmod & chown in Scripts

4 minute read

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:

  • install is 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

Updated: