Every developer has done it. Your deployment script fails with "Permission denied." You are tired. It is 11 PM. You type chmod 777 /var/www/html and everything works. You go to bed. Three months later, your server is compromised because every user on the system could write to your web root, and an uploaded PHP file gave an attacker a shell.
I have seen this exact sequence play out on production systems more than once. The fix is not complicated -- it is understanding what those three digits actually mean and picking the right ones.
The permission model
Every file and directory on a Unix system has three sets of permissions, assigned to three classes of users:
- Owner (u): the user who owns the file
- Group (g): users who belong to the file's group
- Others (o): everyone else
Each class gets three permission bits:
- Read (r = 4): view file contents, or list directory contents
- Write (w = 2): modify the file, or create/delete files in a directory
-
Execute (x = 1): run the file as a program, or enter a directory with
cd
These three bits are represented as a single octal digit (0-7). Three digits, one per class, give you the full permission set.
Permission Binary Octal
rwx 111 7
rw- 110 6
r-x 101 5
r-- 100 4
-wx 011 3
-w- 010 2
--x 001 1
--- 000 0
So chmod 755 script.sh means:
- Owner: rwx (7) -- read, write, execute
- Group: r-x (5) -- read and execute
- Others: r-x (5) -- read and execute
And chmod 644 index.html means:
- Owner: rw- (6) -- read and write
- Group: r-- (4) -- read only
- Others: r-- (4) -- read only
The symbolic notation
Octal is compact but hard to read. The symbolic notation uses letters and operators:
# Add execute for owner
chmod u+x script.sh
# Remove write for group and others
chmod go-w config.yaml
# Set exact permissions for all classes
chmod u=rwx,g=rx,o=rx script.sh # equivalent to 755
The operators are:
-
+adds permissions -
-removes permissions -
=sets exact permissions (removes anything not specified)
I prefer symbolic notation for one-off changes (chmod g+w file) and octal for setting permissions from scratch (chmod 644 file). Use whichever you can read correctly at a glance. Misreading permissions is worse than typing a few extra characters.
Directory permissions are different
This is where most people get confused. The same bits mean different things for files and directories.
Read on a directory lets you list its contents with ls. Without it, you cannot see what files exist inside, even if you know their names and have permission on the files themselves.
Write on a directory lets you create, rename, or delete files inside it. This is critical: write permission on a directory lets you delete any file in it, regardless of the file's own permissions. The file's permissions control writing to the file. The directory's permissions control the file's existence.
Execute on a directory lets you cd into it and access files inside it by name. Without execute, you cannot traverse the directory, even if you have read permission. A directory with read but not execute (4) lets you list file names but not access any of them -- a confusing state that is rarely intentional.
Common directory permissions:
-
755: standard for web directories, application directories -
700: private directories (home directories, SSH) -
1777: shared temporary directories (the leading 1 is the sticky bit)
The sticky bit, setuid, and setgid
Beyond the basic nine bits, three special bits modify behavior:
Sticky bit (1000 or +t): on a directory, only the file owner, directory owner, or root can delete files inside it. This is why /tmp is typically 1777 -- everyone can create temp files, but you can only delete your own.
ls -ld /tmp
drwxrwxrwt 15 root root 4096 Mar 20 10:00 /tmp
# ^ the 't' indicates the sticky bit
Setuid (4000 or u+s): when set on an executable, it runs with the permissions of the file's owner rather than the user who invoked it. The passwd command uses this -- it runs as root so it can modify /etc/shadow, even when a regular user invokes it.
Setgid (2000 or g+s): on an executable, it runs with the file's group. On a directory, new files created inside inherit the directory's group instead of the creating user's primary group. This is invaluable for shared project directories.
# Create a shared project directory
mkdir /opt/project
chgrp developers /opt/project
chmod 2775 /opt/project
# Now all files created inside will belong to the 'developers' group
Common mistakes
Using 777 anywhere. There is almost never a legitimate reason. If your web server needs to write to a directory, make the web server user the owner and use 755 or 750. If a shared directory needs group write access, use 775 with the setgid bit (2775).
Forgetting the umask. When you create a file, its permissions are not what you set -- they are modified by the umask. A umask of 022 (the common default) strips write from group and others. So a file created with default permissions gets 644, and a directory gets 755. If files are showing up with wrong permissions, check umask before blaming the application.
Recursive chmod on mixed files and directories. chmod -R 755 /var/www gives execute permission to every file, including HTML, CSS, and PHP files that should not be executable. Use find to apply different permissions:
# 755 for directories, 644 for files
find /var/www -type d -exec chmod 755 {} \;
find /var/www -type f -exec chmod 644 {} \;
Ignoring ACLs. On modern Linux systems, POSIX ACLs provide finer-grained control than the basic owner/group/others model. If standard permissions cannot express what you need, setfacl and getfacl let you grant specific permissions to specific users or groups without changing ownership.
Not checking parent directory permissions. A file with 644 permissions is inaccessible if any parent directory lacks execute permission for the accessing user. Permissions are checked at every level of the path.
Getting permissions right
When setting up a new server or deployment, I follow a simple principle: start restrictive and loosen only what is necessary. Default to 750 for directories and 640 for files. Make the application user the owner. Put trusted users in the group. Grant others access only if the content is genuinely public.
For quick permission calculations -- especially when you need to convert between octal and symbolic notation, or you are setting up setuid/setgid/sticky bits and want to verify you have the right number -- I keep a chmod calculator at zovo.one/free-tools/chmod-calculator that shows you exactly what each bit means.
Permissions are one of those things you set up once and forget. Make sure the "once" is correct. The cost of getting it wrong is either a broken deployment or a security hole, and neither is fun to debug at 11 PM.
I'm Michael Lip. I build free developer tools at zovo.one. 350+ tools, all private, all free.
Top comments (0)