Skip to main content

Linux Users, Groups, and sudo — Access Control on Production Servers

Manage Linux users, groups, and sudo access to enforce least-privilege access control on production servers shared across engineering teams.

30 Terms

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).

YAML
username:x:UID:GID:comment:home:shell
root:x:0:0:root:/root:/bin/bash
deploy:x:1001:1001:Deploy User:/home/deploy:/bin/bash
nginx:x:999:999:nginx web server:/var/cache/nginx:/sbin/nologin
payment-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:

TEXT
username:$6$salt$hashedpassword:lastchange:min:max:warn:inactive:expire
Only root can read /etc/shadow
ls -la /etc/shadow
## -rw-r----- 1 root shadow 1234 Jan 15 /etc/shadow

The /etc/group file — group membership:

YAML
groupname:x:GID:member1,member2,member3
root:x:0:
deploy:x:1001:
developers:x:1050:alice,bob,charlie
adm:x:4:syslog,deploy

UID ranges and what they mean:

TEXT
UID 0 root -- total system control
UID 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 server

su vs sudo — two different things:

Bash
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 commands

Step-by-Step Lab

Milestone 1 — Inspect the current user and group state

Bash
## Who am I?
whoami
## ubuntu
## My UID, GID, and all group memberships
id
## uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu),4(adm),27(sudo)
## List all users on the system
cat /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 groups
cat /etc/group | cut -d: -f1,3
## Who is currently logged in
who
w ## more detail: uptime and what each user is doing
last ## login history from /var/log/wtmp
lastlog ## last login time for every account

Milestone 2 — Create user accounts

Bash
## Create a human user account (interactive login)
sudo useradd \
--create-home \
--shell /bin/bash \
--comment "Alice Engineer" \
--groups sudo,developers \
alice
## Set initial password
sudo passwd alice
## Verify the account
id alice
grep alice /etc/passwd
## Modify an existing user
sudo usermod --append --groups docker alice ## add to docker group
sudo usermod --shell /bin/zsh alice ## change shell
sudo usermod --comment "Alice - Platform" alice ## update comment
## Lock an account (employee leaves)
sudo passwd --lock alice
sudo usermod --lock alice ## same effect
## Unlock an account
sudo passwd --unlock alice
## Delete a user
sudo userdel alice ## remove user, keep home directory
sudo userdel --remove alice ## remove user AND home directory

Milestone 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.

Bash
## Create a service account for a payment service
sudo 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
## Verify
id 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 account
sudo mkdir -p /opt/payment-service
sudo chown payment-svc:payment-svc /opt/payment-service
sudo 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

Bash
## Create a group for the engineering team
sudo groupadd developers
## Create a group for deployment access
sudo groupadd --system deploy-access
## Add users to a group
sudo gpasswd --add alice developers
sudo gpasswd --add bob developers
## Remove a user from a group
sudo gpasswd --delete alice developers
## List members of a group
getent group developers
## developers:x:1050:alice,bob
## Change a file's group
sudo chgrp developers /var/www/html/
sudo chmod 2775 /var/www/html/
## setgid on directory: new files inherit developers group
## Apply group changes without logging out
newgrp developers ## opens new shell with the new group active
## Or log out and back in for all changes to take effect

Milestone 5 — Configure sudo

Bash
## ALWAYS edit sudoers with visudo -- it validates before saving
## A syntax error in /etc/sudoers can lock everyone out of sudo
sudo visudo
## Or edit a drop-in file (safer -- does not affect main sudoers)
sudo visudo -f /etc/sudoers.d/deploy-access
Bash
## 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 only
deploy ALL=(ALL) NOPASSWD: /bin/systemctl restart payment-service
deploy 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 readable
Cmnd_Alias PAYMENT_CMDS = /bin/systemctl restart payment-service, \
/bin/systemctl reload nginx
deploy ALL=(ALL) NOPASSWD: PAYMENT_CMDS
Bash
## Test sudo configuration
sudo -l -U alice
## Lists what alice is allowed to run with sudo
## Run a command as another user
sudo -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 user
sudo -s -u deploy ## non-login shell as deploy user

Milestone 6 — Audit access

Bash
## Check who has sudo access
grep -E '^[^#].*ALL' /etc/sudoers
ls -la /etc/sudoers.d/
## Review recent sudo usage
grep 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 logins
last | 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: -f1

Common 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)
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

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.

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.

Resources

Explore More in Linux Fundamentals and File System

All 4 Topics

Frequently Asked Questions

Is Linux Users, Groups, and sudo — Access Control on Production Servers free to learn on DevOps Network?

Yes - this topic, like everything on DevOps Network, is 100% free with no paywall or sign-up gate.

What does the Linux Users, Groups, and sudo — Access Control on Production Servers topic cover?

Manage Linux users, groups, and sudo access to enforce least-privilege access control on production servers shared across engineering teams.