Skip to content

Users & Groups

Terminal window
whoami # print current username
who # list all currently logged-in users
who -a # detailed: login time, PID, idle time, host
id # print UID, GID, and all group memberships
id bjmoose # same info for another user
last # login history (user, terminal, date/time)
w # who is logged in AND what they're doing

Linux deliberately uses multiple account types to isolate processes and enforce least-privilege:

TypeUID RangePurpose
root0Superuser - unrestricted access to everything
System accounts1-99 (or 100-999)Created during OS install (sshd, mail, daemon)
Service accounts100-999 (distro-dependent)Created when services are installed (nginx, postgres)
Normal users1000+Human users; have their own /home directory
  • System and service accounts have no dedicated /home and use /sbin/nologin as their shell - they exist to run services, not to login.
  • Normal users start at UID 1000 by default (configured in /etc/login.defs as UID_MIN).

Every user has one line in /etc/passwd. Fields are colon-separated:

username:password:UID:GID:GECOS:home:shell
owl:x:1000:1000:Owl:/home/owl:/bin/bash
FieldDescriptionExample
usernameLogin nameowl
passwordx = password is in /etc/shadowx
UIDUser ID (root=0, system < 1000, users >= 1000)1000
GIDPrimary group ID (matches /etc/group)1000
GECOSFull name / comment (optional)Owl
homeAbsolute path to home directory/home/owl
shellLogin shell (/sbin/nologin for system accounts)/bin/bash

The file permission is 644 - world-readable because system programs need basic user info. Passwords are never stored here on modern systems.


/etc/shadow stores hashed passwords. Permissions: 400 (root-read-only).

daemon:*:16141:0:99999:7:::
owl:$6$iCZyCnBJ...:16316:0:99999:7:::

Each colon-separated field:

FieldDescription
usernameMust match /etc/passwd exactly
password$6$[salt]$[hash] (SHA-512). * or ! means no password / locked
lastchangeDays since Jan 1, 1970 (epoch) that password was last changed
mindaysMin days before password can be changed again
maxdaysPassword must be changed after this many days
warnDays before expiry to warn the user
graceDays after expiry before account is disabled
expireAbsolute expiry date (days since epoch); blank = never
reservedReserved for future use

The password hash format: $6$ (SHA-512 algorithm identifier) + 8-char salt + $ + 88-char hash.


Terminal window
# Basic: creates home dir, sets shell to /bin/bash, assigns next available UID
sudo useradd bjmoose
# Full options
sudo useradd \
-m \ # create home directory
-k /etc/skel \ # copy skeleton files from /etc/skel
-s /bin/bash \ # set login shell
-c "Bullwinkle J Moose" \ # GECOS comment (full name)
-G devs,admins \ # add to supplementary groups
bmoose
# After creation, set a password (account is locked until then)
sudo passwd bmoose

On creation, useradd automatically:

  • Assigns the next available UID >= UID_MIN
  • Creates a primary group with the same name and GID = UID
  • Places !! in /etc/shadow (account locked until password is set)
  • Copies /etc/skel template files to the new home directory
Terminal window
sudo usermod -s /bin/zsh bjmoose # change shell
sudo usermod -c "New Full Name" bjmoose # update GECOS
sudo usermod -aG sudo bjmoose # add to sudo group (use -a to append!)
sudo usermod -G group1,group2 bjmoose # set COMPLETE group list (overwrites)
sudo usermod -L bjmoose # lock account (prepends ! to password hash)
sudo usermod -U bjmoose # unlock account
sudo usermod -e 2025-12-31 bjmoose # set account expiry date
Terminal window
sudo userdel bjmoose # remove user, keep /home/bjmoose
sudo userdel -r bjmoose # remove user AND their home directory
Terminal window
id bjmoose # uid=1002(bjmoose) gid=1002(bjmoose) groups=...
groups bjmoose # show group memberships
finger bjmoose # full account info (if installed)

Terminal window
passwd # change your own password
sudo passwd bjmoose # root changes another user's password (no current pwd needed)
sudo passwd -l bjmoose # lock account (add ! prefix)
sudo passwd -u bjmoose # unlock account
sudo passwd -d bjmoose # delete password (allow passwordless login - dangerous)
sudo passwd -e bjmoose # expire password (force change on next login)

chage manages password aging policy per user. Only root can modify; any user can view their own info with -l.

Terminal window
sudo chage -l bjmoose # view current aging settings
sudo chage -m 7 -M 90 -W 14 bjmoose # min 7d, max 90d, warn 14d before
sudo chage -d 0 bjmoose # force password change at next login
sudo chage -E 2025-12-31 bjmoose # set account expiry date
sudo chage -I 30 bjmoose # disable after 30 days of inactivity

To lock someone out by setting a past expiry date:

Terminal window
sudo chage -E 2000-01-01 rjsquirrel

Terminal window
su # switch to root (requires root password)
su - # switch to root with root's full environment
su bjmoose # switch to bjmoose (requires bjmoose's password)
su - bjmoose # switch to bjmoose with their login environment

su launches a new shell as the target user. You get full, persistent root access until you exit. Giving out the root password is a major security risk.

Terminal window
sudo command # run one command as root
sudo -u bjmoose command # run as a different user
sudo -i # start a root login shell
sudo -l # list what commands you're allowed to run
sudo -k # invalidate cached sudo timestamp (re-prompt next time)

sudo is safer because:

  • Uses your password (root password not needed - not shared)
  • Privilege is time-limited (default: 5-15 minutes before re-prompting)
  • Every command is logged with your identity in /var/log/auth.log or /var/log/secure
  • Granular control: you can allow bjmoose to run only systemctl restart nginx as root, nothing else

