chmod was the command that finally made Unix permissions click for me — not when I memorized that 755 means “rwxr-xr-x,” but when I understood why it means that, as three octal digits each built from the same three bits. Once that clicked, symbolic mode (u+x, go-w) stopped being a separate thing to memorize and became an obvious alternate notation for the same underlying bits. This guide covers both notations, the special bits that trip people up (setuid, setgid, sticky), and the real-world patterns I use for securing files and directories.
What Is the chmod Command?
chmod stands for “change mode.” It changes the permission bits of files and directories:
$ chmod --version
chmod (GNU coreutils) 9.4
Copyright (C) 2023 Free Software Foundation, Inc.
Basic Syntax
chmod [OPTION]... MODE[,MODE]... FILE...
chmod [OPTION]... OCTAL-MODE FILE...
chmod [OPTION]... --reference=RFILE FILE...
How Permission Bits Actually Work
Every file’s mode is stored as a set of bits in its inode. The permission-relevant bits break into three triads — owner, group, other — each with three flags: read (r, value 4), write (w, value 2), and execute (x, value 1). Adding those values together per triad gives you the familiar octal digits: 7 = rwx, 6 = rw-, 5 = r-x, 4 = r–, and so on down to 0 = —.
I tested this directly:
$ chmod 755 src.txt
$ ls -l src.txt
-rwxr-xr-x 2 root root 6 Jul 31 01:36 src.txt
7 (owner) = rwx, 5 (group) = r-x, 5 (other) = r-x — exactly matching the string rwxr-xr-x.
For a regular file, read means “can view content,” write means “can modify content,” and execute means “can run it as a program or script.”
For a directory, the same bits mean something different and this is the part that trips people up most: read means “can list the names inside it” (ls), write means “can create/delete/rename entries inside it” (note: this is about the directory’s own listing, not the permissions of files inside it), and execute means “can enter it and access things inside by name” (cd into it, or open a file inside it by path). A directory with r-- but no x lets you see filenames but not actually access or stat any of them — a subtlety that catches people by surprise the first time they hit it.
Symbolic Mode Notation
Alongside octal, chmod accepts symbolic expressions built from:
- Who:
u(user/owner),g(group),o(other),a(all) - Operator:
+(add),-(remove),=(set exactly) - Permission:
r,w,x, plus special bitss(setuid/setgid) andt(sticky)
I tested a symbolic change directly:
$ chmod u+x src.txt
$ ls -l src.txt
-rwxr-xr-x 2 root root 6 Jul 31 01:36 src.txt
Symbolic mode’s real strength is that it can add or remove specific bits without needing to know or restate the current full mode — chmod g+w file adds group write regardless of whatever else was already set, while chmod 664 file requires you to specify the complete mode from scratch.
Special Permission Bits
Beyond the basic rwx triads, three additional bits matter a lot in real administration:
- setuid (4000, symbolic
u+s): on an executable, makes it run with the file owner’s privileges rather than the invoking user’s — the classic example is/usr/bin/passwd, which needs root privileges to edit/etc/shadoweven when a normal user runs it. - setgid (2000, symbolic
g+s): on an executable, runs with the file’s group privileges; on a directory, it makes new files created inside inherit the directory’s group instead of the creating user’s primary group — extremely useful for shared team directories. - sticky bit (1000, symbolic
+t): on a directory, restricts deletion/renaming of files inside it to their owner (or root), even if the directory itself is world-writable — this is exactly what makes/tmp(mode1777) safe for every user to share without being able to delete each other’s files.
Full List of Parameters
| Option | Long form | Description |
|---|---|---|
-c | --changes | Like verbose, but report only when a change is actually made |
-f | --silent, --quiet | Suppress most error messages |
-v | --verbose | Output a diagnostic for every file processed |
--no-preserve-root | Do not treat / specially (dangerous) | |
--preserve-root | Refuse to operate recursively on / | |
--reference=RFILE | Use RFILE’s mode instead of specifying MODE | |
-R | --recursive | Change files and directories recursively |
--help | Display help and exit | |
--version | Output version information and exit |
Recursive traversal control (used with -R):
| Option | Description |
|---|---|
-H | If argument is a symlink to a directory, traverse it |
-L | Traverse every symlink to a directory encountered |
-P | Never traverse symlinks (default) |
Practical Examples with Output
Setting an exact octal mode:
$ chmod 644 report.txt
$ ls -l report.txt
-rw-r--r-- 1 root root 0 Jul 31 01:36 report.txt
Making a script executable:
$ echo '#!/bin/bash
echo hi' > script.sh
$ chmod +x script.sh
$ ls -l script.sh
-rwxr-xr-x 1 root root 18 Jul 31 01:36 script.sh
$ ./script.sh
hi
Removing all access for others:
$ chmod o-rwx private.txt
Setting different permissions for each category at once, symbolically:
$ chmod u=rwx,g=rx,o= script.sh
$ ls -l script.sh
-rwxr-x--- 1 root root 18 Jul 31 01:36 script.sh
Recursive change across a directory tree:
$ chmod -R 750 /srv/app/data
Setting the setgid bit on a shared team directory:
$ chmod g+s /srv/team_shared
$ ls -ld /srv/team_shared
drwxr-sr-x 2 root root 4096 Jul 31 01:36 /srv/team_shared
The lowercase s in the group execute position confirms setgid is active — new files created inside will inherit the directory’s group.
Setting the sticky bit on a shared, world-writable directory:
$ chmod +t /srv/shared_uploads
$ ls -ld /srv/shared_uploads
drwxrwxrwt 2 root root 4096 Jul 31 01:36 /srv/shared_uploads
Copying mode from a reference file:
$ chmod --reference=template.conf myfile.conf
Common Use Cases
- Making scripts executable after writing or downloading them
- Locking down configuration files containing secrets (
chmod 600) - Securing SSH private keys, which SSH refuses to use if they’re group- or world-readable
- Setting up collaborative directories with setgid for consistent group inheritance
- Restricting sensitive log or data directories to a service account only
Shell Scripting and Automation
Securing an SSH key immediately after generation, matching what ssh-keygen expects:
ssh-keygen -t ed25519 -f "$HOME/.ssh/id_ed25519" -N ""
chmod 600 "$HOME/.ssh/id_ed25519"
chmod 644 "$HOME/.ssh/id_ed25519.pub"
Locking down a secrets file as part of a deployment script:
#!/usr/bin/env bash
set -euo pipefail
SECRETS="/etc/myapp/secrets.env"
chmod 600 -- "$SECRETS"
chown appuser:appuser -- "$SECRETS"
Setting up a shared team directory with setgid inheritance:
mkdir -p /srv/team_shared
chown root:devteam /srv/team_shared
chmod 2775 /srv/team_shared
Real-World System Administration Workflows
- SSH key security:
sshdandsshboth refuse to use private keys with overly permissive modes —chmod 600on the private key and700on~/.sshitself are non-negotiable. - Web server permissions: static files typically need
644(readable by the web server, not writable), while upload directories the application writes to need careful group ownership plus775or narrower, never777. - Cron and systemd service files: many services check the mode of their config or unit files and refuse to run with world-writable configs, as a defense against tampering.
- Shared development directories:
2775(setgid + rwxrwxr-x) on a team directory keeps new files consistently group-owned without every developer remembering tochgrpmanually.
Comparing chmod to Related Commands
chmodvschown:chmodcontrols what each category can do;chowncontrols who falls into the owner/group category in the first place. They’re almost always adjusted together.chmodvsumask:chmodchanges an existing file’s mode after creation;umasksets the default mask applied to new files and directories at creation time, before anychmodis applied.chmodvs ACLs (setfacl):chmod‘s three-triad model can only express one owner, one group, and “everyone else.” When you need finer-grained control — like read access for exactly two unrelated users — POSIX ACLs viasetfacl/getfacllayer additional rules on top of the base modechmodsets.
Troubleshooting Common chmod Issues
“chmod: changing permissions: Operation not permitted”: you need to own the file (or be root) to change its mode — ownership, not group membership, is what grants the right to chmod in the first place.
Script won’t execute despite chmod +x: check the shebang line (#!/bin/bash etc.) is correct and the first line of the file, and confirm the filesystem the script lives on wasn’t mounted with the noexec option (common on /tmp in hardened setups).
SSH refuses a key even after chmod 600: check the parent directory permissions too — ~/.ssh itself needs to be 700; SSH validates the whole path, not just the key file.
Directory listing works but files inside can’t be opened: classic case of read permission without execute permission on the directory — you need x on a directory to traverse into it and access anything inside by name, not just r to list names.
setuid bit disappears after copying or editing the file: many tools (including cp without -p, and any editor that rewrites the file rather than modifying it in place) don’t preserve setuid/setgid bits by default, and the kernel actively strips setuid on write by some file managers as a security precaution — reapply explicitly if you genuinely need it.
Performance Considerations
chmod -R on very large trees has the same per-inode syscall overhead as chown -R — one operation per file. For huge trees, find /path -exec chmod MODE {} + is roughly equivalent in performance, batching arguments the same way -R already does internally, so there’s little to gain from switching between them beyond filtering flexibility (find lets you target only files, only directories, or match by name pattern).
Security Implications
Getting chmod wrong is one of the most common ways real systems get compromised. World-writable directories without a sticky bit let any user delete or replace another user’s files. Overly permissive private keys get silently rejected by SSH but might be accepted by less careful tools. Setuid root binaries are a classic privilege-escalation target — every setuid root file on a system is worth auditing periodically with find / -perm -4000 -type f 2>/dev/null. Never use 777 as a lazy fix for a permission error; it almost always means the ownership is wrong, not that every user on the system genuinely needs full access.
Best Practices
- Use the minimum permission that gets the job done —
600for secrets,644for regular readable files,755for executables and traversable directories. - Use setgid (
g+s) on shared team directories instead of manually fixing group ownership after the fact. - Use the sticky bit (
+t) on any shared, world-writable directory. - Periodically audit for unexpected setuid/setgid binaries with
find / -perm -4000/-perm -2000. - Never resort to
chmod 777— fix the underlying ownership or group membership instead.
Compatibility Across Distributions
GNU chmod is identical in behavior across Debian/Ubuntu, RHEL/Fedora, Arch, and openSUSE. BusyBox chmod (Alpine, minimal containers) supports octal and basic symbolic modes plus -R but has a reduced option set, notably around --reference and full traversal control (-H/-L/-P) — check chmod --help in minimal images before relying on those.
Summary
chmod is really just an interface onto a small, elegant bit-field: three triads of read/write/execute, plus setuid, setgid, and sticky as extra flags layered on top. Once the octal-to-triad mapping and the directory-specific meaning of each bit are second nature, chmod stops being a command you look up and becomes a tool you reach for confidently — which matters, because getting permissions wrong (in either direction, too open or too closed) is one of the most common sources of both security incidents and confusing “permission denied” support tickets.
References
- GNU Coreutils Manual —
chmodinvocation: https://www.gnu.org/software/coreutils/manual/html_node/chmod-invocation.html - Linux man-pages project —
chmod(1): https://man7.org/linux/man-pages/man1/chmod.1.html chmod(2)system call and permission bit documentation: https://man7.org/linux/man-pages/man2/chmod.2.html- Ubuntu Manpage Repository: https://manpages.ubuntu.com/manpages/noble/en/man1/chmod.1.html