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 x for that user. namei -l /var/www/example/index.html lists the mode and owner of every path component.
  • Running chmod 777 to 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.