File System

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.

sysadmin dev

Everything is a file

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
bash
# 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.

Filesystem Hierarchy Standard (FHS)

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.

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

Inodes and metadata

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).

bash
# 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.

Permissions: rwx and octal

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.

Octal notation

Each permission is a bit: r=4, w=2, x=1. Add per triplet:

Octal Binary per class Symbolic Common use
644rw- r-- r---rw-r--r--Config files, world-readable
600rw- --- ----rw-------SSH private keys, secrets
755rwx r-x r-x-rwxr-xr-xExecutables, public dirs
750rwx r-x ----rwxr-x---Team scripts, group-only access
700rwx --- ----rwx------Private scripts, .ssh
bash
# 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

Ownership

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.

bash
# 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

setuid, setgid, sticky bit

Beyond rwx, three special bits change behavior for executables and directories. You will see them in ls -l as s or t.

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-x
  • chmod u+s /path/to/bin
sysadmin

setgid (2xxx)

On executable: run with file's group. On directory: new files inherit the directory's group (shared team uploads).

  • chmod g+s /var/shared/uploads
  • chmod 2775 /var/shared
sysadmin

sticky bit (1xxx)

On 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 /tmpdrwxrwxrwt
  • chmod +t /tmp
sysadmin

Octal 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.

Practical workflows

Tie FHS, inodes, links, and permissions to tasks you actually run.

Permission denied on deploy

bash
# 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

Log rotation filling disk under /var

bash
du -sh /var/log/* | sort -hr | head
# Deleted file still held open by a process?
lsof +L1 | grep deleted

Immutable releases with symlinks

Blue/green deploys often use /opt/app/currentreleases/20250604_1200. Rollback is flipping the symlink.

bash
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