Users, Groups, Permissions & sudo

Read and set the permission bits with confidence, understand what setuid and the sticky bit actually do, and know where identity lives on a Linux host.

beginner 18 min lesson hands-on task included

Permissions are the first thing you check when something “works on my machine” and fails on the server, and the last thing anybody explains properly. Ten minutes here removes an entire category of confusing errors.


Topic 1: Identity — Users, Groups and Where They Live

Every process runs as a UID (user ID) and belongs to a set of GIDs (group IDs). The names you see are a convenience; the kernel only ever compares numbers.

id                     # your identity, numerically and by name
id appuser             # somebody else's
whoami                 # just the username
groups                 # groups you belong to
uid=1000(deploy) gid=1000(deploy) groups=1000(deploy),27(sudo),999(docker)

That docker group is worth staring at: membership is equivalent to root, because anyone who can talk to the Docker socket can start a container that mounts the host filesystem. Adding a user to docker is a privilege grant, not a convenience.

The four files that define identity:

FileHoldsReadable by
/etc/passwdUsername, UID, GID, home directory, login shell. No passwords, despite the name.Everyone
/etc/shadowPassword hashes and ageing policy.root only
/etc/groupGroup names, GIDs, and supplementary members.Everyone
/etc/sudoersWho may run what as whom. Edit with visudo, never directly.root only
getent passwd deploy          # the right way to query -- works with LDAP/SSSD too
awk -F: '$3 >= 1000 {print $1, $3, $7}' /etc/passwd   # human accounts and their shells
awk -F: '$2 == "" {print $1}' /etc/shadow             # accounts with NO password set

A login shell of /usr/sbin/nologin or /bin/false is how service accounts are prevented from being logged into interactively — worth confirming on any account you did not create.

Managing accounts:

sudo useradd -m -s /bin/bash -G docker alice   # -m creates the home dir
sudo passwd alice
sudo usermod -aG sudo alice        # -a is MANDATORY with -G
sudo gpasswd -d alice docker       # remove from one group
sudo userdel -r alice              # -r also removes the home directory

Common mistake: usermod -G docker alice without the -a. -G replaces the entire supplementary group list, so this silently removes alice from sudo and every other group. It is one of the fastest ways to lock yourself out of a box.


Topic 2: The Permission Bits

Every file carries three permission triplets: one for the owner, one for the group, one for everyone else (often called “other” or “world”).

-rwxr-xr-- = 754 OWNER rwx 4+2+1 7 GROUP r-x 4+0+1 5 OTHERS r-- 4+0+0 4 r = 4 (read) · w = 2 (write) · x = 1 (execute) add the values per column — chmod 640 = rw- r-- --- · directories need x to be entered at all
Each triplet is a 3-bit number. Read is 4, write is 2, execute is 1 — add them per column and you have the octal mode that chmod expects.
$ ls -l deploy.sh
-rwxr-xr--  1 deploy  ops  2048 Aug  5 09:14 deploy.sh
│└┬┘└┬┘└┬┘     └──┬─┘ └┬┘
│ │  │  │         │    └── group
│ │  │  └── other │
│ │  └───── group └─────── owner
│ └──────── owner
└────────── file type

The leading character is a type, not a permission:

CharType
-Regular file
dDirectory
lSymbolic link
c / bCharacter / block device
sSocket
pNamed pipe (FIFO)

What the bits mean on a directory — the part everyone gets wrong:

The same three letters mean something different for directories, and this catches people constantly:

BitOn a fileOn a directory
rRead the contentsList the names inside
wModify the contentsCreate and delete entries inside
xExecute itEnter it — cd into it, or traverse it in a path

Two consequences follow that look like bugs until you know this:

  • A directory with r but no x lets you ls the names but not read any of the files, because you cannot traverse into it.
  • w on a directory lets you delete a file you do not own and cannot write to — deletion changes the directory, not the file. That is what the sticky bit exists to prevent.

Common mistake: Fixing a “permission denied” on /data/app/config.yml by chmod-ing the file, when the missing bit is x on /data or /data/app. Every directory in the path needs execute for you to reach the file. Check the whole chain with namei -l /data/app/config.yml.


Topic 3: chmod, chown and umask

chmod 750 deploy.sh          # numeric: exact, unambiguous, what runbooks use
chmod u+x deploy.sh          # symbolic: add execute for the owner
chmod go-w deploy.sh         # remove write from group and other
chmod -R u+rwX,go-rwx /data  # capital X = execute only on DIRECTORIES, not files

