Linux Lesson 6 of 47

Users, Groups & Permissions: the rwx Model, chmod, chown, umask & sudo

Linux is a multi-user operating system. Even on a laptop that only you touch, dozens of accounts already exist — root, www-data, sshd, systemd-network, nobody — and every file, every process, and every network socket is owned by one of them. Permissions are the fence between those owners. Get the fence right and a compromised web server can’t read your SSH keys. Get it wrong and one bad chmod exposes a database password to the entire machine.

This is the lesson beginners most often get wrong, usually in the same three ways: they “fix” a Permission denied by running chmod 777 (which fixes the symptom and opens a hole), they can’t explain why they were able to delete a file they had no write permission on, and they log in as root for everything because they never learned sudo. By the end of this lesson none of those will be mysterious. We’ll build the model from the ground up — identity, the account databases, the rwx bits on files and directories, the octal shortcut, umask, the special bits, and sudo — with real commands and real output at every step.

Read it slowly and type the examples on a throwaway VM, container, or WSL. This is muscle memory you’ll use every single day of a Linux career.

Why this matters

Three concrete situations where this lesson pays off immediately:

The mental model to hold onto: identity meets label meets decision. A process has an identity (a UID and a set of GIDs). Every file carries a label (an owner, a group, and nine permission bits). On every access, the kernel makes one decision by picking a single class and checking only that class’s bits. Everything below is detail on those three things.

Who you are: identity, UID and GID

To Linux you are not your username — you are a UID (user ID), a number. The username is a human-friendly label mapped to that number in a text file. Likewise every group is a GID. Names are for people; the kernel only ever compares numbers.

Run these on any Linux box:

whoami          # your login name
# vinod

id              # your full numeric identity
# uid=1000(vinod) gid=1000(vinod) groups=1000(vinod),27(sudo),999(docker)

groups          # just the group names you belong to
# vinod sudo docker

Read that id output carefully — it’s the most important line in this lesson:

root is UID 0

The account with UID 0 is root, the superuser. The name “root” is just convention — any account with UID 0 is all-powerful. Root bypasses the normal permission checks entirely (with one nuance we’ll cover under the special bits). This is why you don’t hand out root, and why sudo exists.

Accounts fall into three number ranges. The exact boundaries live in /etc/login.defs (UID_MIN, SYS_UID_MIN), but the modern defaults are near-universal:

UID / GID Class Who lives here Login?
0 Superuser root Yes (usually disabled for SSH)
1999 System / service daemon, www-data, sshd, systemd-*, mysql No — nologin/false shell
100060000 Regular (human) You and your teammates Yes
65534 The nobody account Unprivileged fallback (NFS, sandboxes) No

Service accounts get UIDs below 1000 and a non-interactive shell (/usr/sbin/nologin or /bin/false) so that even if their service is compromised, nobody can log in as them. That separation is a core Linux security principle: one service, one unprivileged user.

Real vs effective identity (the short version)

A process actually carries two user IDs: the real UID (who launched it) and the effective UID (EUID) — the one the kernel uses for permission checks. They’re normally identical. The setuid mechanism (later in this lesson) is exactly the trick that makes them differ, and it’s how a normal user can run a program that briefly acts as root. For now: permission checks use the effective UID/GID. A process’s identity travels with it — see the companion lesson on processes, jobs and signals for how a process inherits and carries these IDs.

The account databases: passwd, shadow and group

User and group definitions are plain text files in /etc — no database server, just colon-separated lines you can read with cat (or, better, getent, which also consults LDAP/SSSD if the box uses network accounts). These files live under /etc, part of the standard layout covered in the Filesystem Hierarchy lesson.

/etc/passwd — one line per account

Despite the name, /etc/passwd holds no passwords anymore (that’s /etc/shadow). It’s world-readable and maps names to numbers. A line looks like:

getent passwd vinod
# vinod:x:1000:1000:Vinod H,,,:/home/vinod:/bin/bash

Seven colon-separated fields:

# Field Example Meaning
1 Login name vinod The username.
2 Password x Always x today — the real hash is in /etc/shadow. */! here would mean “no valid password”.
3 UID 1000 Numeric user ID. 0 = root.
4 GID 1000 Numeric primary group ID.
5 GECOS Vinod H,,, Comment field: full name, office, phone (comma-separated). Cosmetic.
6 Home directory /home/vinod Where you land at login; $HOME.
7 Login shell /bin/bash Program run at login. /usr/sbin/nologin = can’t log in interactively.

