chmod: Changing Permissions

Every file and directory on Linux carries a permission mode that controls who can read it, write it, or execute it. The chmod command — short for "change mode" — is how you change that mode. It is one of the very first commands you need once you start writing shell scripts, hosting files, or managing a shared server, because a script that is not executable will not run, and a file that is too open can turn into a security hole.

Overview: How Permissions and chmod Work

Every file and directory has an owner (a user) and a group, plus a permission mode stored in its inode — the on-disk structure holding a file’s metadata: owner, group, size, timestamps, and pointers to its actual data blocks. The permission mode is nine bits, split into three sets of three:

  • Owner (user) — permissions for the user who owns the file
  • Group — permissions for members of the file’s group
  • Other — permissions for everyone else on the system

Each set has three possible permissions: read, write, and execute. What they mean depends on whether you’re looking at a regular file or a directory:

Permission Meaning on a file Meaning on a directory
r Read the file’s contents List the directory’s entries (e.g. with ls)
w Modify or truncate the file’s contents Create, rename, or delete entries inside it
x Execute the file as a program or script "Traverse" into the directory — cd into it, or open a file inside it by path

That last row surprises a lot of newcomers: a directory’s execute bit isn’t about "running" the directory, it’s what lets a process walk through it to reach files inside. Without x on a directory, you can’t cd into it or open a file inside it by path, even if you can technically list its contents.

You can see a file’s current mode with ls -l. The first character is the file type (- for a regular file, d for a directory, l for a symlink), and the next nine characters are the owner, group, and other permissions in order, three characters each. rwxr-xr-- reads as: owner can read/write/execute, group can read/execute, other can only read.

Only the file’s owner or the superuser (via sudo) can change its mode. Having write permission on the file’s contents is not enough — ownership is what the kernel checks. chmod can also set three special bits (setuid, setgid, and the sticky bit) used for privilege escalation and shared directories like /tmp; those get their own lesson, so this one sticks to the standard read/write/execute bits.

Syntax

chmod takes a mode and one or more files or directories:

chmod <mode> <file>...

The mode can be written two ways.

Octal (numeric) mode

Each permission triad becomes a single digit from 0-7, found by adding bit values: r = 4, w = 2, x = 1. Three digits in a row cover owner, group, other.

Digit Bits Meaning
7 rwx 4+2+1 — read, write, execute
6 rw- 4+2 — read, write
5 r-x 4+1 — read, execute
4 r– 4 — read only
0 no permissions

chmod 755 file sets rwxr-xr-x: owner gets everything, group and other get read and execute only. chmod 750 file sets rwxr-x---, which is exactly what chmod u=rwx,g=rx,o= file produces in symbolic mode — same result, different notation.

Symbolic mode

chmod <who><operator><permissions> <file>...
Part Values Meaning
who u, g, o, a user (owner), group, other, all
operator +, -, = add, remove, or set exactly these permissions
permissions r, w, x read, write, execute — combine freely, e.g. rw

Symbolic mode shines when you want to change one permission without disturbing the rest. chmod u+x file adds execute for the owner and leaves group and other exactly as they were — octal mode can’t do that without you first knowing (and rewriting) the whole current mode. Useful flags:

  • -R — apply recursively to a directory and everything inside it
  • -v — verbose; print a line for every file processed
  • -c — like -v, but only prints files whose mode actually changed

Examples

Example 1: Making a script executable

You wrote a deployment script, but the shell refuses to run it directly:

ls -l deploy.sh
-rw-r--r-- 1 ana ana 312 Aug  4 09:12 deploy.sh

The owner has read/write but no execute bit, so ./deploy.sh would fail. Add execute for the owner:

chmod u+x deploy.sh
ls -l deploy.sh
-rwxr--r-- 1 ana ana 312 Aug  4 09:12 deploy.sh

Now the owner’s triad reads rwx, and ./deploy.sh will run. Group and other were untouched — they still can’t execute it, which is fine for a private deploy script.

Example 2: Locking down a config directory

A directory holding database credentials should be readable and enterable by the app’s group, but completely closed to everyone else:

chmod 750 /var/www/app/config
ls -ld /var/www/app/config
drwxr-x--- 2 www-data www-data 4096 Aug  4 09:20 /var/www/app/config

750 breaks down as owner rwx (7), group r-x (5), other --- (0). The owning process can fully manage the directory, group members can list it and read/traverse into files, and everyone else gets nothing — not even a listing.

Example 3: Fixing permissions after extracting an archive

Archives don’t always preserve sane permissions. After unzipping a project, files and directories in ~/projects/webapp can end up with an inconsistent, overly permissive mode. Directories need to be enterable; most regular files should not be executable:

find ~/projects/webapp -type d -exec chmod 755 {} \;
find ~/projects/webapp -type f -exec chmod 644 {} \;
chmod +x ~/projects/webapp/deploy.sh
ls -l ~/projects/webapp
total 16
drwxr-xr-x 3 ana ana 4096 Aug  4 09:31 src
-rw-r--r-- 1 ana ana  892 Aug  4 09:31 package.json
-rwxr-xr-x 1 ana ana  341 Aug  4 09:31 deploy.sh

