- Linux checks permissions in order: are you the owner, then are you in the group, then everyone else. The first match wins and it stops there.
- Because the first match wins, a file set to 604 denies the group read access while granting it to everyone else.
- Every user has one primary group, which owns the files they create, plus any number of secondary groups that grant access.
- umask subtracts from the default permissions on new files. 022 gives 644 files and 755 directories.
- setgid on a directory makes new files inherit its group, which is what keeps a shared document root consistent.
How does Linux decide who can access a file?
Linux checks three things in a fixed order. Are you the owner? If yes, the owner permissions apply and the check stops. If not, are you in the owning group? If yes, the group permissions apply and the check stops. Otherwise the other permissions apply. The first match wins, and nothing later can grant more.
The first match wins, which produces one genuinely odd result
A file set to 604 (owner read-write, group nothing, others read) means a member of the group cannot read it, while a complete stranger can. Linux matched on group, found no permission, and stopped. It never falls through to the other bits. This trips people up the first time they see it.
$ ls -l /var/www/example.com/wp-config.php
-rw-r----- 1 deploy www-data 3021 Jul 29 09:14 wp-config.php
# - rw- r-- ---
# | | | |
# | | | +-- others: nothing
# | | +------------- group www-data: read
# | +------------------------ owner deploy: read and write
# +----------------------------- file type
# That is 640. Exactly what a config file holding a password should be.What is the difference between a user and a UID?
The username is for humans. The kernel only ever deals in numeric user IDs. Everything about ownership and access is decided on the number, and the name is looked up from /etc/passwd for display. That distinction matters more than it sounds when you move files between machines.
# Your numeric identity, and every group you are in
id
# uid=1001(deploy) gid=1001(deploy) groups=1001(deploy),33(www-data)
# Show numeric IDs instead of names
ls -ln /var/www/example.com
# Files whose owner has no matching account (orphaned UIDs)
sudo find /var/www -nouser -o -nogroup 2>/dev/nullThis is why tar archives change ownership
A tar archive stores numeric IDs. Restore it on a server where UID 1001 belongs to a different person and the files now belong to that person. Use tar --numeric-owner when you want the numbers preserved deliberately, and always check ownership after a cross-server restore.
| UID range | Purpose |
|---|---|
| 0 | root: every permission check is skipped entirely |
| 1 to 999 | Service accounts: www-data, mysql, sshd |
| 1000 and above | Human accounts |
| 65534 | nobody: the deliberately powerless fallback |
root is not privileged because of its permissions. The kernel simply does not check them for UID 0.
How do primary and secondary groups differ?
Every user has exactly one primary group, recorded in /etc/passwd, and it is the group applied to any file they create. Secondary groups, recorded in /etc/group, grant access to things but never affect ownership of anything new.
$ id deploy
uid=1001(deploy) gid=1001(deploy) groups=1001(deploy),33(www-data),1005(webdev)
# ^^^^^^^^^^^^^^^^ primary ^^^^^^^^^^^^^^^^^^^^^ secondary
# deploy is in www-data, so it can READ what www-data can read.
# But new files it creates are owned by group deploy, not www-data,
# so the web server cannot read them.
$ sudo -u deploy touch /var/www/example.com/new.php
$ ls -l /var/www/example.com/new.php
-rw-r--r-- 1 deploy deploy 0 Jul 29 10:02 new.php
# ^^^^^^ not www-data: this is the classic surpriseThere are two fixes. Change the user's primary group, which affects everything they create everywhere, or set the setgid bit on the directory, which affects only that tree. The second is almost always the right one.
# Set the group on the tree, then make it inherit
sudo chgrp -R www-data /var/www/example.com
sudo find /var/www/example.com -type d -exec chmod g+s {} \;
# Now anything created inside belongs to www-data automatically
sudo -u deploy touch /var/www/example.com/another.php
ls -l /var/www/example.com/another.php
# -rw-r--r-- 1 deploy www-data 0 Jul 29 10:05 another.phpdrwxr-sr-x is setgid. It survives, so the arrangement holds without anyone maintaining it.How do chmod and chown actually work?
chmod changes what can be done. chown changes who it belongs to. chmod accepts either octal numbers, which set everything at once, or symbolic notation, which adjusts one thing and leaves the rest alone.
# Octal: sets all three sets at once
chmod 644 file.txt # rw- r-- r--
chmod 755 script.sh # rwx r-x r-x
chmod 600 ~/.ssh/id_ed25519
# Symbolic: changes one thing, leaves the rest
chmod u+x script.sh # add execute for the owner
chmod g+w shared/ # add write for the group
chmod o-rwx private/ # remove everything for others
chmod a+r public.txt # add read for all three sets
# Directories only, files only: the pattern that matters
find . -type d -exec chmod 755 {} \;
find . -type f -exec chmod 644 {} \;# Owner only
sudo chown deploy file.txt
# Owner and group
sudo chown deploy:www-data file.txt
# Group only (two equivalent forms)
sudo chown :www-data file.txt
sudo chgrp www-data file.txt
# Recursively across a tree
sudo chown -R deploy:www-data /var/www/example.com
# Copy ownership from an existing file, which avoids typos
sudo chown --reference=/var/www/example.com/index.php newfile.php--reference is the one to use when you are unsure what the ownership should be: it copies from something already correct.chmod -R 755 on a website is a mistake
It sets the execute bit on every file, including images, CSS and PHP source. Some server configurations will then treat executable files differently, and it makes a genuine executable impossible to spot. Always split directories and files with find, as shown above.
What is umask and why do my new files have the wrong permissions?
umask is a mask subtracted from the system defaults whenever a file is created: 666 for files and 777 for directories. The default umask of 022 removes write from group and others, which is where 644 and 755 come from.
| umask | New files | New directories | Use for |
|---|---|---|---|
| 022 | 644 | 755 | Default: only the owner can write |
| 002 | 664 | 775 | Team directories where the group must write |
| 077 | 600 | 700 | Private: nothing visible outside the owner |
| 027 | 640 | 750 | Owner writes, group reads, others excluded |
Directories never get the execute bit removed by umask 022, which is why they end up 755 rather than 644.
# Current value
umask
# In symbolic form, easier to read
umask -S
# Set for this shell only
umask 002
# Persist for one user
echo 'umask 002' >> ~/.bashrc
# Persist for a systemd service
# In the unit file, under [Service]:
# UMask=0002If files created by a deploy script keep coming out unreadable by the web server, check the umask of the process running the script, not the interactive shell you tested in. They are frequently different.
What do setuid, setgid and the sticky bit do?
Three extra bits sit above the usual nine. You will use setgid regularly on shared directories, meet the sticky bit on /tmp, and should treat setuid as something to audit rather than something to set.
| Bit | Octal | On a file | On a directory |
|---|---|---|---|
| setuid | 4000 | Runs as the file's owner, not you | No effect on Linux |
| setgid | 2000 | Runs as the file's group | New files inherit the directory's group |
| sticky | 1000 | No effect | Only the owner of a file can delete it |
setgid on a directory is the useful one. The other two you mostly just need to recognise.
# /tmp: anyone can write, only the owner can delete their own files
ls -ld /tmp
# drwxrwxrwt 12 root root 4096 Jul 29 10:14 /tmp
# ^ the t is the sticky bit
# passwd is setuid root: it must edit /etc/shadow on your behalf
ls -l /usr/bin/passwd
# -rwsr-xr-x 1 root root 68208 ... /usr/bin/passwd
# ^ the s is setuid
# Audit every setuid binary on the system. Do this on any
# server you have inherited.
sudo find / -perm -4000 -type f -ls 2>/dev/nullNever set setuid on your own scripts
Linux ignores setuid on shell scripts anyway, for good reason, and a setuid binary that takes any user input is a route to root. If a task needs root privileges, grant a specific sudo rule for that exact command instead.
For the applied version of all this, with the exact numbers a WordPress install wants, see Linux file permissions for web servers and WordPress. If server accounts are not something you want to own at all, our managed WordPress hosting runs each site in its own isolated container with the permissions maintained for you.
Frequently asked questions
What is the difference between a primary and secondary group in Linux?
A user has exactly one primary group, and it becomes the group owner of every file they create. Secondary groups grant access to existing files but never affect ownership of new ones. That is why adding someone to www-data does not make their new files readable by the web server.
Why can't a group member read a file that others can read?
Because Linux stops at the first matching category. If you are in the owning group, the group bits apply and nothing falls through to the other bits. A file set to 604 denies group members while allowing everyone else, which looks wrong until you know the rule.
What does umask 022 mean?
It removes write permission from group and others on anything newly created. Files start from 666 and become 644; directories start from 777 and become 755. Use 002 instead when a team shares a directory and everyone needs to edit each other's files.
What does the setgid bit do on a directory?
New files and subdirectories created inside inherit the directory's group rather than the creating user's primary group. On a shared web root that keeps ownership consistent no matter who uploads what, which is what stops permissions drifting over time. Set it with chmod g+s.
Should I use chmod 777 to fix a permission problem?
No. 777 lets every account and every process on the machine rewrite the file. When something only works at 777 the actual problem is ownership: the process needs to be in the owning group. Fix it with chown and a 775 group-write directory.
How do I find files with unusual permissions?
sudo find / -perm -4000 -type f -ls lists every setuid binary, and find /var/www -perm -o+w -type f lists anything world-writable under your web root. Both are worth running on a server you have just taken over.
About G7Cloud Engineering
Articles written by the engineers who build and run G7Cloud: UK managed hosting and the AI Website Builder. We write about what we operate every day: containers, backups, databases, and the small-business websites that run on them.
More about G7Cloud →