The Filesystem & Permissions
A process needs to read configuration, write logs, and be denied access to things that aren’t its business. All three of those happen through the filesystem and its permission model. If the process model is “what is running,” this page is “what it’s allowed to touch.” That second question is where most security incidents — and a surprising number of outages — are won or lost.
Everything is a file
Section titled “Everything is a file”This is the deepest idea in Unix, and it’s not a slogan. On Linux, almost everything is exposed through the filesystem as if it were a file:
- A regular file? A file.
- A directory? A special file listing other files.
- A hard disk, a terminal, a keyboard? Files under
/dev. - A running process’s details? Files under
/proc/<pid>. - A network socket, a pipe? File-like objects with file descriptors.
Why does this matter for DevOps? Because it means one set of tools and one permission model governs
everything. The same cat, grep, and chmod that work on a config file work on a device or a
kernel setting. You don’t learn a new API for each kind of resource; you learn the filesystem once.
That uniformity is what makes the shell so powerful as automation glue.
The Filesystem Hierarchy Standard (a quick tour)
Section titled “The Filesystem Hierarchy Standard (a quick tour)”Linux doesn’t scatter files randomly; it follows the FHS, a convention for what goes where. Knowing the map means you can walk onto any Linux server and know where to look.
/ the root of everything (not the root user's home!)├── bin, /usr/bin executable programs (commands)├── etc system-wide CONFIGURATION (text files — read these first)├── var│ ├── log LOGS live here → /var/log│ └── lib application state/data├── home│ └── alice a normal user's home directory (~)├── root the root user's home directory├── tmp temporary files (often wiped on reboot)├── opt optional / third-party software├── proc the kernel's live view of processes (virtual)└── dev devices (disks, terminals) as filesThree to burn into memory: /etc (configuration — where you change behavior), /var/log
(where you go when something breaks, see Logs & Troubleshooting),
and /proc (a live, virtual window into the kernel and every running process).
Users and groups: who is asking
Section titled “Users and groups: who is asking”Every process runs as a user, and every user belongs to one or more groups. Permissions are decided by asking: is the requester the file’s owner, a member of the file’s group, or everyone else? Three identities, checked in that order.
id # who am I? shows my user (uid) and groups (gids)whoami # just the usernamegroups alice # which groups alice belongs toUsers and groups are how you give a service exactly the access it needs and nothing more. Your
database runs as the postgres user; your web app as www-data. If the web app is compromised, the
attacker has the web app’s access — not the database’s, not root’s. That blast-radius limiting is
the entire point.
rwx: the permission bits
Section titled “rwx: the permission bits”Each file carries permissions for three classes (owner / group / other), each with three possible rights:
| Bit | On a file | On a directory |
|---|---|---|
r (read, 4) | view contents | list what’s inside |
w (write, 2) | modify contents | create/delete entries inside |
x (execute, 1) | run it as a program | enter (cd into) it |
ls -l shows them as a 10-character string:
-rwxr-x--- 1 alice devs 4096 Jun 22 10:00 deploy.sh│└┬┘└┬┘└┬┘ └─┬─┘ └─┬┘│ │ │ │ │ └ group: devs│ │ │ │ └────── owner: alice│ │ │ └ other: --- (no access at all)│ │ └─── group: r-x (read + execute)│ └────── owner: rwx (read + write + execute)└──────── type: - = file, d = directory, l = symlinkSo alice can edit and run deploy.sh, anyone in devs can read and run it, and everyone else is
locked out completely.
Octal: the same thing as numbers
Section titled “Octal: the same thing as numbers”Add the values per class — r=4, w=2, x=1 — and you get the octal shorthand you’ll see everywhere:
rwx = 7 rw- = 6 r-x = 5 r-- = 4 --- = 0So rwxr-x--- is 750. Common ones worth memorizing: 644 (rw-r--r--, a normal file:
owner edits, others read), 600 (rw-------, private — use this for secrets and SSH keys), and
755 (rwxr-xr-x, a program or directory everyone may use but only the owner may change).
Changing ownership and permissions: chown and chmod
Section titled “Changing ownership and permissions: chown and chmod”chmod 600 ~/.ssh/id_ed25519 # lock a private key to owner-only (ssh refuses looser perms)chmod +x deploy.sh # add execute so you can run ./deploy.shchmod -R g+w /var/www # recursively give the group write accesschown www-data:www-data app.log # set owner AND group to www-datachown -R alice /home/alice/site # recursively change ownerThe root user, and why you mostly avoid it
Section titled “The root user, and why you mostly avoid it”root (uid 0) is the superuser: it bypasses every permission check. root can read any file, kill any
process, and overwrite the kernel’s settings. That power is also the danger — a typo as root
(rm -rf / with a stray space) can erase the machine, and a process compromised while running as root
hands the attacker the whole box.
So the discipline is: don’t log in as root, and don’t run services as root. Instead, work as a
normal user and elevate for the one command that needs it with sudo:
apt update # fails: needs privilege to write system filessudo apt update # runs that single command as root, then drops backsudo -u postgres psql # run a command as a specific other usersudo is better than a root login for three reasons that are pure DevOps: it’s auditable (every
sudo is logged to /var/log/auth.log, or /var/log/secure on RHEL — you know who did what), it’s scoped (one command,
not a whole session), and it’s grantable (you can let a teammate restart one service without
handing over the keys to the kingdom).
Least privilege: the principle underneath all of it
Section titled “Least privilege: the principle underneath all of it”Every choice above — separate users per service, narrow rwx bits, sudo instead of root — is one
principle wearing different clothes: give each user and process the minimum access it needs to do its
job, and no more.
This is the manual-step thread of this page. The error-prone habit is granting broad access “to make
the error go away” — running everything as root, chmod 777 on the data directory, one shared admin
login. It works right up until something is compromised or someone fat-fingers a command, and then the
blast radius is everything. Least privilege is how you make production safer by default: when the
worst happens, it stays small. The same principle scales up later into
RBAC and network policy and the
zero-trust stance — but it starts right here, with file bits and sudo.
Under the hood — the fourth bit: setuid, setgid, and the sticky bit
Section titled “Under the hood — the fourth bit: setuid, setgid, and the sticky bit”The rwx triplets are only three of the four octal digits. There’s a leading fourth — setuid (4),
setgid (2), and the sticky bit (1) — and it answers a puzzle: how can an ordinary user change
their own password, when that means writing to root-owned /etc/shadow?
The answer is setuid. A program with the setuid bit runs with the privileges of the file’s owner,
not the user who launched it. /usr/bin/passwd is owned by root and is setuid, so running it briefly
grants root — just long enough to update one file. You’ll spot it in ls -l as an s where the owner’s
x would be:
-rwsr-xr-x 1 root root /usr/bin/passwd ← the 's' = setuid rootdrwxrwxrwt 1 root root /tmp ← the 't' = sticky bitThe sticky bit on a world-writable directory like /tmp fixes a different problem: everyone may
create files, but only a file’s owner (or root) may delete it — so users can’t delete each other’s temp
files. And setuid is double-edged: a bug in any setuid-root program is an instant road to root, which is
why such programs are a favourite target (recall Dirty COW) and why you
keep them to a minimum — audit with find / -perm -4000. setuid is least privilege done with a scalpel:
grant the exact extra power for the exact operation, instead of handing the user root.
The architect’s lens
Section titled “The architect’s lens”The permission model is something you configure deliberately on every machine, so it earns the five questions:
- Why does it exist? Because every process must read its config, write its logs, and be denied
what isn’t its business — and “everything is a file” lets one permission model (owner / group / other
×
rwx) govern regular files, devices under/dev, processes under/proc, and sockets alike. - What problem does it solve? It limits blast radius: running each service as its own user
(
postgres,www-data) means a compromise gets only that service’s access — not the database’s, not root’s. - What are the trade-offs? Fine-grained control demands discipline — the lazy “fix” (
chmod 777, run-everything-as-root, one shared admin login) makes the error vanish but strips away the very protection that contains a breach. - When should I loosen it? Almost never broadly; grant the specific user or group the specific
right, reach for a scalpel like setuid (audited with
find / -perm -4000) only when truly needed, and elevate per-command withsudorather than logging in as root. - What breaks if I ignore least privilege? A single compromised process — or a fat-fingered
rm -rf /as root — takes the whole box; the same principle scales up into RBAC and network policy and the zero-trust stance.
Check your understanding
Section titled “Check your understanding”- “Everything is a file.” Give two non-file things Linux exposes as files, and explain why that uniformity helps automation.
- You see
-rw-r-----. Who can read this file, who can write it, and what is the octal form? - A service throws a permission error reading
/etc/app/config.yml. Why ischmod 777the wrong fix, and what would you do instead? - Give three concrete reasons
sudo apt updateis preferable to logging in as root and runningapt update. - State the principle of least privilege in one sentence, then connect it to blast radius: how does running each service as its own user limit the damage from a single compromise?
Show answers
- Open-ended — e.g. a running process is exposed under
/proc/<pid>, and a device (disk, terminal) under/dev; sockets and pipes are file-like with file descriptors. The uniformity helps automation because one set of tools and one permission model (cat,grep,chmod) governs everything — you learn the filesystem once instead of a new API per resource. -rw-r-----means the owner can read and write, the group can read only, and everyone else has no access. Ownerrw- = 6, groupr-- = 4, other--- = 0, so the octal form is640.chmod 777lets anyone read, write, and execute the file — it removes the very protection that contains a breach, so it’s almost always wrong. The right fix is to grant the specific user or group (e.g. the service’s user) the specific right it needs — here, ensure the service’s user can read/etc/app/config.yml, e.g. via ownership or a640file owned by that user/group.- (1) Auditable — every
sudois logged to/var/log/auth.log, so you know who did what. (2) Scoped — it elevates for one command, then drops back, rather than a whole root session. (3) Grantable — you can let a teammate run one privileged command without handing over full root. - Give each user and process the minimum access it needs and no more. Running each service as its own user limits blast radius: if the web app (running as
www-data) is compromised, the attacker has only the web app’s access — not the database’s, not root’s — so a single compromise stays contained instead of taking the whole box. It scales up later into RBAC and network policy.