Linux directory permissions look like noise the first time you see them. A string of letters and dashes, three groups of three, maybe a + or an @ at the end. After a while, they become second nature, but only because at some point you sat down and decoded what each character actually meant. This guide is the sitting-down-and-decoding part, written for people who have to use Linux at work and don’t want to keep guessing.
If you’ve been typing chmod 777 (the command that changes a file’s permissions, in this case making it world-readable, world-writable, and world-executable) on things that aren’t working, you’re not alone, and you’re also doing it wrong. The model underneath is older than most of the tools you use daily, and it pays to understand it. Once you do, the letters stop being noise and start being information.
The three groups, and the three bits
Every file and directory on a Linux system has an owner, a group, and a set of permissions. The owner is a single user, the group is a collection of users (an old Unix concept, the same one used to bill time on mainframes), and “other” is everyone who is neither the owner nor in the group. When you run ls -l, you see all of this in a single line.
The permissions for each of those three groups consist of three bits: read (r), write (w), and execute (x). Read means “look at the contents.” Write means “change the contents.” Execute means “run it as a program” (for files) or “enter it” (for directories). For directories specifically, execute has a counterintuitive meaning: it doesn’t mean “run this directory” (you can’t). It means “traverse” or “pass through.” A directory with read but no execute permission lets you list the files in it but not open them. A directory with execute but no read lets you open files you know the name of, but not list what’s there.
The classic mistake is setting a directory to r-- (read-only, no execute) and wondering why nothing works. The directory is “open” in the sense that you can see what’s inside, but every attempt to access a file inside it fails with “Permission denied.” If that has happened to you, the missing bit was almost always execute on a parent directory.
Reading the string
When you see something like -rwxr-xr--, you are looking at ten characters. The first is the file type: - for a regular file, d for a directory, l for a symbolic link (a pointer that refers to another file by name), c for a character device, b for a block device, p for a named pipe, and s for a socket. The next nine characters are the three groups of three bits.
The first group of three is for the owner. The second is for the group. The third is for everyone else. So -rwxr-xr-- decodes as: a regular file, owner can read/write/execute, group can read/execute, others can read only. That file is executable by its owner and group, but writable only by the owner. The other users on the system can read it but not change it or run it.
The execute bit on files is what makes scripts work. A Python file with the shebang #!/usr/bin/env python (the first line of a script that tells the operating system which interpreter to use) needs to be executable for you to run it directly as ./script.py. Without the execute bit, you’d have to invoke Python explicitly: python script.py. Same code, but the permission is what makes the difference between “is a program” and “is a text file.”
Numeric mode: the version you’ll memorize
The letters are great for reading. For setting, most people use numbers, because numbers are faster to type and harder to get wrong. The mapping is simple: read is 4, write is 2, execute is 1, and you add them together.
7means read + write + execute (4 + 2 + 1)6means read + write (4 + 2)5means read + execute (4 + 1)4means read only3means write + execute (2 + 1)2means write only1means execute only0means nothing
A three-digit mode like 755 is owner-group-other. So 755 means: owner can do anything (7), group can read and execute (5), others can read and execute (5). That’s a typical setting for a directory or a script that everyone needs to be able to run, but only the owner can change. 644 is the typical setting for a regular file: owner can write, everyone can read. Web servers love 644 for static files.
The numbers you’re going to type over and over are:
644for regular files you want to be readable755for scripts and directories you want to be traversable700for directories that should be private to a single user (typical home directory)600for private files like SSH keys (read/write for owner only, no permissions for anyone else)400for files that should be readable but never modified
You’ll find yourself needing 600 for SSH private keys because OpenSSH (the most common SSH client and server) refuses to use a key with overly permissive bits. That’s a safety feature, and the error message (“Permissions 0644 for ‘/home/you/.ssh/id_rsa’ are too open”) is the most common reason people learn about octal mode (the way numbers like 644, 755 are read in permissions: each digit represents three bits, and each bit is a power of two: 4 for read, 2 for write, 1 for execute) for the first time.
The special bits: setuid, setgid, and the sticky bit
There are three extra permission bits beyond the basic nine, and they change the meaning of the file in ways that aren’t obvious from the letters alone.
setuid (set user ID) on an executable file makes it run as the file’s owner, not as the user who invoked it. The classic example is passwd (the command to change your password), which is owned by root (the superuser account, which can do anything on the system) and has the setuid bit set, so any user can run it and have it write to the system’s password database. You’ll see this as an s in the owner’s execute position: -rwsr-xr-x. The lowercase s means the bit is set and the execute bit is also set. An uppercase S would mean setuid is set but the execute bit is not, which is a broken configuration that does nothing useful.
setgid (set group ID) on a directory means new files created inside it inherit the directory’s group, not the user’s primary group. This is a real workflow tool for shared project directories. Set it on a directory, and everyone working in that directory has their files appear with the same group ownership, so the group’s read/write permissions keep working without per-user fiddling.
setgid on a regular file, like setuid, makes it run with the file’s group identity. It’s less common than setuid.
The sticky bit on a directory means only the owner of a file (or root) can delete or rename it, even if other users have write permission on the directory. This is what makes /tmp work: anyone can create files there, but you can’t delete someone else’s files in /tmp. The bit shows up as a t in the others’ execute position: drwxrwxrwt. Lowercase t is sticky plus execute. Uppercase T would be sticky without execute, which is a broken state.
Common scenarios you’ll actually encounter
A few real-world permission scenarios that come up again and again:
When a web application can’t write to a directory it needs, the fix is almost always ownership or group, not mode. Changing the mode to 777 will make the symptom go away, but you’ve also made the directory writable by every user on the system. The right answer is usually to put the web server user (often www-data on Debian/Ubuntu, nginx or apache elsewhere) into the application’s group, set the group write bit, and use chown (change owner) to set the group ownership. chown -R :www-data /var/www/app and chmod -R g+rwX /var/www/app is a common pair of commands for this case. The capital X in chmod is special: it adds execute only to directories and to files that already have execute for someone. It’s a way to make a tree traversable without making every file executable.
If a script you just wrote won’t run with ./script.sh: Permission denied, the cause is the missing execute bit. The fix is chmod +x script.sh. Don’t be tempted to bash script.sh as a workaround forever; you’ll forget which scripts are executable and which aren’t, and the next person who tries to use them will hit the same wall.
If a shared dropbox-style directory behaves strangely (files appear with the wrong group, or collaborators can’t edit them), the cause is almost always the setgid bit being missing on the directory. chmod g+s shared/ is usually the fix.
When SSH complains about a private key being too permissive, chmod 600 ~/.ssh/id_rsa is the right answer. The error is annoying but correct: a key with 644 permissions means any other user on the system could read it, and that defeats the entire purpose of a private key.
The umask and the defaults
Every Linux process has a umask (file mode creation mask, a number that gets subtracted from the default permissions when new files are created), a three-digit number that determines the default permissions for new files and directories. The default on most systems is 022, which means new files come out as 644 and new directories as 755. That’s why you don’t have to set permissions every time you create a file.
If you want different defaults for yourself, set the umask in your shell’s startup file. umask 077 is a common choice for laptops and shared machines: it makes new files 600 and new directories 700, so nothing you create is readable by other users by default. The trade-off is that you have to be more deliberate about making things readable for collaboration, but the security benefit is real.
Trade-offs
The classic anti-pattern is chmod 777. Don’t do it. Yes, it fixes the immediate problem. Yes, you can move on with your day. But you’ve now made the file writable by every user and every process on the system. On a single-user laptop, the risk is small. On a shared server, it’s a security hole someone will eventually find. There’s almost always a more specific fix that grants the right access to the right user or group.
The other anti-pattern is the recursive chmod -R 777 / command. Please don’t. There are documented cases of people breaking their systems so thoroughly that they had to reinstall. The recursive flag is real. It walks every file and every directory under the path you give it. If that path is / or /var or even just the wrong subdirectory, you can take down a system in a single command.
Setuid is a powerful feature and a security risk. The setuid bit on a program owned by root means that any user can run that program with root privileges. If the program has a vulnerability, that vulnerability is now exploitable by any user. The setuid bit is heavily audited in well-known system binaries, but writing your own setuid program is a fast way to introduce a privilege-escalation bug. The advice is simple: don’t write setuid programs. Use sudo (a tool that lets specific users run specific commands as root, with a log of what they did) instead.
The numeric mode is concise and fast, but it hides information. chmod u+x script.sh is more verbose than chmod 755 script.sh, but the verbose form is self-documenting in a script or a README. For interactive work, the numbers are fine. For documentation, the letters are better.
When to learn more, and when to stop here
For most people, the operating theory of permissions fits in a few minutes: three groups, three bits, numeric mode from the addition, and the three special bits when you encounter them. That’s enough to handle ninety percent of what you’ll see in real life. The other ten percent involves ACLs (Access Control Lists, a finer-grained permissions system that lets you grant specific permissions to specific users beyond the owner/group/other model) and SELinux/AppArmor (mandatory access control systems that add an extra layer of restrictions on top of the basic permissions). If you ever hit a system that has those enabled, expect a separate learning curve. They have their own vocabulary, their own debugging tools, and their own way of saying “permission denied” when the standard ls -l looks fine.
The single most useful habit is reading ls -l output without translating it back to numbers in your head. If you see -rw-r-----, you should immediately know: owner can read and write, group can read, others have nothing. If that takes you more than a second, practice for a few minutes. After a while, the string becomes the source of truth, and the numbers become the shorthand.