The first find sets every directory to 755 (traversable and listable by everyone, writable only by the owner). The second sets every regular file to 644 (readable by everyone, writable only by the owner, executable by no one). The final line re-adds execute specifically to the one file that actually needs it. This two-step pattern — directories get x, files don’t, except the ones that should run — is the standard way to sanitize a tree’s permissions.

How chmod Works Step by Step

When you run chmod, several things happen in sequence:

  • The shell parses the command line and hands chmod its mode argument and the list of target paths.
  • chmod validates the mode string — either three (or four) octal digits, or one or more comma-separated symbolic clauses like u+x.
  • For each target, chmod calls the chmod(2) (or fchmodat(2)) system call, passing the new mode bits to the kernel.
  • The kernel checks that the calling process’s effective user ID matches the file’s owner, or that the process has the CAP_FOWNER capability (which root, and therefore sudo, has). If neither is true, the call fails with a permission error and nothing changes.
  • If the check passes, the kernel writes the new mode directly into the file’s inode. No new inode is allocated and the file’s data is never touched.
  • The file’s ctime (metadata change time) is updated to reflect the change. Its mtime (content modification time) and atime (last access time) are left alone — chmod changes metadata, not content.
  • With -R, chmod walks the directory tree and repeats these steps for every entry it finds, by default without following symbolic links.

Common Mistakes

Mistake 1: Reaching for 777

When a permission error is annoying, it’s tempting to nuke it:

chmod 777 deploy.sh

This makes the file readable, writable, and executable by literally everyone on the system, including other users’ processes. For a script that runs with your privileges, or a file with any sensitive content, this is a real security hole, not just sloppy style. Grant only what’s needed:

chmod 750 deploy.sh

That gives the owner full control and the group read/execute, while other gets nothing — almost always what you actually want.

Mistake 2: Forgetting to chmod before running a script

A freshly created or downloaded script is not executable by default:

./deploy.sh
-bash: ./deploy.sh: Permission denied

The file exists and is readable, but the execute bit is off, so the kernel refuses to run it as a program. Set the execute bit first:

chmod +x deploy.sh
./deploy.sh

Mistake 3: Blanket recursive chmod on a whole project tree

It’s common to see people "fix" permission errors on a directory with one broad stroke:

chmod -R 755 ~/projects/webapp

This looks harmless but it sets the execute bit on every file in the tree, including data files, images, .env files, and source code that should never be marked executable. It’s misleading (an executable-looking config.json confuses tools and reviewers) and can even be a minor security smell if the tree is ever served or scanned for executables. Treat files and directories separately, as in Example 3:

find ~/projects/webapp -type d -exec chmod 755 {} \; && find ~/projects/webapp -type f -exec chmod 644 {} \;

Best Practices

  • Grant the minimum permissions that let the job get done — avoid 777 and world-writable files almost entirely.
  • Use octal mode (chmod 644) when you’re setting an exact, known mode; use symbolic mode (chmod g+w) when you’re adjusting one bit without disturbing the rest.
  • When recursing over a mixed tree, set directories and files separately (755 vs 644) instead of one blanket chmod -R.
  • Verify the result with ls -l (or ls -ld for a directory) after every non-trivial chmod.
  • Prefer group permissions over "other" permissions for sharing access among trusted collaborators — it’s easier to audit who’s in a group than to reason about every user on the box.
  • Never make a script world-writable; a writable, executable file that other users can edit is a direct path to running someone else’s code as you.
  • Use sudo chmod deliberately and sparingly on files you don’t own — changing permissions on system files can break services or open security gaps.

Practice Exercises

  • Create a file named greet.sh containing a one-line script that prints a greeting. Make it executable for the owner only (not group, not other), then run it with ./greet.sh. Check your work with ls -l — you should see rwx------.
  • You have a directory ~/shared_reports that group members should be able to read and list, but not modify, while everyone else should have no access at all. Work out the single chmod command (octal or symbolic) that sets this on the directory.
  • You cloned a project into ~/projects/api and every file, including .json config files and images, is currently executable. Using two find commands, restore directories to 755 and regular files to 644, then re-add the execute bit only to the project’s actual entry-point script.

Summary

  • chmod changes a file or directory’s permission mode — nine bits split into owner, group, and other, each with read, write, and execute.
  • Execute means "run as a program" on a file, but "traverse into" on a directory — a very common point of confusion.
  • Octal mode (chmod 755 file) sets an exact mode in one step; symbolic mode (chmod u+x file) adjusts specific bits without touching the rest.
  • Only the file’s owner or root (via sudo) can change its permissions; the kernel enforces this at the chmod(2) system call.
  • chmod updates only the inode’s mode and ctime — it never touches file content, mtime, or atime.
  • Avoid 777 and blanket recursive chmod -R on mixed trees; set directories and files to different, deliberate modes.