Skip to content

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 | xxd

Single 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

DirectoryPurposeExamples
/Root — the top of the entire filesystem treeEverything lives under here
/binEssential user commands needed for single-user models, cp, cat, bash
/sbinEssential system administration commandsmount, fsck, iptables
/libShared libraries needed by /bin and /sbinlibc.so, kernel modules
/bootBoot loader filesvmlinuz, initrd, GRUB config

Configuration & Variable Data

DirectoryPurposeExamples
/etcSystem-wide configuration filespasswd, fstab, nginx/nginx.conf
/varVariable data that changes during operationLogs, caches, spool files, databases
/var/logSystem and application log filessyslog, auth.log, nginx/access.log
/var/tmpTemporary files preserved across rebootsUnlike /tmp, survives restarts
/tmpTemporary files — may be cleared on rebootSession files, build artifacts

User Directories

DirectoryPurposeExamples
/homeUser home directories/home/alice, /home/bob
/rootHome directory for the root (superuser) accountRoot's personal files

Programs & Libraries

DirectoryPurposeExamples
/usrUser programs — the bulk of installed softwareSecond-level hierarchy
/usr/binNon-essential user commandsgit, python, vim
/usr/sbinNon-essential system commandscron, useradd
/usr/libLibraries for /usr/bin and /usr/sbinPython packages, shared objects
/usr/localLocally installed software (not from package manager)Custom-built tools
/usr/shareArchitecture-independent dataMan pages, documentation, icons
/optOptional/third-party software packages/opt/google/chrome

Special & Virtual Directories

DirectoryPurposeExamples
/devDevice files — interfaces to hardware and pseudo-devicessda, null, zero, urandom
/procVirtual filesystem exposing kernel and process infocpuinfo, meminfo, 1/status
/sysVirtual filesystem exposing kernel objects (drivers, devices)Device attributes, power management

Mount Points

DirectoryPurposeExamples
/mntTemporary mount point for manual mountsMounting a disk for maintenance
/mediaAuto-mounted removable mediaUSB drives, CD-ROMs
/srvData served by the systemWeb 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 -hT

File Types

Unix has seven file types. You can identify them with ls -l — the first character of the output tells you the type.

SymbolTypeDescription
-Regular fileText, binary, image, anything with content
dDirectoryA file that contains a list of name→inode mappings
lSymbolic linkA pointer to another file path
bBlock deviceBuffered access to hardware (disks, partitions)
cCharacter deviceUnbuffered access to hardware (terminals, serial ports)
pNamed pipe (FIFO)Inter-process communication channel
sSocketInter-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 special

Regular 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 column

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 directory

Device 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 +0000

Names 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:

TimestampUpdated WhenView With
atime (access)File content is readls -lu
mtime (modify)File content is changedls -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
PermissionOn FilesOn Directories
r (read)View contentsList contents (ls)
w (write)Modify contentsCreate/delete files within
x (execute)Run as programEnter 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

BitOctalOn FilesOn Directories
setuid4000Runs as file owner
setgid2000Runs as file groupNew files inherit directory's group
sticky1000Only 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 bit

INFO

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     0
bash
# View currently mounted filesystems
mount | column -t

# Or with filesystem type and human-readable sizes
df -hT

Virtual 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 process

sysfs (/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/operstate

tmpfs

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/ramdisk

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 0

Hard 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"

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/syslog

Comparison

FeatureHard LinkSymbolic Link
Same inode as targetYesNo (has its own inode)
Survives target deletionYesNo (becomes dangling)
Can cross filesystemsNoYes
Can link to directoriesNoYes
Shows target in ls -lNoYes (-> target)
SizeSame as targetLength 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/
bash
# Print current directory
pwd

# Change directory
cd /var/log          # absolute path
cd ../etc            # relative path
cd ~                 # home directory
cd -                 # previous directory

Listing & 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 f

Disk 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/alice

File 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.py

A relative path is interpreted from the current working directory:

../projects/app/src/main.py

Special Path Components

SymbolMeaningExample
/Root directory (at start) or path separator/etc/hosts
.Current directory./script.sh
..Parent directorycd ../.. (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 data

If 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.11

Real-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 files

Temp 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"' EXIT

WARNING

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 -i