sudo flow

Terminal window
sudo visudo # ALWAYS use visudo - validates syntax before saving
sudo visudo -f /etc/sudoers.d/owl # edit per-user drop-in file (preferred approach)

Basic sudoers entry format:

who where = (as_whom) what
owl ALL = (ALL) ALL # owl can run anything as anyone
bob ALL = (root) /usr/bin/apt # bob can only run apt as root
%admins ALL = (ALL) ALL # group 'admins' gets full sudo

Prefer drop-in files in /etc/sudoers.d/ over editing the master file directly.

System accounts use /sbin/nologin as their shell - they can run as service processes but cannot generate an interactive shell:

Terminal window
grep nologin /etc/passwd # list all no-login accounts
sudo usermod -s /sbin/nologin bjmoose # prevent login for a user account

In /etc/passwd:

sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin

groupname:password:GID:user1,user2,...
devs:x:1010:owl,alice,bob
FieldDescription
groupnameGroup name
passwordGroup password (rarely used; x = see /etc/gshadow)
GIDGroup ID; 0-99 system groups; GID_MIN+ for user groups
membersComma-separated list of supplementary members (primary group members not listed)
  • Each user has exactly one primary group (from /etc/passwd). New files get this group as owner.
  • Users can have up to ~15 supplementary groups. These grant additional permissions.
  • id shows both: gid=1000(owl) groups=1000(owl),27(sudo),1010(devs)
Terminal window
# Create
sudo groupadd devs
sudo groupadd -g 1050 -r svcgroup # specific GID, system group
# Modify
sudo groupmod -n developers devs # rename group
sudo groupmod -g 2000 devs # change GID
# Delete (must remove all members first or reassign primary group)
sudo groupdel devs
# Membership
groups owl # list owl's groups
id -Gn owl # same, names only
sudo usermod -aG devs owl # add owl to devs (append!)
sudo gpasswd -d owl devs # remove owl from devs

By default, useradd creates a group with the same name and GID as the user. This is the User Private Group model:

  • GID = UID for the primary group
  • Default umask = 002 (files: 664 rw-rw-r--, dirs: 775 rwxrwxr-x)
  • Allows collaboration: both owner and group can write by default

Bash reads initialization files in a specific order depending on invocation type:

TypeFiles Read (in order, first found wins)
Login shell/etc/profile -> ~/.bash_profile OR ~/.bash_login OR ~/.profile
Interactive non-login shell~/.bashrc
Non-interactiveFile specified in $BASH_ENV

Shell startup order

Most users only modify ~/.bashrc - it’s read every time a new terminal opens.

Terminal window
alias # list all current aliases
alias ll='ls -la --color=auto' # define an alias (add to ~/.bashrc for persistence)
alias gs='git status'
unalias ll # remove an alias

No spaces around =; quote the value if it contains spaces.


Variables that child processes inherit (exported) vs variables local to the shell.

TaskCommand
Show all variablesenv or printenv or export
Show one variableecho $HOME
Set (local to shell)MYVAR=value (no spaces around =)
Export to child procsexport MYVAR=value
PermanentAdd export MYVAR=value to ~/.bashrc, then source ~/.bashrc
One-shot for a commandKEY=value command
VariableMeaningExample
HOMEUser’s home directory/home/owl
USERCurrent usernameowl
SHELLPath to login shell/bin/bash
PATHColon-separated list of directories searched for commands/usr/local/bin:/usr/bin:/bin
PWDCurrent working directory/home/owl/projects
EDITORDefault text editorvim
PS1Primary shell prompt format\u@\h:\w\$
HISTFILEPath to history file~/.bash_history
Terminal window
# Add ~/bin to the front of PATH (temporarily)
export PATH=$HOME/bin:$PATH
# Permanently: add to ~/.bashrc
echo 'export PATH=$HOME/bin:$PATH' >> ~/.bashrc

Special PS1 sequences:

EscapeMeaning
\uUsername
\hHostname (short)
\HHostname (full FQDN)
\wCurrent working directory
\WBasename of current directory
\dDate (Day Mon Date)
\tTime (HH:MM:SS)
\!History number of this command
\$$ for normal user, # for root

Bash keeps command history in ~/.bash_history.

Terminal window
history # show numbered list of previous commands
history 20 # show last 20
history -c # clear in-memory history (not saved to file yet)
history -w # write current history to file
Key / SyntaxAction
Up / Down arrowsBrowse previous/next commands
Ctrl-RReverse incremental search through history
!!Repeat last command
!nRun command number n from history
!stringRun most recent command starting with string
!$Last argument of the previous command
VariablePurpose
HISTFILEPath to history file (default: ~/.bash_history)
HISTSIZENumber of commands kept in memory
HISTFILESIZENumber of commands saved to file
HISTCONTROL=ignoredupsDon’t save duplicate consecutive commands
HISTCONTROL=ignorespaceDon’t save commands starting with a space
HISTIGNOREColon-separated patterns to exclude (e.g., ls:cd:exit)
ShortcutEffect
Ctrl-AMove to beginning of line
Ctrl-EMove to end of line
Ctrl-UDelete from cursor to beginning of line
Ctrl-KDelete from cursor to end of line
Ctrl-WDelete word before cursor
Ctrl-LClear screen (like clear)
Ctrl-CKill/interrupt current process
Ctrl-ZSuspend current process (send to background)
Ctrl-DSend EOF (logout or close shell)
TabAuto-complete file/command/variable names