setuid (4xxx)
On an executable, run with the file owner's UID. Enables controlled privilege escalation (passwd writes to /etc/shadow).
ls -l /usr/bin/passwd→-rwsr-xr-xchmod u+s /path/to/bin
Paths like /var/log/nginx/access.log look like folders and files on disk—but the kernel tracks inodes, maps them through the VFS, and exposes almost everything as a path you can open, read, or write. Master the FHS layout and permissions and you can debug any Linux host in minutes.
Unix treats I/O uniformly: regular files, directories, devices, sockets, and pipes are accessed through file descriptors and path names.
| Type | Example path | What you do with it |
|---|---|---|
| Regular file | /etc/hosts |
Read/write bytes; editors, cat, log tailers |
| Directory | /var/log |
List names; kernel stores directory entries pointing to inodes |
| Block device | /dev/nvme0n1 |
Random access storage; mkfs, mount |
| Char device | /dev/tty, /dev/null |
Stream I/O; terminals, null sink |
| Socket | /var/run/docker.sock |
IPC; clients connect like a network endpoint |
| Pipe / FIFO | myfifo (created with mkfifo) |
Producer/consumer between processes |
| Virtual (procfs) | /proc/self/maps |
Kernel-generated live process data |
# File type appears in the first column of ls -l
ls -l /dev/null /etc/passwd /var/run/docker.sock 2>/dev/null
# c = char device, d = directory, s = socket, - = regular file
file /bin/bash /dev/sda /proc/cpuinfo
Pro Tip: Redirecting errors to /dev/null discards bytes—same syscall as writing a file. 2>/dev/null is idiomatic bash, not magic.
FHS defines where distributions place binaries, config, variable data, and mount points. Paths are stable across Ubuntu, RHEL, and Alpine—your playbooks and mental model transfer.
/ ├── bin/ sbin/ → commands ├── etc/ → configuration ├── home/ root/ → user homes ├── usr/ → apps, lib, share ├── var/ → logs, lib state, cache ├── tmp/ run/ → ephemeral ├── dev/ proc/ sys/ → devices & kernel views ├── opt/ mnt/ media/ boot/ lib/
Pro Tip: When a disk fills up, check df -h then drill into /var (logs, Docker) and /tmp first—those cause most production incidents. sysadmin
A path is a human-friendly name. The kernel stores file data in an inode (index node): a struct with owner, permissions, timestamps, size, and pointers to data blocks.
Directory entries are just name → inode number mappings. Renaming a file updates one directory entry; the inode (and data blocks) stay put unless you move across filesystems (which is copy + delete).
/var/log/app.log directory entry
│ │
│ name "app.log" │
└──────────► inode 8847291 ◄──────┐
│ │ hard link
│ │ same inode
├─ mode, uid, gid │
├─ size, timestamps│
└─ block pointers ─┘
│
▼
disk blocks
# Inode number in second column (-i)
ls -li /etc/hosts /etc/hostname
# Detailed inode stats
stat /etc/passwd
# Inodes remaining on a filesystem (exhaustion = "No space" despite free GB)
df -i /
Warning: Millions of tiny files can exhaust inodes while df -h still shows free space. Mail spools, container layers, and node_modules are common culprits. Always run df -i.
Links create additional names for data. They behave differently in backups, across filesystems, and when the target is deleted.
| Hard link | Symbolic link (symlink) | |
|---|---|---|
| Points to | Same inode (same file data) | Path string; separate inode |
| Cross filesystem | No | Yes |
| Directories | Usually disallowed for non-root | Yes (ln -s) |
| Target deleted | Data remains until all hard links gone | Dangling link ("broken symlink") |
| Typical use | Dedup, incremental backups | Version paths, config aliases (/etc/alternatives) |
# Hard link — same inode, same link count
ln /var/data/invoice.pdf /var/backup/invoice.pdf
ls -li /var/data/invoice.pdf /var/backup/invoice.pdf
# Symlink — points to a path; can cross mounts
ln -s /opt/app/current/config.yml /etc/myapp/config.yml
ls -l /etc/myapp/config.yml
# Find broken symlinks after a deploy
find /opt/app -type l ! -exec test -e {} \; -print
Pro Tip: Use readlink -f to resolve a symlink to its canonical absolute path—handy in deploy scripts when $0 or config paths are linked. dev
Every inode stores permission bits for three classes: user (owner), group, and others. Each class gets read, write, execute.
Symbolic (ls -l)
-rwxr-xr--
user: rwx · group: r-x · other: r-- → 754
Meaning of x
on files: execute
on dirs: access (cd, ls)
Without x on a dir, you cannot traverse it even if you can read files inside.
Each permission is a bit: r=4, w=2, x=1. Add per triplet:
| Octal | Binary per class | Symbolic | Common use |
|---|---|---|---|
| 644 | rw- r-- r-- | -rw-r--r-- | Config files, world-readable |
| 600 | rw- --- --- | -rw------- | SSH private keys, secrets |
| 755 | rwx r-x r-x | -rwxr-xr-x | Executables, public dirs |
| 750 | rwx r-x --- | -rwxr-x--- | Team scripts, group-only access |
| 700 | rwx --- --- | -rwx------ | Private scripts, .ssh |
# Fix a leaked world-readable env file after deploy
chmod 600 /opt/api/.env
chown deploy:deploy /opt/api/.env
# Web root: dirs executable, files not writable by www-data group
find /var/www/app -type d -exec chmod 755 {} \;
find /var/www/app -type f -exec chmod 644 {} \;
# Default permissions for *new* files (mask subtracted from 666/777)
umask
umask 027 # new files: 640, new dirs: 750
chown user:group file sets inode owner (root required unless you own the file on some systems). chgrp changes group only. Processes run as a UID; the kernel checks owner/group/other bits on every access.
# Recursive ownership fix after extracting a tarball as root
chown -R nginx:nginx /var/cache/nginx
# ACLs when rwx triplets are not enough (NFS, shared team dirs)
getfacl /data/shared
setfacl -m u:alice:rwx /data/shared
setfacl -m g:engineering:r-x /data/shared
Beyond rwx, three special bits change behavior for executables and directories. You will see them in ls -l as s or t.
On an executable, run with the file owner's UID. Enables controlled privilege escalation (passwd writes to /etc/shadow).
ls -l /usr/bin/passwd → -rwsr-xr-xchmod u+s /path/to/binOn executable: run with file's group. On directory: new files inherit the directory's group (shared team uploads).
chmod g+s /var/shared/uploadschmod 2775 /var/sharedOn a directory (e.g. /tmp): only the file owner (or root) may delete/rename their own files—even if others have write on the dir.
ls -ld /tmp → drwxrwxrwtchmod +t /tmpOctal with special bits uses a fourth digit: 4755 = setuid + 755, 1777 = sticky + 777.
Warning: setuid binaries are a security surface. Never setuid on shells or interpreters you control (python, bash). Audit with find / -perm -4000 -type f 2>/dev/null after incidents.
Tie FHS, inodes, links, and permissions to tasks you actually run.
# Who am I? What does the file look like?
id
ls -la /opt/api/releases/current/.env
namei -l /opt/api/releases/current/.env # trace every path component
du -sh /var/log/* | sort -hr | head
# Deleted file still held open by a process?
lsof +L1 | grep deleted
Blue/green deploys often use /opt/app/current → releases/20250604_1200. Rollback is flipping the symlink.
ln -sfn /opt/app/releases/20250604_1200 /opt/app/current
ls -la /opt/app/current
systemctl restart myapp
Pro Tip: namei -l path walks each path component and prints permissions—faster than guessing which parent directory blocks traversal. infra