/etc/shadow — the hashed passwords and aging rules

/etc/shadow is readable only by root (mode 640, group shadow) — that’s the whole point of splitting it out. It holds the password hash and the account-aging policy:

sudo getent shadow vinod
# vinod:$y$j9T$Ha3l...$k2Vd...:19834:0:99999:7:::

Nine fields:

# Field Example Meaning
1 Login name vinod Must match /etc/passwd.
2 Hashed password $y$j9T$… Salted hash (see below). !/* = locked/no login. !! = never set. Empty = no password required (dangerous).
3 Last change 19834 Days since 1 Jan 1970 the password was last changed. 0 = must change at next login.
4 Min age 0 Days you must wait before changing again.
5 Max age 99999 Days before the password expires and must be changed.
6 Warn 7 Days before expiry to start warning the user at login.
7 Inactive (empty) Days after expiry the account is still usable before it’s locked.
8 Expire (empty) Absolute account-expiry date (days since epoch) — the whole account dies.
9 Reserved (empty) Unused.

Field 2 has the form $id$salt$hash, where the leading $id$ names the hashing algorithm: $1$ is legacy MD5 (never use it), $5$ is SHA-256, $6$ is SHA-512 (a common RHEL/Rocky default), $2b$/$2y$ is bcrypt, and $y$ is yescrypt — the modern default on Debian 11+ and Ubuntu 22.04+.

/etc/group — group definitions and membership

getent group docker
# docker:x:999:vinod,deploy
# Field Example Meaning
1 Group name docker Human-friendly label.
2 Password x Rarely used; group passwords live in /etc/gshadow.
3 GID 999 Numeric group ID.
4 Members vinod,deploy Comma-separated secondary members. Your primary group membership comes from /etc/passwd field 4 and is not listed here.

That last point trips people up: getent group vinod may show an empty member list even though vinod is clearly in the vinod group — because it’s his primary group, recorded in passwd, not in group’s member field.

Managing users and groups

Two families of commands exist. useradd/groupadd/usermod are the low-level tools present on every distro and used in scripts. adduser/addgroup are Debian/Ubuntu’s friendly interactive Perl wrappers (on RHEL, adduser is merely a symlink to useradd). Prefer the low-level tools for portability.

⚠️ All of these need root, so prefix with sudo.

Task Portable (all distros) Debian/Ubuntu friendly
Create user + home + shell useradd -m -s /bin/bash alice adduser alice (interactive)
Create a system user useradd -r -s /usr/sbin/nologin svc_app adduser --system svc_app
Set a password passwd alice passwd alice
Create a group groupadd developers addgroup developers
Delete user + home userdel -r alice deluser --remove-home alice

useradd without -m does not create the home directory — a classic surprise where the user logs in and lands in / with no dotfiles. Always pass -m for a human account.

usermod — and the one flag everyone gets wrong

usermod modifies an existing account:

Flag Does Note
-aG grp Append to secondary groups The safe way to add a group.
-G g1,g2 Set (replace) all secondary groups ⚠️ Overwrites — omit a group and the user loses it.
-g grp Change primary group Affects the GID of newly created files.
-l newname Rename the login Does not rename the home dir.
-d /new/home -m Move the home directory -m migrates existing files.
-s /bin/zsh Change login shell Use /usr/sbin/nologin to disable login.
-L / -U Lock / unlock the password -L prepends ! to the hash.
-e YYYY-MM-DD Set account expiry Empty string clears it.

The single most common beginner mistake in all of user management:

# WRONG — you meant to *add* docker, but -G REPLACES every secondary group.
# alice is now ONLY in docker; she just lost sudo, adm, and everything else.
sudo usermod -G docker alice

# RIGHT — -a means append. Add docker, keep the rest.
sudo usermod -aG docker alice

Always use -aG, never bare -G, unless you truly mean “set the complete list.” And one more gotcha: group membership is evaluated at login. After usermod -aG, the user’s current shell still doesn’t have the new group — they must log out and back in, or run newgrp docker to start a subshell with the group active. Verify with id.

Passwords and aging: passwd and chage

sudo passwd alice        # set/reset alice's password (prompts twice)
passwd                   # change YOUR OWN password
Command Effect
passwd -l alice Lock the account (disable password login).
passwd -u alice Unlock it.
passwd -e alice Expire now — force a change at next login.
passwd -S alice Show status (locked? last change? aging).
chage -l alice List all aging settings, human-readable.
chage -M 90 alice Password expires every 90 days.
chage -m 1 alice Minimum 1 day between changes.
chage -W 7 alice Warn 7 days before expiry.
chage -E 2026-12-31 alice Whole account expires on that date.
chage -d 0 alice Force a password change at next login.

⚠️ passwd -d alice deletes the password, leaving the account able to log in with no password. Almost never what you want.

Group tools and inspecting identity

Create and delete groups with groupadd developers and groupdel developers (a group can’t be deleted while it’s still someone’s primary group). To manage membership you can also use gpasswd -a alice developers to add and gpasswd -d alice developers to remove — a convenient alternative to usermod -aG. To inspect identity: id alice shows a user’s full UID/GID/groups, groups alice shows just the group names, and newgrp developers starts a subshell whose active primary group is developers (handy right after you’ve added yourself to a group and don’t want to log out).

userdel -r alice removes the account and its home directory and mail spool. ⚠️ -r is destructive and irreversible — the user’s files are gone. Files elsewhere on the system that alice owned become “orphaned” (owned by a now-unused UID); find / -uid <old-uid> locates them.

The permission model: three classes, three perms

Now the heart of the lesson. Every file and directory carries nine permission bits, arranged as three classes each with three perms:

ls -l prints them as a 10-character string:

ls -l report.txt
# -rw-r--r-- 1 vinod staff 4096 Jul  9 10:12 report.txt

Decoding -rw-r--r--:

Position Chars Meaning
1 - File type: - file, d directory, l symlink, c/b device, s socket, p pipe.
2–4 rw- Owner (user) class: read + write, no execute.
5–7 r-- Group class: read only.
8–10 r-- Other class: read only.

The two names after the bits — vinod staff — are the file’s owner and group. So this file: owner vinod may read/write; anyone in group staff may read; everyone else may read.

The crucial subtlety, and the one the diagram below makes concrete: the kernel checks exactly one class. It asks, in order, “are you the owner? then only owner bits apply. Else are you in the group? then only group bits apply. Else other bits apply.” The first matching class wins and no other is consulted — even if a later class would grant more. This produces the classic surprise: if you own a file whose mode is ----rw-r-- (owner gets nothing, group gets read/write), you — the owner — are matched to the owner class and denied, even though your group would have allowed it.

What r/w/x mean — on a FILE

Perm On a file Concretely lets you
r Read cat, less, cp the file’s contents.
w Write Modify or truncate the contents (append, edit, overwrite).
x Execute Run it as a program or script. A script needs r+x; a compiled binary needs only x.

Note what write on a file does not let you do: it does not let you delete or rename the file. That power lives on the directory, which is the single biggest source of confusion for beginners.

What r/w/x mean — on a DIRECTORY (the surprise)

A directory is really a list of names mapped to inodes. So the bits mean something different:

Perm On a directory Concretely lets you
r Read the listing ls the directory to see the names. Without x too, you get names but no details.
w Modify the listing Create, delete, and rename entries inside it. This is the power to delete files.
x Traverse / enter cd into it and access items by name (cat dir/file). Also called the “search” bit.

Read that w-on-a-directory row again, because it explains the eternal beginner riddle:

“Why can I delete a file I have no write permission on — or can’t delete one I own?”

Deleting a file is really removing its name from the directory listing. That’s a write to the directory, not a write to the file. So:

This is why /tmp needs the sticky bit (below): the directory is world-writable so anyone can create temp files, but the sticky bit stops you deleting other people’s files. And it’s why x without r on a directory is useful: you can reach dir/known-file if you know its exact name, but you can’t ls to discover what’s there — a common pattern for locked-down home directories. Deleting and renaming are covered hands-on in the files & directories lesson.

The decision, visualized

When a process touches a file, the kernel walks a short decision path: take the process’s effective UID/GID, pick the one applicable class (owner → else group → else other), and check whether that class holds the requested r/w/x bit — with root (UID 0) bypassing the check and setuid having already changed the effective UID upstream.

Linux file-permission decision path showing a process with its effective UID and GID arriving at a target file, the kernel selecting exactly one class — owner, else group, else other — then checking that single class's rwx bits, with annotations for the setuid bit raising the effective UID and root (UID 0) bypassing the check, ending in an allow (check) or deny (EACCES alert) verdict

Trace it once and it sticks: identity enters on the left, exactly one class is chosen in the middle, one bit is checked on the right, and the answer is allow or deny — with root and setuid as the two ways the normal rules bend.

Reading and changing permissions: symbolic and octal

There are two notations for permissions, and you need both: symbolic (letters — great for relative tweaks) and octal (numbers — great for stating an absolute mode). chmod (change mode) accepts either.

Octal: three digits, each 0–7

Give each perm a value — r = 4, w = 2, x = 1 — and add them up per class. One octal digit (0–7) encodes one class’s three bits:

Octal Binary Symbolic Perms granted
7 111 rwx read + write + execute
6 110 rw- read + write
5 101 r-x read + execute
4 100 r-- read only
3 011 -wx write + execute
2 010 -w- write only
1 001 --x execute only
0 000 --- nothing

A full mode is three digits: owner, group, other. To read rw-r--r--: owner rw- = 4+2 = 6, group r-- = 4, other r-- = 4644. To build “owner full, group read+execute, other nothing”: 7, 5, 0 → 750. That’s the entire trick.

The modes you’ll use constantly:

Octal Symbolic Typical use Reasoning
644 rw-r--r-- Normal files (docs, config) Owner edits; everyone reads.
755 rwxr-xr-x Directories; executables/scripts All can enter/run; only owner writes.
600 rw------- Private files (~/.ssh/id_ed25519) Owner only; SSH requires this.
700 rwx------ Private directories (~/.ssh) Owner only can enter.
640 rw-r----- Group-readable secrets/config Owner writes, group reads, other nothing.
750 rwxr-x--- Group-shared directory, no outsiders Group can enter; other locked out.
664 rw-rw-r-- Group-collaborative files Owner + group write; other reads.
775 rwxrwxr-x Group-collaborative directory Owner + group create/delete.
400 r-------- Read-only key material No accidental edits.
777 rwxrwxrwx ⚠️ Almost always wrong Everyone can write — a security hole.

⚠️ chmod 777 is the cargo-cult “fix” that beginners reach for. It does make “Permission denied” go away — by letting every account on the system modify the file or fill the directory. The correct fix is to grant the minimum bit to the right class (usually via correct ownership + 640/750). Never leave 777 on anything real.

Symbolic: relative edits with letters

Symbolic mode reads as chmod [who][op][perm]. The who is one or more of u (owner), g (group), o (other), or a (all — the default if you omit it). The op is + (add), - (remove), or = (set exactly, clearing whatever else was there). The perm is r, w, or x, plus three specials you’ll meet shortly: X, s (setuid/setgid), and t (sticky).

X (capital) is a gem: “execute only if it’s a directory or already has some execute bit.” It lets you chmod -R a+rX a tree to make directories traversable and scripts runnable without slapping execute on every plain data file.

chmod u+x deploy.sh        # add execute for the owner
chmod go-w report.txt      # remove write from group AND other
chmod a=r notes.txt        # set EXACTLY read for everyone (clears w/x)
chmod u=rw,go=r config.ini # multiple clauses, comma-separated → 644
chmod -R a+rX,go-w website/ # recursive: dirs+scripts executable, no group/other write

chmod on many files at once uses -R (recursive). ⚠️ Never run chmod -R 777 / or chmod -R from the wrong directory — recursive mode changes are one of the fastest ways to make a system unbootable or insecure. Double-check your target path before pressing Enter.

Changing ownership: chown and chgrp

Permissions decide what each class can do; ownership decides which class you are. Only root can give a file away to another user (chown); a regular owner may change only the group, and only to a group they belong to.

The forms are quick to learn: chown alice file changes the owner; chown alice:devs file sets owner and group in one shot; chown :devs file (note the leading colon) or the dedicated chgrp devs file changes only the group; chown -R alice:devs /srv/app re-owns a whole tree recursively; and chown --reference=a.txt b.txt copies a.txt’s owner and group onto b.txt.

# Give a web directory to the web user and its group, recursively.
sudo chown -R www-data:www-data /var/www/html

# Fix a private key that somehow ended up group-readable.
chmod 600 ~/.ssh/id_ed25519 && ls -l ~/.ssh/id_ed25519
# -rw------- 1 vinod vinod 411 Jul  9 10:20 /home/vinod/.ssh/id_ed25519

umask: the filter for new files

When you create a file, why does it come out 644 and not 666? Because of umask — a mask that subtracts permission bits from the base defaults at creation time.

umask            # show current mask
# 0022
umask -S         # show it symbolically
# u=rwx,g=rx,o=rx

With the common 022, the mask removes write from group and other:

umask New file becomes New dir becomes Who uses it
022 644 (rw-r--r--) 755 (rwxr-xr-x) System default; single-admin boxes.
002 664 (rw-rw-r--) 775 (rwxrwxr-x) Default for regular users on Debian/RHEL (user-private-group scheme) — enables group collaboration.
027 640 (rw-r-----) 750 (rwxr-x---) Hardened: nothing for “other”.
077 600 (rw-------) 700 (rwx------) Maximum privacy; each user isolated.

Because the file base is 666 (no execute bit to begin with), umask can never make a new file executable — that’s why fresh scripts always need an explicit chmod +x. Set your session mask with umask 027; make it permanent by adding that line to ~/.bashrc (per-user) or /etc/profile / /etc/login.defs (system-wide).

Special bits: setuid, setgid and the sticky bit

Beyond the nine standard bits sit three special bits, encoded as an optional fourth (leading) octal digit — setuid = 4, setgid = 2, sticky = 1. They change how the perms behave, and they surface in ls -l by overloading the execute positions with s/S (setuid/setgid) or t/T (sticky):

Bit Octal On a file On a directory Shows in ls -l as
setuid 4000 Run with the file owner’s effective UID (ignored on Linux) s in owner-exec slot: -rwsr-xr-x
setgid 2000 Run with the file group’s effective GID New files/subdirs inherit the directory’s group s in group-exec slot: -rwxr-sr-x / drwxr-sr-x
sticky 1000 (ignored on modern Linux) Only a file’s owner (or dir owner or root) may delete/rename it t in other-exec slot: drwxrwxrwt

A capital S/T means the special bit is set but the underlying execute bit is not — usually a mistake worth noticing.

setuid in the wild — passwd. How can an ordinary user change their own password when that means writing to root-owned /etc/shadow? Because passwd is setuid-root:

ls -l /usr/bin/passwd
# -rwsr-xr-x 1 root root 59976 Feb  6  2024 /usr/bin/passwd

The s in the owner slot means: whoever runs passwd, the process’s effective UID becomes root (the file’s owner) for its lifetime — just long enough to update the shadow file, with the program’s own logic ensuring you only change your entry. ⚠️ setuid-root binaries are a top security concern; audit them with find / -perm -4000 -type f 2>/dev/null.

setgid for shared directories — the teammate problem, solved. Put a shared group on a directory and set the setgid bit; now every file created inside inherits that group automatically, so both teammates can read each other’s work:

sudo groupadd project
sudo usermod -aG project alice
sudo usermod -aG project bob
sudo mkdir /srv/project
sudo chgrp project /srv/project
sudo chmod 2775 /srv/project           # 2 = setgid; 775 = group can write
ls -ld /srv/project
# drwxrwsr-x 2 root project 4096 Jul  9 10:30 /srv/project

The s in the group slot is setgid. Any file alice or bob creates in /srv/project is group-owned by project (not by their personal primary group), so the other can read/edit it. Pair with umask 002 so the group write bit survives.

The sticky bit — why /tmp is safe. /tmp must be writable by everyone (any program creates temp files there), but you must not be able to delete my temp file. The sticky bit does exactly that:

ls -ld /tmp
# drwxrwxrwt 10 root root 4096 Jul  9 10:31 /tmp

The trailing t means: even though the directory is rwxrwxrwx (anyone can create), you can only delete or rename files you own. Set it yourself with chmod +t dir or chmod 1777 dir.

sudo: run one command as another user, safely

You almost never log in as root. Instead you use sudo (“substitute user do”) to run one command with elevated privilege. Why the indirection?

Common invocations:

Command What it does
sudo cmd Run cmd as root.
sudo -u alice cmd Run cmd as alice, not root.
sudo -i Start a login root shell (loads root’s env, like su -).
sudo -s Start a root shell keeping your environment.
sudo -l List what you are allowed to run.
sudo -k Forget the cached credential (next sudo re-prompts).
sudo -v Refresh the credential timestamp (default cache ~15 min).
sudo !! Re-run your previous command with sudo (shell history trick).

Who may sudo: the wheel / sudo group

Membership in one administrative group grants sudo rights out of the box — but the group name differs by family. On Debian/Ubuntu it’s the sudo group (sudo usermod -aG sudo alice); on RHEL/Fedora/Rocky it’s the wheel group (sudo usermod -aG wheel alice). Add a user to the right one — then have them re-login — and they can immediately escalate.

Reading a sudoers line

Rules live in /etc/sudoers and drop-in files under /etc/sudoers.d/. ⚠️ Never edit /etc/sudoers with a plain editor — a syntax error can lock everyone out of root. Use visudo, which validates the file on save and refuses to write a broken one.

A sudoers rule has this grammar:

who    where = (as_whom[:as_group])   [tags]   what
Field In alice ALL=(ALL:ALL) NOPASSWD: /usr/bin/systemctl Meaning
who alice (or %group) The user, or a group prefixed with %.
where ALL On which hosts the rule applies (relevant for shared sudoers).
as_whom (ALL:ALL) Which target user and group they may become.
tags NOPASSWD: Optional; e.g. run without re-entering a password.
what /usr/bin/systemctl The exact command(s) permitted; ALL = anything.

Worked examples:

# Full admin, must enter own password. This is what wheel/sudo membership expands to:
%wheel   ALL=(ALL:ALL)  ALL

# A deploy user may restart nginx as root, no password, and nothing else:
deploy   ALL=(root)  NOPASSWD: /usr/bin/systemctl restart nginx

# alice may run backups as the 'postgres' user:
alice    ALL=(postgres)  /usr/local/bin/pg_backup.sh

The middle rule is the least-privilege ideal: deploy gets exactly one root action and cannot, for example, sudo -i into a root shell. Prefer many narrow rules in /etc/sudoers.d/ over granting blanket ALL.

Hands-on lab

A self-contained lab you can run on any throwaway Linux VM, container (docker run -it --rm ubuntu bash), or WSL. You’ll need sudo/root. Every step has the command, what you should see, and a one-line “what just happened.”

1. Know yourself.

id; umask
# uid=1000(you) gid=1000(you) groups=1000(you),27(sudo)
# 0022

What happened: you confirmed your UID, primary group, secondary groups, and the mask applied to new files.

2. Create a shared group and two users.

sudo groupadd project
sudo useradd -m -s /bin/bash alice
sudo useradd -m -s /bin/bash bob
sudo passwd alice          # set a password when prompted

What happened: two regular accounts (UID ≥ 1000) with home dirs and a shell, plus an empty project group.

3. Read the databases you just changed.

getent passwd alice
# alice:x:1001:1001:...:/home/alice:/bin/bash
sudo getent shadow alice | cut -c1-25
# alice:$y$j9T$....
getent group project
# project:x:1002:

What happened: you saw alice’s passwd line, the hashed password living in root-only shadow, and the still-memberless project group.

4. Add both users to the group — the right way — and verify.

sudo usermod -aG project alice
sudo usermod -aG project bob
id alice
# uid=1001(alice) gid=1001(alice) groups=1001(alice),1002(project)

What happened: -aG appended project without wiping alice’s other groups. (In a real login, alice must re-login or newgrp project before the group is active in her shell.)

5. Build a setgid shared directory.

sudo mkdir /srv/project
sudo chgrp project /srv/project
sudo chmod 2775 /srv/project
ls -ld /srv/project
# drwxrwsr-x 2 root project 4096 ... /srv/project

What happened: the s in the group slot means new files inside inherit the project group, so alice and bob can collaborate.

6. Prove group inheritance.

sudo -u alice touch /srv/project/from-alice.txt
ls -l /srv/project/from-alice.txt
# -rw-r--r-- 1 alice project 0 ... from-alice.txt

What happened: the file’s group is project (inherited from the setgid dir), not alice’s personal group — that’s the whole point.

7. See the directory-write rule in action (the “delete a file you don’t own” demo).

sudo chmod 1777 /srv/project        # world-writable + sticky (like /tmp)
sudo -u bob rm -f /srv/project/from-alice.txt
# rm: cannot remove '/srv/project/from-alice.txt': Operation not permitted

What happened: bob can write the directory, so ordinarily he could delete any entry in it — but the sticky bit overrides that and denies the unlink because bob doesn’t own the file. (-f skips the “write-protected?” prompt so you see the sticky-bit denial itself.) Remove the sticky bit (chmod 0775) and bob could delete alice’s file despite not owning it — exactly the danger the sticky bit exists to prevent.

8. Symbolic vs octal chmod.

echo 'secret=42' | sudo tee /srv/project/app.env >/dev/null
sudo chmod 640 /srv/project/app.env       # octal: owner rw, group r, other none
sudo chmod g+r,o-r /srv/project/app.env   # symbolic tweak (already there — no-op)
ls -l /srv/project/app.env
# -rw-r----- 1 root project 10 ... app.env

What happened: 640 locks the secret to the owner (write) and project group (read); “other” gets nothing — the correct pattern for a shared secret, not chmod 777.

9. Try sudo scoping.

sudo -l                    # what may YOU run?
sudo -u alice whoami       # → alice
sudo whoami                # → root

What happened: you ran commands as another regular user and as root, and listed your own sudo rights.

10. Clean up. ⚠️ userdel -r deletes home directories.

sudo userdel -r alice
sudo userdel -r bob
sudo groupdel project
sudo rm -rf /srv/project

What happened: the accounts, their homes, the group, and the lab directory are gone. (Files they owned elsewhere would now show a bare numeric UID.)

Common mistakes and troubleshooting

Symptom Likely cause Fix
Permission denied running a script No x bit, or wrong interpreter chmod +x script.sh; confirm the shebang and that you have r too.
Permission denied reading a file you can ls You’re in the wrong class, or missing x on a parent dir ls -l the file; check every parent has x with namei -l /path/to/file.
Added user to a group but access still denied Group membership only applies at login Log out/in, or newgrp <group>; verify with id.
usermod -G removed the user from other groups Used -G (set) instead of -aG (append) Re-add the lost groups; always use -aG.
New user can’t log in / lands in / Created without -m (no home) or with nologin shell useradd -m -s /bin/bash; fix shell with usermod -s.
SSH refuses your key: “bad permissions” ~/.ssh or the private key is group/other-accessible chmod 700 ~/.ssh; chmod 600 ~/.ssh/id_*.
Web server 403 on a file that exists The service user (www-data) isn’t owner/group, or a parent dir lacks x Reason as www-data; fix ownership/chmod on the file and its parents.
Can delete a file you didn’t create You have write on the directory Expected. Add the sticky bit (chmod +t dir) to restrict deletes to owners.
sudo: command not found or “not in sudoers” User isn’t in sudo/wheel usermod -aG sudo alice (Debian) / wheel (RHEL) as root.

The three nastiest gotchas, in prose:

  1. chmod 777 “fixes” nothing safely. When something is “Permission denied,” resist 777. Find out which class you are for that file (ls -l + id) and grant the smallest bit to the right class — usually by correcting ownership (chown) and using 640/750. 777 on a directory lets any local account plant or replace files there; on a web root it’s a remote-code-execution waiting room.

  2. The -G vs -aG amputation. usermod -G docker alice silently strips alice from sudo, adm, and every other secondary group, replacing them all with just docker. People discover it when they lose sudo. Burn -aG into your fingers, and always id afterward to confirm.

  3. Missing x on a parent directory. You set a file to 644 and still get denied — because you (or the service) can’t traverse into a parent directory that lacks x for your class. Permissions are checked at every component of the path. namei -l /srv/project/app.env prints the mode of each segment so you can spot the one blocking you. This is the number-one “but the file permissions look fine!” mystery.

Cheat-sheet

Command Does
id / id user Show UID, GID, and group membership.
whoami / groups Current username / your groups.
getent passwd|shadow|group NAME Look up an account/group (honours LDAP/SSSD).
sudo useradd -m -s /bin/bash NAME Create user with home + shell.
sudo userdel -r NAME ⚠️ Delete user and home.
sudo usermod -aG GRP NAME Append a secondary group (the safe way).
sudo passwd NAME Set/reset a password.
chage -l NAME Show password-aging policy.
sudo groupadd NAME / groupdel Create / delete a group.
newgrp GRP Activate a group in the current session.
ls -l / ls -ld DIR Show file / directory permissions.
chmod 640 FILE Set octal mode (r=4 w=2 x=1 per class).
chmod u+x,go-w FILE Symbolic edit (who ± perm).
chmod -R a+rX DIR Recursive; X = execute only on dirs/existing-x.
chown user:group FILE Change owner and group.
chgrp GRP FILE Change group only.
umask 027 Set the new-file mask (→ files 640, dirs 750).
chmod 2775 DIR setgid dir (files inherit its group).
chmod +t DIR / 1777 Sticky bit (owners-only delete, like /tmp).
find / -perm -4000 -type f 2>/dev/null Audit all setuid binaries.
namei -l /path/to/file Show the mode of every path component.
sudo -l List your sudo rights.
sudo visudo Safely edit sudoers (validates on save).

Interview and exam questions

Q: You own a file with mode ----rw----. Can you read it? A: No. The kernel matches you to the owner class (because you’re the owner) and stops there — owner has ---. The group’s rw is never consulted, because only the first matching class applies. Ownership is checked before group membership.

Q: Explain how you can delete a file you have no write permission on. A: Deleting a file removes its name from the containing directory — that’s a write to the directory, governed by the directory’s w bit, not the file’s. If you have w (and x) on the directory, you can delete even a read-only file you don’t own. The sticky bit is what prevents this in shared dirs like /tmp.

Q: What’s the difference between usermod -G and usermod -aG? A: -G sets (replaces) the complete list of secondary groups — omitting a group removes the user from it. -aG appends without touching existing memberships. Always use -aG unless you deliberately want to overwrite.

Q: Convert rwxr-x--- to octal, and 2750 back to symbolic. A: rwx=7, r-x=5, ---=0 → 750. 2750: the leading 2 is setgid; 750 = rwxr-x---, so rwxr-s--- (setgid shown in the group slot).

Q: Why is /usr/bin/passwd setuid-root, and how do you spot it? A: Changing your password writes to root-owned /etc/shadow, which an ordinary user can’t do. The setuid bit makes passwd run with the file owner’s (root’s) effective UID. You spot it by the s in the owner-execute slot: -rwsr-xr-x.

Q: A user was added to the docker group but docker ps still says permission denied. Why? A: Group membership is granted at login. Their current shell predates the change. They must log out and back in, or run newgrp docker. Confirm with id.

Q: What does umask 027 produce for a new file and a new directory, and why can’t a new file be executable? A: File 640, directory 750. The file base is 666 (never 777), so it has no execute bit to keep — umask can only remove bits, so new files are never executable and always need explicit chmod +x.

Q: (RHCSA-style) Create user deploy, make /srv/web a directory the web group can collaborate in with correct group inheritance. A:

sudo groupadd web
sudo useradd -m -G web deploy
sudo mkdir /srv/web
sudo chgrp web /srv/web
sudo chmod 2775 /srv/web     # setgid so new files inherit group 'web'

Q: (LFCS-style) Grant the user ci permission to restart nginx via sudo — and nothing else. A: Run sudo visudo -f /etc/sudoers.d/ci and add:

ci ALL=(root) NOPASSWD: /usr/bin/systemctl restart nginx

This is least privilege — ci cannot open a root shell or run any other command.

Q: How do you find every setuid binary on a system, and why would you? A: find / -perm -4000 -type f 2>/dev/null. setuid-root binaries run as root regardless of who invokes them, so each is a potential privilege-escalation vector; you audit them to ensure only trusted, patched programs carry the bit.

Q: A file has mode 644 and correct ownership, but a service still can’t read it. What’s the most likely cause? A: A parent directory lacks execute (x) for the service’s class, so the path can’t be traversed. Permissions are enforced at every path component; use namei -l /full/path to find the offending directory.

Q: Why prefer sudo over logging in as root? A: Per-command auditing (who ran what), least privilege (grant single commands), no shared root password (users use their own), and fewer accidents (you escalate deliberately instead of living as root).

Key takeaways

linuxpermissionschmodchownumasksudousersgroupsetc-passwdetc-shadowsetuidsticky-bitrhcsafundamentals
Need this built for real?

Vinod is a Senior Cloud Architect (22+ yrs) — available for Azure / AWS / GCP architecture, landing zones, and migrations.

Work with me

Comments