DevToolbox
All articles

Linux chmod Permissions Explained: Octal, Symbolic, and Real-World Examples

· 8 min read

File permissions are one of the first things that trips up developers new to Linux and Unix-like systems. When you accidentally lock yourself out of a script, find that your web server cannot read its own files, or realise you have left SSH private keys world-readable, you are dealing with a permissions problem. Understanding chmod from first principles will save you hours of frustration and prevent real security vulnerabilities.

Why File Permissions Matter

Linux is a multi-user operating system. Multiple users — and multiple system processes running as different users — can be active on the same machine simultaneously. Without a permission system, any process could read, modify, or delete any file on the system. The permission model is the fundamental mechanism that prevents this.

Even on a single-user server, permissions matter because web servers, databases, and application processes typically run as their own dedicated system users. Granting these processes only the access they need — and nothing more — limits the blast radius if they are ever compromised.

The Permission Model: User, Group, Other

Every file and directory in Linux has three sets of permissions, one for each of three categories of user:

  • User (u): The owner of the file. Typically the user who created it.
  • Group (g): A named group of users. The file has an associated group, and all members of that group share the group permissions.
  • Other (o): Everyone else — any user on the system who is not the owner and is not in the file's group.

Within each category, three types of permission can be granted or denied:

  • Read (r): For files: view the contents. For directories: list the directory's contents with ls.
  • Write (w): For files: modify or delete the file. For directories: create, rename, or delete files within the directory.
  • Execute (x): For files: run the file as a program. For directories: enter the directory with cd and access files within it (even if you cannot list them).

The execute bit on directories deserves particular attention. A directory with read permission but no execute permission lets you see the names of files inside but not access them. A directory with execute permission but no read permission lets you access files inside if you know their exact names but does not let you list them. In practice, directories almost always have both or neither.

Reading ls -l Output

The ls -l command shows permissions in a ten-character string. Here is how to decode it:

drwxr-xr-x  2  alice  developers  4096  May 26 10:00  project/
-rw-r--r--  1  alice  developers  1234  May 26 10:01  README.md
-rwxr-x---  1  alice  developers   512  May 26 10:02  deploy.sh
lrwxrwxrwx  1  alice  developers    11  May 26 10:03  latest -> release/v2

Breaking down the first character:

  • - — regular file
  • d — directory
  • l — symbolic link
  • c — character device
  • b — block device

The next nine characters are three groups of three, representing user / group / other permissions:

d  rwx  r-x  r-x
│   │    │    │
│   │    │    └── other: read + execute, no write
│   │    └─────── group: read + execute, no write
│   └──────────── user:  read + write + execute
└──────────────── type: directory

A - in a permission position means that permission is absent. So rw-r--r-- means the user has read and write, the group has read only, and others have read only.

Octal Notation

Octal notation represents permissions as a three-digit (or four-digit, for special bits) number. Each digit is computed by summing the values of the permissions that are set:

Read    (r) = 4
Write   (w) = 2
Execute (x) = 1
None        = 0

You add together the values for the permissions you want to grant, yielding a number from 0 to 7 for each category. Then the three digits are placed in order: user digit, group digit, other digit.

rwx = 4 + 2 + 1 = 7
r-x = 4 + 0 + 1 = 5
r-- = 4 + 0 + 0 = 4
--- = 0 + 0 + 0 = 0

755 = rwx r-x r-x  (user: full, group: read+execute, other: read+execute)
644 = rw- r-- r--  (user: read+write, group: read, other: read)
600 = rw- --- ---  (user: read+write, group: none, other: none)
700 = rwx --- ---  (user: full, group: none, other: none)

Once you internalize the three-value table (4, 2, 1), you can calculate any octal permission in your head. A digit of 7 always means full access; 5 means read and execute; 6 means read and write; 4 means read only.

Symbolic Notation

Symbolic notation uses letters and operators to specify permission changes relative to the current state. It is more verbose than octal but more readable for making targeted changes without touching other permissions.

The syntax is: who operator permission

  • who: u (user), g (group), o (other), a (all three)
  • operator: + (add), - (remove), = (set exactly)
  • permission: r, w, x
chmod u+x script.sh        # add execute for the owner
chmod go-w config.yml      # remove write from group and other
chmod a+r public.html      # add read for all three categories
chmod u=rwx,g=rx,o=        # set exactly: user full, group r+x, other nothing
chmod +x deploy.sh         # shorthand: add execute for all (same as a+x)

Symbolic notation is particularly useful in scripts that need to make a targeted change without knowing or overwriting the existing permissions.

Common Permission Patterns

755 — Directories and executables

rwxr-xr-x. The owner has full control. Everyone else can read and execute (or enter, for directories) but cannot write. This is the standard permission for directories, web-served directories, and executable scripts or binaries that should be runnable by all users.

chmod 755 /var/www/html
chmod 755 /usr/local/bin/my-tool

644 — Regular files

rw-r--r--. The owner can read and write. Everyone else can only read. This is the standard permission for configuration files, HTML files, CSS files, and any file that should be readable by the web server but only writable by the owner.

