A wrong file mode can stop sshd from trusting your key or let a web server read a secrets file it should never see. This guide shows how to read Linux permission bits, set them in octal and symbolic form, change ownership, and pick sensible modes for the files you actually manage on a server. It is Part 2 of the Linux Server Baseline series, and the commands are the same on Ubuntu 24.04 and Debian 12.
Read the ls -l output
Start with a directory listing:
ls -l /var/www/example
total 8
drwxr-s--- 2 deploy www-data 4096 Sep 17 10:02 assets
lrwxrwxrwx 1 deploy deploy 22 Sep 17 10:02 current -> /srv/releases/20260917
-rw-r----- 1 deploy www-data 612 Sep 17 10:02 index.html
The first character is the type: - for a regular file, d for a directory, l for a symbolic link. The next nine characters are three rwx triplets for the owning user, the owning group, and everyone else (other). A dash means the bit is off. After that come the link count (for a directory it also counts its subdirectories), the owner, the group, the size, the modification time, and the name.
A + right after the nine bits means the file has an ACL, and a . on RHEL means it has an SELinux context. Symlinks always show rwxrwxrwx; the target's mode is what counts.
Only one triplet applies to a given user. If you own the file, the kernel uses the user triplet and stops there, even if the group triplet would allow more. Otherwise it checks the group triplet if you belong to the group, and falls back to other. To see the numeric mode next to the symbolic one, use stat:
stat -c '%a %A %U:%G %n' /var/www/example/index.html
640 -rw-r----- deploy:www-data /var/www/example/index.html
What read, write, and execute mean
The same three letters mean different things on files and directories:
| Bit | On a file | On a directory |
|---|---|---|
| r | Read the contents | List the names inside |
| w | Change the contents | Create, delete, and rename entries (only together with x) |
| x | Run it as a program | Enter it and reach entries by name |
Deleting or renaming a file is controlled by the directory's w and x bits, not by the file's own mode, so a read-only file in a writable directory can still be deleted. Without x on a directory, nothing inside it is reachable, whatever the files' own modes say. With r but no x, ls prints the names but ls -l shows ? for every field and reports permission denied.
A script needs both r and x for a normal user to run it, because the interpreter has to read the file.
Octal and symbolic modes
In octal, each triplet is the sum of r=4, w=2, and x=1, giving one digit per triplet. rw- is 4+2=6, r-x is 4+1=5, and --- is 0. The modes you will use most:
| Octal | Symbolic | Typical use |
|---|---|---|
| 644 | rw-r--r-- |
Config files and static assets anyone may read |
| 600 | rw------- |
Private keys, authorized_keys
|
| 755 | rwxr-xr-x |
Directories and programs everyone may use |
| 700 | rwx------ |
~/.ssh, private script directories |
| 750 | rwxr-x--- |
Directories the owner manages and one group reads |
Symbolic mode names who (u, g, o, or a for all), an operator (+ adds, - removes, = sets exactly), and the bits:
chmod u+x deploy.sh
chmod g-w shared.conf
chmod o= secrets.env
chmod a+r README.md
These add execute for the owner, remove group write, remove every permission for other, and give everyone read access.
The difference matters when files in a set have different modes. chmod 644 replaces all nine bits, so it would strip execute from a script that had it. chmod g-w changes one bit and leaves everything else alone. Use octal when you know the complete end state (a new key file, a fresh directory) and symbolic when you want one change applied to files that may differ. To convert between the two forms without doing the arithmetic in your head, use the chmod calculator. Check the result with stat -c '%a %n' deploy.sh.
Ownership and the umask
chown changes the owner and optionally the group. chgrp changes only the group:
sudo chown deploy:www-data /var/www/example/index.html
sudo chgrp www-data /var/www/example/index.html
sudo chown -R deploy:www-data /var/www/example
Only root can give a file to another user. A file's owner can change its group, but only to a group they belong to. Check memberships with id deploy. After sudo usermod -aG www-data deploy, the new group only applies to sessions started after the change, so log out and back in.
When a program creates a file it normally asks for mode 666 (777 for a directory), and the umask removes bits from that. With a umask of 022, new files are 644 and directories are 755. With 002, they are 664 and 775, so the group can write too. Check your current value:
umask
0022
Root and most services use 022. On Ubuntu, regular users whose primary group has the same name as the user get 002 at login through pam_umask, so run umask rather than assuming. For a service that writes sensitive files, set UMask=0027 in its systemd unit so new files come out 640 and nothing is readable by other.
Setuid, setgid, and the sticky bit
A fourth octal digit goes in front of the usual three: 4 for setuid, 2 for setgid, 1 for sticky.
Setuid (4) on an executable makes it run with the file owner's privileges. That is how passwd can update /etc/shadow:
stat -c '%a %A %n' /usr/bin/passwd
4755 -rwsr-xr-x /usr/bin/passwd
The s sits in the user execute position. Linux ignores setuid on scripts that start with #!.
Setgid (2) on an executable runs it with the file's group. On a directory, it is more useful: new files and subdirectories inherit the directory's group, and new subdirectories inherit the setgid bit. The sticky bit (1) on a directory means only a file's owner, the directory's owner, or root can delete or rename entries. That is what keeps users from deleting each other's files in /tmp:
stat -c '%a %A %n' /tmp
1777 drwxrwxrwt /tmp
An uppercase S or T means the special bit is set but the execute bit under it is not, which is usually a mistake.
A worked example: a team directory where everything stays in the devs group.
sudo mkdir -p /srv/shared
sudo chown root:devs /srv/shared
sudo chmod 2775 /srv/shared
Reading 2775 digit by digit: 2 is setgid, 7 gives the owner rwx, 7 gives the group rwx, and 5 gives other r-x, which ls -ld shows as drwxrwsr-x. Team members also need a umask of 002 for their new files to be group-writable. GNU chmod keeps setgid on a directory when you give a plain three-digit mode such as 755, so clear it explicitly with chmod g-s if you mean to.
These bits are also what you audit for. World-writable files, world-writable directories without the sticky bit, and setuid or setgid binaries:
sudo find / -xdev -type f -perm -0002
sudo find / -xdev -type d -perm -0002 ! -perm -1000
sudo find / -xdev -type f \( -perm -4000 -o -perm -2000 \) -exec ls -l {} +
-perm -0002 matches files with at least that bit set. -xdev keeps find on one filesystem, so repeat the command for separate mounts such as /home or /srv. The setuid list should be system tools like sudo, su, passwd, and mount. Anything under a home directory, /srv, or a web root needs a closer look, and dpkg -S /path/to/binary tells you whether a package installed it.
Recursive changes and common mistakes
The classic mistake is sudo chmod -R 755 /var/www/example. It fixes directories and makes every file executable at the same time. Before any large recursive change, save the current modes and ownership so you can put them back:
sudo getfacl -pR /var/www/example > ~/www-perms.acl
Restore with sudo setfacl --restore="$HOME/www-perms.acl". Both tools come from the acl package. Then treat directories and files separately:
sudo find /var/www/example -type d -exec chmod 755 {} +
sudo find /var/www/example -type f -exec chmod 644 {} +
Capital X does the same job in one command. It adds execute only to directories and to files that already have an execute bit for someone:
sudo chmod -R u=rwX,go=rX /var/www/example
Scripts that were executable stay that way, and ordinary files do not gain the bit. It will not remove execute from files that already have it, so use the find version to clean up after a bad chmod -R.
Other mistakes that show up often:
- Permission denied on a file whose mode looks fine. A parent directory is missing
xfor that user.namei -l /var/www/example/index.htmllists the mode and owner of every path component. - Running
chmod 777to make an error go away. It gives every local user and process write access. Find out which user the process runs as (ps -eo user,comm) and fix the owner or group instead. - Adding a user to a group and testing in the same shell. The session still has the old group list until the next login.
Permissions for common server files
A web root owned by the deploy user, readable by the web server through its group, and closed to everyone else:
sudo chown -R deploy:www-data /var/www/example
sudo find /var/www/example -type d -exec chmod 2750 {} +
sudo find /var/www/example -type f -exec chmod 640 {} +
Setgid on the directories keeps files that deploy adds later in the www-data group. If the application has to write somewhere, such as an uploads directory, give group write on that directory only (sudo chmod 2770 /var/www/example/uploads).
For SSH, sshd refuses to use authorized_keys if the file or ~/.ssh is writable by others, and the client refuses a private key that other users can access:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys ~/.ssh/id_ed25519
chmod 644 ~/.ssh/id_ed25519.pub
Hardening SSH for Production Servers covers the rest of the SSH setup and how to read sshd's log when a key is rejected.
For an application secrets file, let root edit it and the service's group read it:
sudo chown root:myapp /etc/myapp/secrets.env
sudo chmod 640 /etc/myapp/secrets.env
The service must run as a user in the myapp group. Everyone else gets nothing.
When one owner and one group are not enough, such as a second team that needs read access to a directory, use ACLs instead of loosening the other bits:
sudo setfacl -R -m g:auditors:rX /srv/reports
getfacl /srv/reports
ls -l then shows a + after the mode. Add -d to set a default ACL that new files inherit.
Where to go next
Run the three find audits above on each server and check the web root, ~/.ssh, and secrets files against the modes in this guide. Part 3 of the series, Cron Jobs Explained: Syntax, Pitfalls, and Logging, puts these modes to work on scheduled scripts that need to be executable and write logs somewhere safe.
Top comments (0)