Unix File Systems and Permissions
Permissions are one of those things you think you understand until you have to explain them to someone else. The nine-character string from ls -l — -rw-r--r-- — looks simple, but there are enough edge cases hiding in it to keep things interesting. This covers the core concepts and the parts that actually trip people up.
The Directory Tree
Everything in Unix lives under /. There's no C: drive, no volumes by letter — it's one tree, and other disks get mounted into it at whatever point you choose.
/home) and virtual filesystems (like /dev) are mounted directly into a single tree.| Directory | Purpose |
|---|---|
/bin, /usr/bin | System and user binaries (commands) |
/sbin, /usr/sbin | System administration binaries (often root-only) |
/etc | Configuration files — plain text, almost always editable |
/home | User home directories |
/var | Logs, caches, mail, databases — anything that grows over time |
/tmp | Temporary files, usually cleared on reboot |
/proc, /sys | Linux virtual filesystems exposing process and kernel/device information. They are not universal Unix paths; BSD and macOS expose this information differently. |
/dev | Device files — disks, terminals, serial ports |
File Types
The first character of ls -l output tells you what something actually is:
- regular file d directory l symbolic link c character device (terminals, serial ports) b block device (disks) s socket (used by network services and databases) p named pipe (FIFO — for inter-process communication)
Reading the Permission Bits
$ ls -l notes.txt -rw-r--r-- 1 jason users 4096 Feb 5 12:34 notes.txt Breakdown: 1 number of hard links jason owner users group 4096 size in bytes Feb 5 modification time notes.txt name
For directories, x (execute) means you can enter it (cd into it) and access files inside. Without x on a directory, you can't traverse it even if you can read the directory listing.
Changing Permissions: chmod
Numeric Mode
Read = 4, Write = 2, Execute = 1. Add them for each of the three groups (owner, group, others):
| Command | Result | Typical use |
|---|---|---|
chmod 755 script.sh | rwxr-xr-x | Executable: owner full, others read+execute |
chmod 644 config.txt | rw-r--r-- | Files: owner read+write, others read |
chmod 600 id_rsa | rw------- | Private keys and secrets: owner only |
chmod 700 ~/.ssh | rwx------ | Private directories: owner only |
chmod 2775 /srv/shared | rwxrwsr-x | Setgid directory for shared team projects |
Symbolic Mode
$ chmod u+x script.sh # add execute for owner (u=user/owner) $ chmod go-w file.txt # remove write from group and others $ chmod a+r readme.txt # add read for all (a=all) $ chmod o-rx /home/jason # remove read+execute from others on home dir
Ownership: chown and chgrp
$ chown jason file.txt # change owner $ chown jason:users file.txt # change owner and group $ chgrp www-data /var/www/html # change group only $ chown -R jason:jason ~/project # recursive — useful after copying files as root # You need root to give a file to another user. # You can change your own files to a group you belong to without sudo.
Special Permission Bits
Setuid (4xxx)
When set on an executable, it runs as the file's owner rather than whoever ran it. The classic example is passwd — ordinary users need to be able to change their own passwords, but the password database (/etc/shadow) requires root access to modify:
$ ls -l /usr/bin/passwd
-rwsr-xr-x 1 root root 59976 ... /usr/bin/passwd
↑ 's' in owner execute position = setuid
Setgid (2xxx)
On an executable: same idea — runs with the file's group. On a directory: new files created inside inherit the directory's group instead of the creator's primary group. Useful for shared project directories:
$ chmod 2775 /srv/shared
$ ls -ld /srv/shared
drwxrwsr-x 2 root devteam 4096 ... /srv/shared
↑ 's' in group execute position = setgid
Files created inside /srv/shared automatically get group "devteam"
regardless of which team member creates them.
Sticky Bit (1xxx)
On a directory, only the file's owner (or root) can delete or rename files inside, even if others have write permission on the directory. /tmp uses this — everyone can write there, but you can't delete each other's files:
$ ls -ld /tmp
drwxrwxrwt 12 root root 4096 ... /tmp
↑ 't' at end = sticky bit
Access Control Lists (ACLs)
Standard Unix permissions give you three buckets — owner, group, everyone else. ACLs can add per-user or per-group permissions, but their commands and exact behaviour vary by operating system and filesystem. The following getfacl/setfacl examples describe the common Linux workflow on ACL-capable filesystems such as ext4, XFS, and Btrfs. FreeBSD also provides these commands with filesystem-specific details; macOS commonly uses chmod ACL syntax instead. Read the local manuals before changing ACLs.
$ getfacl file.txt # view current ACL $ setfacl -m u:sarah:rw file.txt # give sarah read+write $ setfacl -m g:contractors:r file.txt # give contractors read $ setfacl -x u:sarah file.txt # remove sarah's ACL entry $ setfacl -b file.txt # remove all ACL entries When a file has an ACL, ls -l shows a '+' at the end of the permissions: -rw-r--r--+ 1 jason users 4096 ... file.txt
Finding Files by Permission
$ find /usr/bin -perm -4000 # all setuid binaries (security audit) $ find /home -perm -0002 # entries with the other-write bit set $ find /var/www -not -user www-data # files not owned by the web user $ find /tmp -perm -002 -type f # world-writable files in /tmp
dispelled