CoursesLinux essentialsThe filesystem hierarchy

The filesystem hierarchy

What lives where, and why it matters.

Beginner10 min · lesson 3 of 25

Walk into any professional kitchen and you already know roughly where to look. Knives by the prep bench, cold food in the walk-in fridge, mop and bleach by the sink. Nobody handed you a map when you arrived. There is a shared convention for where things go, and once you know it, you can work in a kitchen you have never set foot in.

Linux directories follow the same idea. A written agreement called the FHS (Filesystem Hierarchy Standard, the rulebook for which kind of file belongs in which directory) lays out where configuration goes, where logs go, and where a person's files go. Learn the layout once and you can find your way around a machine you have never logged into, whether it runs Ubuntu, Debian, or Red Hat. Everything hangs off one starting point, the root of the filesystem, written as a single forward slash (/). There are no drive letters like C: here. One tree, one top, and every file is a leaf somewhere on it.

~/secopslog — bash
$ ls /
bin boot dev etc home lib lib32 lib64 libx32 lost+found media mnt opt proc root run sbin snap srv sys tmp usr var

Those names are not random, and you meet the same ones on every machine. They sort into a few families: the parts you configure, the parts that fill up on their own, the parts that belong to people, the programs themselves, and a set of live windows into the running machine. One quirk worth knowing early: on modern systems /bin, /sbin, and /lib are usually shortcuts that point into /usr, a cleanup called the usr-merge. Follow the shortcut and you land in the same place.

The tree, by what each branch is for
You configure it
/etc
services, users, network, all plain text
/boot
the kernel and bootloader
It changes on its own
/var/log
the machine's diary
/var
spools, caches, queues
/tmp
scratch space, world-writable
People
/home
each user's files and settings
/root
the administrator's home
Programs and libraries
/usr
most installed software
/bin, /sbin
commands (now links into /usr)
Live kernel views
/proc
running processes
/sys
devices and kernel settings
/dev
hardware as files
Every path on the machine hangs off / (the root). These are the families you meet first.

The settings binder: /etc

A building manager keeps a thick binder of settings: heating schedule, key-holder list, alarm codes, all on paper anyone on staff can read and edit. That is /etc (pronounced 'et-see', from et cetera). It holds the configuration for the system and its services, and almost all of it is plain text you can open, read, and change with a normal editor. There is no hidden registry and no binary blobs you need a special tool to poke at.

One of the most important files in there is /etc/passwd, the roster of every account on the machine.

/etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
sshd:x:110:65534::/run/sshd:/usr/sbin/nologin
alice:x:1000:1000:Alice Chen,,,:/home/alice:/bin/bash

