DEV Community

Cover image for Linux File Permissions Explained: chmod, chown, and umask
Sysemperor
Sysemperor

Posted on • Originally published at sysemperor.com

Linux File Permissions Explained: chmod, chown, and umask

The most common Linux question I see from developers coming from a Windows or macOS background is some variation of "why does everything say Permission denied?" The answer is always the same: Linux permissions are strict by default and unforgiving when misconfigured. The good news is that once you can read the permission string, the rest takes about five minutes to internalise.


Reading the permission string

Run ls -l on any file:

-rwxr-xr-- 1 emperor devs 4096 Apr 28 09:14 deploy.sh
Enter fullscreen mode Exit fullscreen mode

The first ten characters are the permission string:

- rwx r-x r--
│ │   │   └── others: read only
│ │   └── group: read and execute
│ └── owner: read, write, execute
└── type: - = file, d = directory, l = symlink
Enter fullscreen mode Exit fullscreen mode

Each three-character block is the same: r (read), w (write), x (execute). A - means the permission is not granted.


chmod — change permissions

Symbolic mode

chmod u+x deploy.sh      # give the owner execute permission
chmod g-w config.yml     # remove write from the group
chmod o=  secrets.env    # remove all permissions from others
chmod a+r README.md      # give everyone read permission
Enter fullscreen mode Exit fullscreen mode

Targets: u = owner, g = group, o = others, a = all three.
Operators: + = add, - = remove, = = set exactly (overrides existing).

Numeric (octal) mode

Each permission has a value: r=4, w=2, x=1. Add them per group.

chmod 755 deploy.sh    # owner: rwx (7) | group: r-x (5) | others: r-x (5)
chmod 644 config.yml   # owner: rw- (6) | group: r-- (4) | others: r-- (4)
chmod 600 secrets.env  # owner: rw- (6) | group: --- (0) | others: --- (0)
chmod 700 private/     # owner: rwx (7) | group: --- (0) | others: --- (0)
Enter fullscreen mode Exit fullscreen mode

755 is the standard for executables and directories that need to be traversable by everyone. 644 is standard for files that should be readable but only writable by the owner. 600 is for anything sensitive — SSH keys, credentials, private config.

If you find yourself guessing the right octal number, SysEmperor's free chmod Calculator lets you toggle permissions visually and gives you the exact command — no mental arithmetic required.

Recursive

chmod -R 755 public/
Enter fullscreen mode Exit fullscreen mode

Applies to every file and subdirectory under public/. Use with care — setting everything to 755 makes all files executable, which is rarely what you want. When you need different permissions for files and directories, use find:

find public/ -type f -exec chmod 644 {} \;
find public/ -type d -exec chmod 755 {} \;
Enter fullscreen mode Exit fullscreen mode

chown — change ownership

chown emperor deploy.sh           # change owner only
chown emperor:devs deploy.sh      # change owner and group
chown :devs deploy.sh           # change group only
chown -R emperor:devs /var/app    # recursive
Enter fullscreen mode Exit fullscreen mode

You need sudo to transfer a file to a different user. Changing the group to one you already belong to does not require it.


Special permission bits

Three extra bits sit above the standard rwxrwxrwx. They appear as a fourth octal digit or as modified characters in ls -l.

Setuid (4xxx / s on owner execute) — the executable runs as its owner, not the user who launched it. /usr/bin/passwd uses this to let ordinary users modify /etc/shadow.

Setgid (2xxx / s on group execute) — on a directory, new files inherit the directory's group instead of the creator's primary group. Standard practice for shared project directories where all team members need the same group ownership.

Sticky bit (1xxx / t on others execute) — on a directory, only the file's owner can delete it even if others have write permission on the directory. /tmp uses this so users cannot delete each other's temporary files.

ls -ld /tmp
drwxrwxrwt 14 root root 4096 Apr 29 07:31 /tmp
#        ^ t = sticky bit
Enter fullscreen mode Exit fullscreen mode

umask — the permission filter for new files

When you create a file or directory, the kernel starts with a base permission and subtracts the umask. The default on most Linux systems is 022.

For files, the base is 666 (no execute by default):

666 - 022 = 644   (rw-r--r--)
Enter fullscreen mode Exit fullscreen mode

For directories, the base is 777:

777 - 022 = 755   (rwxr-xr-x)
Enter fullscreen mode Exit fullscreen mode

Check your current umask:

umask
Enter fullscreen mode Exit fullscreen mode

Change it for the session:

umask 027    # new files: 640, new directories: 750
Enter fullscreen mode Exit fullscreen mode

A umask of 027 is common in environments where files should be hidden from users outside the group. Set it in ~/.bashrc or /etc/profile to apply it permanently.


Common gotchas

Script refuses to run. The execute bit is not set. chmod +x script.sh fixes it.

Permission denied when writing to a shared directory. Check ls -ld on the directory — the group write bit (position 5 in the string) needs to be set, and you need to be a member of the owning group. groups shows your current group memberships.

SSH key ignored by sshd. Key permissions must be strict. ~/.ssh must be 700, private keys and authorized_keys must be 600. If they are too open, sshd silently skips the file and falls back to password auth. This is the most common reason for "my key doesn't work."

Ownership changes when copying files. cp creates a new file owned by you. Use rsync -a to preserve ownership, or rsync --chown=user:group to set ownership explicitly during the copy.


Found this useful? SysEmperor has more Linux tutorials and free developer tools — including a chmod Calculator, fstab Editor, and downloadable AI skills at sysemperor.com.

Top comments (0)