chmod 644 /var/www/html/index.html
chmod 644 /etc/nginx/nginx.conf

600 — Private keys and secrets

rw-------. Only the owner can read and write. Group and other have no permissions at all. This is mandatory for SSH private keys. OpenSSH will refuse to use a private key file that has group or other read permissions, because a world-readable private key is no longer private.

chmod 600 ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_ed25519
chmod 600 .env

777 — When it is dangerous and why

rwxrwxrwx. Everyone on the system can read, write, and execute. On a shared server, any user or process can overwrite your files, inject malicious code, or read your secrets. On a web server, if the web server process can write to a directory that it also serves, an attacker who can upload a file can upload and execute arbitrary code.

chmod 777 is almost never the right answer. If you find yourself using it to "fix" a permission error, stop and diagnose the real problem — usually an incorrect owner (chown), an incorrect group, or a missing group membership.

700 — Private directories

rwx------. Only the owner can list, read, write, or enter the directory. Use this for directories containing sensitive data that should never be accessible by other users or system processes.

chmod 700 ~/.ssh
chmod 700 ~/private-keys

664 — Group-shared files

rw-rw-r--. The owner and group members can read and write; others can only read. Use this when multiple users in the same group need to collaborate on files — for example, a development team sharing files in a common directory.

chmod 664 /var/www/html/shared-config.json

Special Permission Bits

Beyond the standard user/group/other permissions, Linux supports three special bits that modify execution behaviour. These are represented as a fourth leading digit in octal notation (e.g., 4755, 2755, 1777).

Setuid (4xxx)

When set on an executable file, the program runs with the permissions of the file's owner, not the user who invoked it. The classic example is /usr/bin/passwd, which needs to modify /etc/shadow (owned by root) but must be executable by ordinary users. Its setuid bit allows it to do so.

chmod 4755 /usr/local/bin/special-tool
# ls -l output:
# -rwsr-xr-x  (note the 's' where execute would be for user)

The s in the user execute position indicates setuid is set and the execute bit is also set. An uppercase S means setuid is set but execute is not set — which is usually a mistake.

Setgid (2xxx)

On an executable, the program runs with the permissions of the file's group. On a directory, new files created inside the directory inherit the directory's group rather than the creating user's primary group. This is extremely useful for shared project directories.

chmod 2775 /var/www/html
# New files created here inherit the directory's group automatically

Sticky bit (1xxx)

On a directory, the sticky bit restricts deletion: only the file's owner, the directory's owner, or root can delete or rename files within the directory, even if other users have write permission on the directory. This is how /tmp works — anyone can create files there, but users can only delete their own files.

chmod 1777 /tmp
# ls -l output:
# drwxrwxrwt  (note the 't' at the end)

Recursive chmod

The -R flag applies the permission change recursively to a directory and all of its contents.

chmod -R 755 /var/www/html

Be careful with recursive chmod. Applying the same permissions to both files and directories is often wrong — files should typically be 644 while directories should be 755. A common pattern to apply different permissions to files and directories in a single command:

find /var/www/html -type f -exec chmod 644 {} \;
find /var/www/html -type d -exec chmod 755 {} \;

Common Mistakes

chmod 777 everything

When a permission error occurs, the tempting shortcut is chmod 777. Resist this. It creates security vulnerabilities, particularly on web servers. Debug the actual permission problem: check the file owner (ls -l), the running user of the process (ps aux), and the group membership (id username).

Forgetting execute on directories

Setting a directory to 644 (rw-r--r--) instead of 755 is a common mistake. Without the execute bit, users cannot cd into the directory or access any files inside it, even if the files themselves have permissive permissions. If you get "Permission denied" trying to access a directory, check its execute bit first.

Ignoring ownership

chmod changes permissions; chown changes ownership. If a file is owned by the wrong user, no combination of chmod settings will make it work correctly for the right user. Always verify both ownership and permissions when debugging.

Practical Examples

Setting up a web server directory

sudo chown -R www-data:www-data /var/www/html
sudo find /var/www/html -type d -exec chmod 755 {} \;
sudo find /var/www/html -type f -exec chmod 644 {} \;

This sets all directories to 755 and all files to 644, with the web server user (www-data on Debian/Ubuntu systems) as the owner. The web server can read everything; only root or the owner can write.

Securing SSH keys

chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_ed25519
chmod 644 ~/.ssh/id_ed25519.pub
chmod 600 ~/.ssh/authorized_keys
chmod 600 ~/.ssh/config

The .ssh directory must be 700. The private key must be 600. The public key can be 644. authorized_keys must be 600. If any of these are more permissive, ssh will print a warning and refuse to use the keys.

Setting up a shared project directory

sudo groupadd developers
sudo usermod -aG developers alice
sudo usermod -aG developers bob

sudo mkdir /projects/shared
sudo chown alice:developers /projects/shared
sudo chmod 2775 /projects/shared

The setgid bit (the leading 2) ensures that all new files and directories created inside /projects/shared automatically belong to the developers group, so all team members can access them without manual chown after every file creation.

Try it free
chmod Calculator
100% client-side · no signup · no upload
Open tool →