Every process on a Linux system runs as a user. Every file is owned by a user. Every permission check asks one question: who is asking, and do they have the right?
On a production server at Zerodha, dozens of engineers may have SSH access. Dozens of services run simultaneously — nginx, PostgreSQL, a payment API, a log shipper. Each of these must be isolated from the others. A bug in the log shipper should not be able to read the payment API's database credentials. A junior engineer with SSH access should not be able to restart the database server.
Linux users and groups are the mechanism that enforces this isolation. Mastering them is what lets you build production systems where each component has exactly the access it needs — and nothing more.
Why It Matters
Shared accounts are the enemy of security and auditability. When five engineers share a single deploy account, you cannot tell which engineer ran a command that deleted a production table. When a service runs as root, a single vulnerability in that service is a full server compromise.
The principle of least privilege says: every user and process gets the minimum access needed to do their job. Not convenience-level access. Not "we can restrict it later" access. The minimum, from day one.
Core Principles
The /etc/passwd file — user database:
Every user account on a Linux system has an entry in /etc/passwd. Despite the name, passwords have not been stored here since the 1980s (they moved to /etc/shadow).
username:x:UID:GID:comment:home:shell root:x:0:0:root:/root:/bin/bashdeploy:x:1001:1001:Deploy User:/home/deploy:/bin/bashnginx:x:999:999:nginx web server:/var/cache/nginx:/sbin/nologinpayment-svc:x:1002:1002:Payment Service:/opt/payment:/sbin/nologin Fields: username Login name x Password placeholder (actual hash in /etc/shadow) UID User ID number (0 = root, 1-999 = system, 1000+ = human) GID Primary group ID comment Full name or description home Home directory shell Login shell (/sbin/nologin = cannot log in interactively)The /etc/shadow file — password hashes:
username:$6$salt$hashedpassword:lastchange:min:max:warn:inactive:expire Only root can read /etc/shadowls -la /etc/shadow## -rw-r----- 1 root shadow 1234 Jan 15 /etc/shadowThe /etc/group file — group membership:
groupname:x:GID:member1,member2,member3 root:x:0:deploy:x:1001:developers:x:1050:alice,bob,charlieadm:x:4:syslog,deployUID ranges and what they mean:
UID 0 root -- total system controlUID 1-99 Static system accounts (distro-managed)UID 100-999 Dynamic system accounts (created by package installs)UID 1000+ Human user accounts System accounts (UID < 1000) typically:- Have /sbin/nologin or /bin/false as shell- Have no password- Own files belonging to their service- Cannot SSH into the serversu vs sudo — two different things:
su (substitute user):- Switches to another user completely- Requires the TARGET user's password- Opens a new shell as that user- su - root asks for root's password- Dangerous: gives full root shell- Hard to audit which commands were run sudo (superuser do):- Runs ONE command with elevated privileges- Requires YOUR OWN password- Configured per-user and per-command- Every command logged to /var/log/auth.log- Easy to audit who ran what when- Can be restricted to specific commandsStep-by-Step Lab
Milestone 1 — Inspect the current user and group state
## Who am I?whoami## ubuntu ## My UID, GID, and all group membershipsid## uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu),4(adm),27(sudo) ## List all users on the systemcat /etc/passwd | cut -d: -f1,3,7 | column -t -s:## root 0 /bin/bash## daemon 1 /usr/sbin/nologin## ubuntu 1000 /bin/bash ## List all groupscat /etc/group | cut -d: -f1,3 ## Who is currently logged inwhow ## more detail: uptime and what each user is doinglast ## login history from /var/log/wtmplastlog ## last login time for every accountMilestone 2 — Create user accounts
## Create a human user account (interactive login)sudo useradd \ --create-home \ --shell /bin/bash \ --comment "Alice Engineer" \ --groups sudo,developers \ alice ## Set initial passwordsudo passwd alice ## Verify the accountid alicegrep alice /etc/passwd ## Modify an existing usersudo usermod --append --groups docker alice ## add to docker groupsudo usermod --shell /bin/zsh alice ## change shellsudo usermod --comment "Alice - Platform" alice ## update comment ## Lock an account (employee leaves)sudo passwd --lock alicesudo usermod --lock alice ## same effect ## Unlock an accountsudo passwd --unlock alice ## Delete a usersudo userdel alice ## remove user, keep home directorysudo userdel --remove alice ## remove user AND home directoryMilestone 3 — Create service accounts
Service accounts are non-human accounts for running application processes. They follow the principle of least privilege — minimal access, no interactive login.
## Create a service account for a payment servicesudo useradd \ --system \ --no-create-home \ --shell /sbin/nologin \ --comment "Payment Service Account" \ payment-svc ## --system assigns UID in the system range (< 1000)## --no-create-home no home directory needed## --shell /sbin/nologin cannot log in interactively ## Verifyid payment-svc## uid=999(payment-svc) gid=999(payment-svc) groups=999(payment-svc) getent passwd payment-svc## payment-svc:x:999:999:Payment Service Account:/:/sbin/nologin ## Create application directory owned by service accountsudo mkdir -p /opt/payment-servicesudo chown payment-svc:payment-svc /opt/payment-servicesudo chmod 750 /opt/payment-service ## Verify no one can SSH as this account## (The /sbin/nologin shell rejects interactive sessions with a message)ssh payment-svc@localhost## This account is currently not available.Milestone 4 — Manage groups
## Create a group for the engineering teamsudo groupadd developers ## Create a group for deployment accesssudo groupadd --system deploy-access ## Add users to a groupsudo gpasswd --add alice developerssudo gpasswd --add bob developers ## Remove a user from a groupsudo gpasswd --delete alice developers ## List members of a groupgetent group developers## developers:x:1050:alice,bob ## Change a file's groupsudo chgrp developers /var/www/html/sudo chmod 2775 /var/www/html/## setgid on directory: new files inherit developers group ## Apply group changes without logging outnewgrp developers ## opens new shell with the new group active## Or log out and back in for all changes to take effectMilestone 5 — Configure sudo
## ALWAYS edit sudoers with visudo -- it validates before saving## A syntax error in /etc/sudoers can lock everyone out of sudosudo visudo ## Or edit a drop-in file (safer -- does not affect main sudoers)sudo visudo -f /etc/sudoers.d/deploy-access## Sudoers syntax:## who where=(as_whom) what ## Full sudo access (like being root)alice ALL=(ALL:ALL) ALL ## Sudo without password (for CI/CD service accounts)jenkins ALL=(ALL) NOPASSWD: ALL ## Restrict to specific commands onlydeploy ALL=(ALL) NOPASSWD: /bin/systemctl restart payment-servicedeploy ALL=(ALL) NOPASSWD: /bin/systemctl reload nginx ## Allow a group to have sudo%developers ALL=(ALL) ALL ## Allow restart of specific services, no password%deploy-access ALL=(ALL) NOPASSWD: /bin/systemctl restart * ## Aliases make rules readableCmnd_Alias PAYMENT_CMDS = /bin/systemctl restart payment-service, \ /bin/systemctl reload nginxdeploy ALL=(ALL) NOPASSWD: PAYMENT_CMDS## Test sudo configurationsudo -l -U alice## Lists what alice is allowed to run with sudo ## Run a command as another usersudo -u payment-svc /opt/payment-service/bin/check-config ## Open a shell as another user (use sparingly, loses audit trail)sudo -i -u deploy ## login shell as deploy usersudo -s -u deploy ## non-login shell as deploy userMilestone 6 — Audit access
## Check who has sudo accessgrep -E '^[^#].*ALL' /etc/sudoersls -la /etc/sudoers.d/ ## Review recent sudo usagegrep sudo /var/log/auth.log | tail -20## Jan 15 10:23:45 server sudo: alice : TTY=pts/0 ;## PWD=/home/alice ; USER=root ; COMMAND=/bin/systemctl restart nginx ## See recent loginslast | head -20 ## See failed login attempts (brute force detection)grep 'Failed password' /var/log/auth.log | tail -20 ## Find accounts with no password (security risk)sudo awk -F: '($2 == "") {print $1}' /etc/shadow ## Find accounts that can log in (have a real shell)grep -v '/nologin\|/false' /etc/passwd | cut -d: -f1Common Mistakes
| Mistake | Problem | Fix |
|---|---|---|
| Running services as root | Full server compromise if service is exploited | Create a dedicated service account |
| Shared accounts (team all use "deploy") | Cannot audit who did what | One named account per human |
| NOPASSWD: ALL for CI runners | CI runner compromise = full server access | Restrict to exact commands needed |
| Editing /etc/sudoers directly | Syntax error locks everyone out of sudo | Always use visudo |
| Forgetting to log out after usermod | Group changes do not apply until next login | Use newgrp or log out and back in |
Troubleshooting
| Symptom | First Command | What to Look For |
|---|---|---|
| sudo: command not found | which command |
Command not in sudo's PATH |
| User cannot SSH in | grep username /etc/passwd |
Shell is /sbin/nologin |
| Permission denied despite sudo | sudo -l -U username |
Command not in allowed list |
| Group change not taking effect | id username |
User needs to log out and back in |
| Account locked | sudo passwd -S username |
Status shows L (locked) |
PLACEMENT PRO TIP**Tip:** Drop files into `/etc/sudoers.d/` instead of editing the main `/etc/sudoers`. Each file is separate, easier to manage, and a syntax error in one file does not prevent other sudo rules from working. Use descriptive names: `/etc/sudoers.d/deploy-access`, `/etc/sudoers.d/jenkins-ci`.
REMEMBER THIS**Remember:** `usermod --append --groups` requires the `--append` flag. Without it, `usermod --groups docker alice` REPLACES all of Alice's groups with just `docker`, removing her from sudo, developers, and everything else. Always use `--append` when adding to groups.
COMMON MISTAKE / WARNING**Security:** Review accounts with login shells quarterly: `grep -v '/nologin\|/false' /etc/passwd`. Any service account that somehow has a real shell is a potential lateral movement path if that service is compromised. Service accounts must always use `/sbin/nologin` or `/bin/false` as their shell.