Appearance
The Unix File System
An In-Depth Guide
Published: March 12, 2026 · Last edited: March 12, 2026
Introduction
The Unix file system is one of the most influential designs in computing history. Born at Bell Labs in the early 1970s, its core abstractions have survived largely unchanged for over 50 years — a testament to getting the fundamentals right.
Understanding the Unix file system means understanding:
- How files are organized — a single rooted tree of directories
- How files are stored — inodes, data blocks, and the separation of names from content
- How access is controlled — permissions, ownership, and special bits
- How hardware is abstracted — devices, virtual filesystems, and mount points
Whether you're working on Linux, macOS, or any BSD variant, these concepts are universal.
Core Philosophy
"Everything Is a File"
Unix's most radical idea is treating nearly everything as a file. Regular files, directories, hardware devices, inter-process communication channels, and even kernel data structures all present themselves through the same interface: open, read, write, close.
This means:
- You read from a hard drive the same way you read from a text file
- You write to a network socket the same way you write to a log file
- You inspect running processes by reading files in
/proc
bash
# Reading a "file" that is actually kernel data
cat /proc/cpuinfo
# Writing to a "file" that is actually a device
echo "hello" > /dev/ttyUSB0
# Reading from a "file" that produces random bytes
head -c 16 /dev/urandom | xxdSingle Rooted Tree
Unlike Windows (which uses drive letters like C:\ and D:\), Unix has a single root directory /. Everything — every disk, every partition, every network share — is attached somewhere within this one tree.
/
├── bin/
├── etc/
├── home/
│ ├── alice/
│ └── bob/
├── tmp/
├── usr/
│ ├── bin/
│ ├── lib/
│ └── share/
└── var/
├── log/
└── tmp/This simplicity is intentional. You never need to know which disk a file lives on — you just need to know its path.
Directory Hierarchy
The Filesystem Hierarchy Standard (FHS) defines where things go on a Unix/Linux system. Here are the key directories and their purposes.
System Directories
| Directory | Purpose | Examples |
|---|---|---|
/ | Root — the top of the entire filesystem tree | Everything lives under here |
/bin | Essential user commands needed for single-user mode | ls, cp, cat, bash |
/sbin | Essential system administration commands | mount, fsck, iptables |
/lib | Shared libraries needed by /bin and /sbin | libc.so, kernel modules |
/boot | Boot loader files | vmlinuz, initrd, GRUB config |
Configuration & Variable Data
| Directory | Purpose | Examples |
|---|---|---|
/etc | System-wide configuration files | passwd, fstab, nginx/nginx.conf |
/var | Variable data that changes during operation | Logs, caches, spool files, databases |
/var/log | System and application log files | syslog, auth.log, nginx/access.log |
/var/tmp | Temporary files preserved across reboots | Unlike /tmp, survives restarts |
/tmp | Temporary files — may be cleared on reboot | Session files, build artifacts |
User Directories
| Directory | Purpose | Examples |
|---|---|---|
/home | User home directories | /home/alice, /home/bob |
/root | Home directory for the root (superuser) account | Root's personal files |
Programs & Libraries
| Directory | Purpose | Examples |
|---|---|---|
/usr | User programs — the bulk of installed software | Second-level hierarchy |
/usr/bin | Non-essential user commands | git, python, vim |
/usr/sbin | Non-essential system commands | cron, useradd |
/usr/lib | Libraries for /usr/bin and /usr/sbin | Python packages, shared objects |
/usr/local | Locally installed software (not from package manager) | Custom-built tools |
/usr/share | Architecture-independent data | Man pages, documentation, icons |
/opt | Optional/third-party software packages | /opt/google/chrome |
Special & Virtual Directories
| Directory | Purpose | Examples |
|---|---|---|
/dev | Device files — interfaces to hardware and pseudo-devices | sda, null, zero, urandom |
/proc | Virtual filesystem exposing kernel and process info | cpuinfo, meminfo, 1/status |
/sys | Virtual filesystem exposing kernel objects (drivers, devices) | Device attributes, power management |
Mount Points
| Directory | Purpose | Examples |
|---|---|---|
/mnt | Temporary mount point for manual mounts | Mounting a disk for maintenance |
/media | Auto-mounted removable media | USB drives, CD-ROMs |
/srv | Data served by the system | Web server files, FTP data |
Inspecting the Hierarchy
bash
# See top-level directories
ls /
# See disk usage per directory
du -sh /* 2>/dev/null | sort -hr | head -20
# See which filesystem each directory lives on
df -hTFile Types
Unix has seven file types. You can identify them with ls -l — the first character of the output tells you the type.
| Symbol | Type | Description |
|---|---|---|
- | Regular file | Text, binary, image, anything with content |
d | Directory | A file that contains a list of name→inode mappings |
l | Symbolic link | A pointer to another file path |
b | Block device | Buffered access to hardware (disks, partitions) |
c | Character device | Unbuffered access to hardware (terminals, serial ports) |
p | Named pipe (FIFO) | Inter-process communication channel |
s | Socket | Inter-process communication endpoint |
bash
# Identify file types
ls -l /dev/sda # brw-rw---- ... block device
ls -l /dev/tty # crw-rw-rw- ... character device
ls -l /tmp # drwxrwxrwt ... directory (with sticky bit)
ls -l /usr/bin/python3 # lrwxrwxrwx ... symbolic link (often)
# Use the file command for content-based identification
file /bin/ls # ELF 64-bit LSB executable
file /etc/hostname # ASCII text
file /dev/null # character specialRegular Files
The most common type. Contains data — text, binary, images, executables, anything. The kernel doesn't care about the content; interpreting it is the application's job.
Directories
A directory is itself a file. Its content is a table mapping names to inode numbers. When you ls a directory, the kernel reads this table.
bash
# A directory entry maps names to inodes
ls -li /etc | head -5
# Output shows inode numbers in the first columnSymbolic Links (Symlinks)
A symlink is a file that contains a path to another file. The kernel transparently follows the link when you access it.
bash
# Create a symlink
ln -s /var/log/syslog ~/current-log
# The symlink points to the target
ls -l ~/current-log
# lrwxrwxrwx 1 user user 15 ... current-log -> /var/log/syslog
# Reading the symlink reads the target
cat ~/current-log # shows syslog contents
# Symlinks can break if the target is removed
rm /var/log/syslog
cat ~/current-log # error: No such file or directoryDevice Files
Block and character devices live in /dev. They let userspace programs communicate with hardware through the standard file interface.
bash
# Common device files
ls -l /dev/null # discard anything written to it
ls -l /dev/zero # produces infinite zero bytes
ls -l /dev/urandom # produces random bytes
ls -l /dev/sda # first SCSI/SATA disk (block device)
ls -l /dev/tty # current terminal (character device)Named Pipes and Sockets
These are inter-process communication (IPC) mechanisms that appear in the filesystem.
bash
# Create a named pipe
mkfifo /tmp/mypipe
# In one terminal — writer blocks until a reader connects
echo "hello" > /tmp/mypipe
# In another terminal — reader gets the data
cat /tmp/mypipe # outputs "hello"Inodes & Storage
What Is an Inode?
An inode (index node) is a data structure that stores all metadata about a file except its name. Every file and directory has exactly one inode.
An inode contains:
- File type and permissions
- Owner (UID) and group (GID)
- File size
- Timestamps (access, modification, change)
- Number of hard links
- Pointers to the data blocks on disk
bash
# View inode information
stat /etc/hostname File: /etc/hostname
Size: 11 Blocks: 8 IO Block: 4096 regular file
Device: 802h/2050d Inode: 131074 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2026-03-10 08:15:22.000000000 +0000
Modify: 2026-01-15 14:30:00.000000000 +0000
Change: 2026-01-15 14:30:00.000000000 +0000
Birth: 2026-01-15 14:30:00.000000000 +0000Names Are Just Pointers
A filename is not stored inside the inode. Instead, directory entries map names to inode numbers. This separation is what makes hard links possible.
Directory entry (in parent directory's data):
┌──────────────┬──────────────┐
│ Name │ Inode Number │
├──────────────┼──────────────┤
│ . │ 2 │
│ .. │ 2 │
│ etc │ 131073 │
│ home │ 262145 │
│ var │ 393217 │
└──────────────┴──────────────┘Inode Limits
Each filesystem has a finite number of inodes, set at creation time. You can run out of inodes (and be unable to create files) even if disk space remains.
bash
# Check inode usage
df -i
# Example output:
# Filesystem Inodes IUsed IFree IUse% Mounted on
# /dev/sda1 3276800 185432 3091368 6% /Three Timestamps
Every inode tracks three timestamps:
| Timestamp | Updated When | View With |
|---|---|---|
| atime (access) | File content is read | ls -lu |
| mtime (modify) | File content is changed | ls -l |
| ctime (change) | Inode metadata is changed (permissions, owner, links) | ls -lc |
TIP
Many modern systems mount with noatime or relatime to reduce disk writes, since updating atime on every read is expensive.
Permissions & Ownership
Every file has an owner (a user), a group, and three sets of permissions: one for the owner, one for the group, and one for everyone else.
The Permission Bits
-rwxr-xr-- 1 alice devs 4096 Mar 10 09:00 deploy.sh
│└┬┘└┬┘└┬┘
│ │ │ └── Others: read only
│ │ └───── Group (devs): read + execute
│ └───────── Owner (alice): read + write + execute
└─────────── File type: regular file| Permission | On Files | On Directories |
|---|---|---|
r (read) | View contents | List contents (ls) |
w (write) | Modify contents | Create/delete files within |
x (execute) | Run as program | Enter directory (cd) |
Octal Notation
Each permission set maps to a number: r=4, w=2, x=1.
bash
chmod 755 script.sh # rwxr-xr-x (owner: all, group: read+exec, others: read+exec)
chmod 644 config.txt # rw-r--r-- (owner: read+write, group: read, others: read)
chmod 700 private/ # rwx------ (owner only)Special Bits
| Bit | Octal | On Files | On Directories |
|---|---|---|---|
| setuid | 4000 | Runs as file owner | — |
| setgid | 2000 | Runs as file group | New files inherit directory's group |
| sticky | 1000 | — | Only file owner can delete their files |
bash
# setuid example: passwd runs as root regardless of who calls it
ls -l /usr/bin/passwd
# -rwsr-xr-x 1 root root ... /usr/bin/passwd
# ^ 's' instead of 'x' = setuid
# Sticky bit example: /tmp lets everyone write but only owners delete
ls -ld /tmp
# drwxrwxrwt ... /tmp
# ^ 't' = sticky bitINFO
For a complete permissions reference with more examples, see the File Permissions Quick Reference.
Mount Points & Virtual Filesystems
How Mounting Works
In Unix, you don't access disks by drive letter. Instead, you mount a filesystem onto an existing directory, and that directory becomes the entry point to the mounted filesystem's contents.
bash
# Mount a USB drive onto /mnt/usb
mount /dev/sdb1 /mnt/usb
# Now /mnt/usb contains the USB drive's files
ls /mnt/usb
# Unmount when done
umount /mnt/usb/etc/fstab — Persistent Mounts
The file /etc/fstab defines filesystems to mount at boot:
bash
# /etc/fstab format:
# <device> <mount point> <type> <options> <dump> <pass>
/dev/sda1 / ext4 defaults 0 1
/dev/sda2 /home ext4 defaults 0 2
tmpfs /tmp tmpfs defaults,noatime 0 0bash
# View currently mounted filesystems
mount | column -t
# Or with filesystem type and human-readable sizes
df -hTVirtual Filesystems
Not all filesystems correspond to disks. Several exist purely in memory, exposing kernel data.
procfs (/proc)
Exposes process and kernel information as files:
bash
# Current process's environment
cat /proc/self/environ | tr '\0' '\n'
# System memory info
cat /proc/meminfo | head -5
# CPU information
cat /proc/cpuinfo | grep "model name" | head -1
# Each running process gets a directory
ls /proc/1/ # init/systemd process
ls /proc/$$/ # current shell processsysfs (/sys)
Exposes device and driver information:
bash
# Screen brightness (on laptops)
cat /sys/class/backlight/*/brightness
# Block device info
cat /sys/block/sda/size
# Network interface state
cat /sys/class/net/eth0/operstatetmpfs
A filesystem that lives entirely in RAM. Fast, but contents are lost on reboot.
bash
# /tmp is often a tmpfs
df -T /tmp
# Filesystem Type ...
# tmpfs tmpfs ...
# Create your own tmpfs
mount -t tmpfs -o size=512m tmpfs /mnt/ramdiskLinks Deep Dive
Hard Links
A hard link is an additional directory entry pointing to the same inode. The file's data has no "original" — all hard links are equal.
bash
# Create a hard link
echo "shared content" > original.txt
ln original.txt hardlink.txt
# Both point to the same inode
ls -li original.txt hardlink.txt
# 131074 -rw-r--r-- 2 user user 15 ... original.txt
# 131074 -rw-r--r-- 2 user user 15 ... hardlink.txt
# ^ link count is now 2
# Modifying one modifies the other (same data)
echo "updated" >> hardlink.txt
cat original.txt # shows "shared content\nupdated"
# Deleting one doesn't affect the other
rm original.txt
cat hardlink.txt # still works — data exists until link count reaches 0Hard link rules:
- Cannot cross filesystem boundaries (must be same partition)
- Cannot link to directories (to prevent cycles in the tree)
- All links are equal — there is no "original"
Symbolic Links (Soft Links)
A symbolic link is a separate file that stores a path string. The kernel follows this path when you access the link.
bash
# Create a symbolic link
ln -s /var/log/syslog ~/log-shortcut
# It's a different inode, different file
ls -li /var/log/syslog ~/log-shortcut
# Different inode numbers
# Symlink stores the path as its content
readlink ~/log-shortcut # /var/log/syslogComparison
| Feature | Hard Link | Symbolic Link |
|---|---|---|
| Same inode as target | Yes | No (has its own inode) |
| Survives target deletion | Yes | No (becomes dangling) |
| Can cross filesystems | No | Yes |
| Can link to directories | No | Yes |
Shows target in ls -l | No | Yes (-> target) |
| Size | Same as target | Length of path string |
Common Use Cases
bash
# Symlinks: version management
ln -s python3.11 /usr/local/bin/python3
ln -s /etc/nginx/sites-available/mysite /etc/nginx/sites-enabled/mysite
# Hard links: space-efficient backups (used by rsync --link-dest)
rsync -a --link-dest=../previous-backup/ source/ current-backup/Navigation & Inspection Commands
Navigating
bash
# Print current directory
pwd
# Change directory
cd /var/log # absolute path
cd ../etc # relative path
cd ~ # home directory
cd - # previous directoryListing & Searching
bash
# List with details and hidden files
ls -la
# List with inode numbers
ls -li
# Recursive tree view (install tree if not available)
tree -L 2 /etc
# Find files by name
find /etc -name "*.conf" -type f
# Find files by size (larger than 100MB)
find / -size +100M -type f 2>/dev/null
# Find files modified in the last 24 hours
find /var/log -mtime -1 -type fDisk Usage
bash
# Filesystem disk space (human-readable, with types)
df -hT
# Directory sizes (sorted, top 10)
du -sh /var/* 2>/dev/null | sort -hr | head -10
# Single directory total
du -sh /home/aliceFile Inspection
bash
# Detailed metadata
stat myfile.txt
# Content type detection
file myfile.txt # ASCII text
file /bin/ls # ELF 64-bit LSB executable
file image.png # PNG image data, 800 x 600
# List block devices (disks and partitions)
lsblk
# Example output:
# NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
# sda 8:0 0 500G 0 disk
# ├─sda1 8:1 0 512M 0 part /boot
# └─sda2 8:2 0 499.5G 0 part /Paths
Absolute vs Relative
An absolute path starts from root (/) and unambiguously identifies a location:
/home/alice/projects/app/src/main.pyA relative path is interpreted from the current working directory:
../projects/app/src/main.pySpecial Path Components
| Symbol | Meaning | Example |
|---|---|---|
/ | Root directory (at start) or path separator | /etc/hosts |
. | Current directory | ./script.sh |
.. | Parent directory | cd ../.. (go up two levels) |
~ | Home directory (shell expansion, not kernel) | ~/documents |
Path Resolution
When the kernel resolves a path, it walks the directory tree component by component:
Resolving: /home/alice/file.txt
1. Start at inode 2 (root /)
2. Look up "home" in root's directory entries → inode 131073
3. Look up "alice" in home's directory entries → inode 262147
4. Look up "file.txt" in alice's directory entries → inode 393281
5. Return inode 393281's dataIf any component is a symlink, the kernel restarts resolution from the symlink's target path. The kernel limits symlink depth (typically 40 levels) to prevent infinite loops.
bash
# See the real path after resolving all symlinks
realpath /usr/bin/python3
# /usr/bin/python3.11Real-World Scenarios
Where Do Logs Go?
bash
/var/log/syslog # General system log (Debian/Ubuntu)
/var/log/messages # General system log (RHEL/CentOS)
/var/log/auth.log # Authentication events
/var/log/kern.log # Kernel messages
/var/log/nginx/ # Nginx access and error logs
/var/log/journal/ # systemd journal (binary, use journalctl)Where Do Configs Live?
bash
/etc/ # System-wide configuration
~/.config/ # Per-user configuration (XDG standard)
~/.bashrc # Bash shell config
~/.ssh/ # SSH keys and config
/etc/systemd/system/ # systemd service filesTemp File Best Practices
bash
# Create a temp file safely (avoids race conditions)
tmpfile=$(mktemp)
echo "data" > "$tmpfile"
# Create a temp directory
tmpdir=$(mktemp -d)
# Clean up when done
rm "$tmpfile"
rm -rf "$tmpdir"
# Trap for cleanup on script exit
trap 'rm -rf "$tmpdir"' EXITWARNING
Never hardcode temp file paths like /tmp/myapp.tmp — another user or process could create the file first, leading to security issues (symlink attacks). Always use mktemp.
Finding What's Using Disk Space
bash
# Top 20 largest files on the system
find / -type f -exec du -h {} + 2>/dev/null | sort -hr | head -20
# Disk usage by top-level directories
du -sh /* 2>/dev/null | sort -hr
# Check if inodes are exhausted
df -iRelated
- File Permissions — Quick reference for the permission system covered in this guide
- DDD & Hexagonal Architecture in Python — How software architecture builds on top of system-level understanding