chown deploy:ops deploy.sh   # owner and group together
chown -R deploy: /srv/app    # trailing colon = set group to the user's own group
chgrp ops deploy.sh          # group only

The capital X in that fourth line is a genuinely useful trick: recursive +x makes every text file executable, which is noise at best. +X grants execute only where it makes sense — directories, and files that already had it somewhere.

umask — why new files are never 666:

umask is a mask of bits to remove from the default mode of anything you create. The kernel defaults are 666 for files and 777 for directories; the umask subtracts from those.

umask          # 0022 on most systems
umask -S       # u=rwx,g=rx,o=rx -- the same thing, readable
Baseumask 022Result
File666-022644 — rw-r—r—
Directory777-022755 — rwxr-xr-x

Files never get the execute bit by default no matter what the umask says — that is why a script you just wrote needs chmod +x before it will run.

A umask of 077 is the hardened choice for a shared host: new files are 600, readable by nobody but you. Set it in /etc/profile for interactive sessions or UMask= in a systemd unit for a service.

Try it yourself: Run umask, then touch t1 && mkdir d1 && ls -ld t1 d1. Confirm the modes match the arithmetic above. Then umask 077, repeat with new names, and compare.


Topic 4: The Special Bits — setuid, setgid, sticky

Three extra bits sit above the nine you have seen. They appear as a fourth octal digit and change how execution and ownership behave.

BitOctalShows asEffect
setuid4000s in the owner’s x slotThe program runs as its owner, not as you.
setgid2000s in the group’s x slotOn a file: runs as the owning group. On a directory: new files inherit the directory’s group.
sticky1000t in the other x slotIn a shared directory, only the file’s owner may delete it.

setuid, the one to respect:

ls -l /usr/bin/passwd
-rwsr-xr-x 1 root root 68208 /usr/bin/passwd
   ^

passwd must edit /etc/shadow, which only root can write. The setuid bit lets any user run it as root for the duration. This is necessary — and it is also why every setuid binary is a potential privilege-escalation path. A bug in one is a root shell for whoever finds it.

# Audit: every setuid binary on the box
find / -xdev -perm -4000 -type f -exec ls -l {} \; 2>/dev/null

Run that on a host you own and confirm you recognise every result. An unexpected setuid binary in /tmp or a user’s home directory is an incident, not a curiosity.

The sticky bit and /tmp:

ls -ld /tmp
drwxrwxrwt 10 root root 4096 Aug  5 09:14 /tmp
         ^

/tmp is world-writable — it has to be. Without the sticky bit, any user could delete any other user’s temp files, because write permission on a directory is what governs deletion. The t restricts deletion to each file’s owner.

chmod 1777 /shared    # sticky + rwx for all
chmod 2775 /team      # setgid: everything created inherits the 'team' group

That setgid directory trick is the standard fix for “the other team cannot read the files I put in the shared folder” — without it, each file gets the creator’s primary group.


Topic 5: sudo — Controlled Escalation

sudo runs one command as another user (root by default), logging who did what.

sudo systemctl restart nginx
sudo -u postgres psql         # as a specific user, not root
sudo -i                       # a full root login shell
sudo -l                       # what am I permitted to run?
sudo -E ./script.sh           # preserve MY environment variables

sudo -E matters more than it seems. By default sudo resets the environment for safety, so a PATH or proxy variable you set is not there when the command runs. That is why a script works interactively and then fails under sudo for no visible reason.

Granting narrowly:

# /etc/sudoers.d/deploy  — always create files here, never edit /etc/sudoers directly
%ops    ALL=(ALL)   /bin/systemctl restart nginx, /bin/systemctl reload nginx
deploy  ALL=(ALL)   NOPASSWD: /usr/local/bin/deploy.sh

Always validate before you log out — a syntax error in sudoers can lock everyone out of root:

sudo visudo -c -f /etc/sudoers.d/deploy

Common mistake: Granting %ops ALL=(ALL) /bin/vi or any command with a shell escape. vi can spawn a shell, so that line is functionally ALL=(ALL) ALL. The same applies to find, less, awk, and anything that runs arbitrary commands — narrow-looking grants that are actually full root.

Try it yourself: Run sudo -l on any host you have access to and read exactly what you are permitted to run. Then run sudo env | wc -l and compare with env | wc -l to see how much sudo strips.