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
chmodits mode argument and the list of target paths. chmodvalidates the mode string — either three (or four) octal digits, or one or more comma-separated symbolic clauses likeu+x.- For each target,
chmodcalls thechmod(2)(orfchmodat(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_FOWNERcapability (whichroot, and thereforesudo, 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 —
chmodchanges metadata, not content. - With
-R,chmodwalks 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
777and 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 (
755vs644) instead of one blanketchmod -R. - Verify the result with
ls -l(orls -ldfor a directory) after every non-trivialchmod. - 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 chmoddeliberately 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.shcontaining 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 withls -l— you should seerwx------. - You have a directory
~/shared_reportsthat group members should be able to read and list, but not modify, while everyone else should have no access at all. Work out the singlechmodcommand (octal or symbolic) that sets this on the directory. - You cloned a project into
~/projects/apiand every file, including.jsonconfig files and images, is currently executable. Using twofindcommands, restore directories to755and regular files to644, then re-add the execute bit only to the project’s actual entry-point script.
Summary
chmodchanges 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 thechmod(2)system call. chmodupdates only the inode’s mode and ctime — it never touches file content, mtime, or atime.- Avoid
777and blanket recursivechmod -Ron mixed trees; set directories and files to different, deliberate modes.