Each line is one account, split into fields by colons: the username, then an x, then two numbers, then a comment, a home directory, and a login shell. The two numbers are the UID and GID (user ID and group ID, the machine's real internal names for a person and their group). UID 0 is root, the all-powerful administrator account. The x is a placeholder. The real password hash sits in a separate file, /etc/shadow, because /etc/passwd is readable by everyone and you do not want password hashes readable by everyone. Notice the accounts ending in nologin. Those are service accounts, like the web server's www-data, deliberately given no usable login shell so a stolen service credential cannot open an ordinary session.

~/secopslog — bash
$ ls -l /etc/passwd /etc/shadow
-rw-r--r-- 1 root root 2891 Jul 10 09:14 /etc/passwd -rw-r----- 1 root shadow 1601 Jul 10 09:14 /etc/shadow

Look at the permissions. /etc/passwd is readable by everyone (rw-r--r--). /etc/shadow is not (rw-r-----); only root and the shadow group can read it. That split is deliberate, and it is one of the first things to check on a machine you are hardening. A world-readable /etc/shadow is a serious finding, because anyone who can read the hashes can walk away and crack them offline at their leisure.

Things that grow: /var

/var (short for variable) is the drawer that fills up while the machine runs: print spools, package caches, mail queues, and above all logs. The single most useful place in here is /var/log, the machine's diary of what happened and when.

~/secopslog — bash
$ ls /var/log
alternatives.log apt auth.log btmp cloud-init.log dpkg.log journal kern.log private syslog ufw.log wtmp

For a security engineer, /var/log/auth.log (on Debian and Ubuntu; it is /var/log/secure on Red Hat) is the page you turn to first. Every login, every sudo (the command that runs something as the administrator), and every SSH attempt (SSH is the program that lets you log in over the network) lands here. Watch what a few seconds of a real internet-facing server looks like.

~/secopslog — bash
$ grep 'Failed password' /var/log/auth.log | tail -3
Jul 17 08:22:14 web01 sshd[20455]: Failed password for invalid user admin from 185.220.101.47 port 41022 ssh2 Jul 17 08:22:17 web01 sshd[20455]: Failed password for invalid user admin from 185.220.101.47 port 41288 ssh2 Jul 17 08:22:19 web01 sshd[20461]: Failed password for root from 185.220.101.47 port 41302 ssh2

That is an automated attacker trying common usernames and passwords against SSH, thousands of times an hour, on any machine with a public address. Two lessons live in that output. First, this is why you turn off password logins and use keys instead. Second, because /var/log is an ordinary directory on disk, an attacker who gets in will often try to erase these very lines to cover their tracks. That is why serious setups copy logs off the machine as they are written, to somewhere the attacker cannot reach.

Where people live: /home and /root

/home is the apartment building. Every ordinary user gets a directory under it, /home/alice for Alice, /home/raj for Raj, and inside it their files and their personal settings. Those per-user settings hide in 'dotfiles', files whose names start with a dot so they stay out of the way in a normal listing. The administrator does not live here. root has its own home at the top level, /root, kept separate so the machine can still be repaired even when /home sits on a broken disk.

One dotfile directory matters more than the rest for security: .ssh.

~/secopslog — bash
$ ls -la ~/.ssh
total 20 drwx------ 2 alice alice 4096 Jul 15 11:02 . drwxr-xr-x 8 alice alice 4096 Jul 17 07:40 .. -rw------- 1 alice alice 571 Jul 15 11:02 authorized_keys -rw------- 1 alice alice 411 Jul 15 11:02 id_ed25519 -rw-r--r-- 1 alice alice 103 Jul 15 11:02 id_ed25519.pub

authorized_keys is the guest list for logging in as this user without a password; anyone whose key is in that file can walk in. The id_ed25519 file is a private key, the equivalent of the physical key to a lock, and its permissions are 600 (readable only by its owner) for a reason. SSH refuses to use a private key other people can read, and it refuses to trust an authorized_keys file the group or world can write to. If you ever add a key and logins keep failing for no obvious reason, wrong permissions on this directory are the usual culprit.

The machine as files: /proc and /sys

Here is the part that surprises people. /proc and /sys look like directories full of files, but nothing in them is stored on any disk. They are a live dashboard, a wall of gauges the kernel (the core part of the operating system that talks directly to the hardware) draws for you on demand, dressed up as files so you can read them with the same tools you use for everything else. Open a file in /proc and the kernel builds the answer in that instant.

~/secopslog — bash
$ cat /proc/loadavg
0.52 0.58 0.59 2/548 9021

That one line is the current system load. The first three numbers are how busy the machine has been over the last one, five, and fifteen minutes. Then comes a count of how many processes are running right now out of the total that exist, and finally the ID of the most recently created process. No file on disk holds that; the kernel produced it the moment you asked.

Every running program gets its own directory under /proc, named after its PID (process ID, the number the kernel uses to tell one running program from another). Point at the main SSH daemon, the oldest sshd process and the one that sits and waits for new connections, then read its status file.

~/secopslog — bash
$ cat /proc/$(pgrep -o sshd)/status | head
Name: sshd Umask: 0022 State: S (sleeping) Tgid: 812 Ngid: 0 Pid: 812 PPid: 1 TracerPid: 0 Uid: 0 0 0 0 Gid: 0 0 0 0

State, memory, the user it runs as (Uid 0 means root), and its parent process are all live. PPid 1 points at PID 1, which is special: it is the very first program the kernel starts, systemd on modern machines (the program that brings up everything else), and every other process descends from it. Read its directory and you get the same wall of live state.

~/secopslog — bash
$ ls /proc/1/
arch_status attr autogroup auxv cgroup cmdline comm cwd environ exe fd fdinfo io limits loginuid maps mem mountinfo mounts net ns oom_score root sched smaps stack stat statm status syscall task wchan

This is the 'everything is a file' idea that runs through Linux, and it is why cat and grep, which you already know, double as tools for inspecting the live kernel. /sys is its sibling, a similar tree that exposes hardware and kernel settings. It can even tell you, in plain text, whether the processor is protected against the well-known Meltdown and Spectre chip flaws.

~/secopslog — bash
$ grep . /sys/devices/system/cpu/vulnerabilities/*
/sys/devices/system/cpu/vulnerabilities/meltdown:Mitigation: PTI /sys/devices/system/cpu/vulnerabilities/spectre_v1:Mitigation: usercopy/swapgs barriers and __user pointer sanitization /sys/devices/system/cpu/vulnerabilities/spectre_v2:Mitigation: Retpolines, IBPB: conditional, IBRS_FW, STIBP: disabled, RSB filling

For a DevSecOps engineer that is a free, scriptable way to check a whole fleet of servers, with no extra software installed, and confirm every one of them is running with its chip mitigations switched on. A line that reads 'Vulnerable' instead of 'Mitigation' is a machine that needs attention.

The shared scratch table: /tmp

/tmp is the communal workbench. Any program, run by any user, can drop files on it, which is exactly what makes it handy and exactly what makes it dangerous. Look at its permissions.

~/secopslog — bash
$ ls -ld /tmp
drwxrwxrwt 18 root root 4096 Jul 17 08:30 /tmp

The row of letters ends in t, not the usual x. That is the sticky bit, a small but important safety catch. In a directory everyone can write to, the sticky bit says only the owner of a file (or root) may delete or rename it, so users cannot quietly wipe out each other's files. What the sticky bit does not do is stop the writing in the first place. Anyone can still create files here, which is why /tmp is a favorite landing spot for attackers to stage tools once they get a foothold.

Predictable /tmp names are an attack, and noexec is why staging fails
The classic /tmp bug is a program that writes to a fixed, guessable path like /tmp/app.lock or /tmp/output.txt. An attacker who creates that file first (or drops a symlink pointing somewhere else) can trick the program into overwriting a file it should never touch, a race known as a symlink attack. Write temp files with mktemp, which picks an unpredictable name and safe permissions for you. And know that many hardened systems mount /tmp with the noexec option, meaning a binary copied there cannot be run. If your own install script stages a helper in /tmp and it dies with 'Permission denied' on a file you can clearly read, noexec is the reason. Stage under /usr/local or your home directory instead.
Quick check
01In /etc/passwd, the www-data account has /usr/sbin/nologin where a normal user would have /bin/bash. What is that doing?
Incorrect — The line is right there in the file, which is readable by everyone; nologin hides nothing.
Correct — A nologin shell shrinks what a compromised service account can be used for.
Incorrect — Password hashes live in /etc/shadow and have nothing to do with the shell field.
Incorrect — If anything it does the opposite, by denying a usable shell.
02The lesson reads system load with cat /proc/loadavg. What is unusual about files under /proc compared with files under /home?
Incorrect — nothing under /proc is stored on disk at all.
Correct — /proc is a live view the kernel generates on demand, not stored data.
Incorrect — many /proc files, such as loadavg, are readable by ordinary users.
Incorrect — they occupy no disk space because they are generated on demand, not stored.
03On a machine you are hardening you run ls -l /etc/shadow and see -rw-r--r-- 1 root shadow 1601 ... /etc/shadow. Why is this a serious finding?
Incorrect — /etc/shadow is normal and expected; the problem is its permissions, not its existence.
Incorrect — root ownership with group shadow is the normal, correct setup; the read bit is the issue.
Incorrect — hashes are deliberately kept in /etc/shadow precisely so they are not world-readable like /etc/passwd.
Correct — shadow should be rw-r----- ; a world-readable copy exposes every hash to offline cracking.

Try this

Work through “The shared scratch table: /tmp” yourself on a sandbox you can throw away, following the commands above in order. Then break one step deliberately and re-run, so you have seen the failure before it finds you.

Takeaway

The trap worth remembering here: predictable /tmp names are an attack, and noexec is why staging fails. Check that on your own systems before you need to, because it is cheaper to find on a quiet afternoon than during an incident.

Related