Skip to content

User and Group Accounts: passwd, shadow, useradd, and Groups

Linux is designed to run more than one user's work at the same time, and each account needs its own files kept separate from everyone else's. That separation is enforced by two numbers attached to every account: a numeric UID and a numeric GID. Every mechanism this module covers — permission bits, ownership, sudo, and umask — is built on top of these accounts, so this is where the module starts.

You will see where account data actually lives, how a primary group differs from a supplementary group, how a system account differs from a human one, and how to use the useradd, usermod, groupadd, and gpasswd command families safely.

Warning

Placeholders such as <username> and <group> in the commands below are not literal values — replace them with a real, verified value before running anything. Do not copy a command containing < or > straight into a shell; those characters have shell redirection meaning. Run every account- and group-creation or deletion example only on a disposable lab VM.

What an Account Is and Where It Lives

Every user account consists of a login name, a unique UID, a primary group GID, a comment field, a home directory, and a login shell. This data is stored in a handful of plain text files, each line made of colon-separated (:) fields.

Instead of reading these files directly with cat, use getent, which goes through the Name Service Switch (NSS). Account data does not have to come only from a local file — it can come from LDAP or another directory service instead, so a local grep is not a complete check.

/etc/passwd: the Core Account Record

getent passwd root
getent passwd "$(id -un)"

A representative result:

root:x:0:0:root:/root:/bin/bash
ali:x:1000:1000:Ali Valiyev:/home/ali:/bin/bash

The seven fields mean:

# Field Meaning
1 login name the username visible in ls -l output
2 password placeholder x means the real hash lives in /etc/shadow
3 UID the account's numeric identifier
4 GID the primary group's numeric identifier
5 GECOS a comment, usually the full name or the account's purpose
6 home the directory entered at login
7 shell the program started at login

The x in the second field records an important piece of history: /etc/passwd is world-readable, so storing a password hash there directly used to be a real risk. The hash has since been moved to /etc/shadow, which only the superuser can read.

The seventh field is not always an interactive shell. Service accounts commonly set it to /usr/sbin/nologin or /bin/false, which blocks interactive login through that account without preventing a service from running its own process under that identity.

/etc/shadow: Password and Aging Data

/etc/shadow is readable only by root, and it holds the password hash and its aging information:

sudo getent shadow "$(id -un)"

A representative, shortened result:

ali:$y$j9T$...:19950:0:99999:7:::

The second field is the password hash. $6$ marks SHA-512, $y$ marks yescrypt; current Ubuntu and Debian are moving toward yescrypt. If the field begins with ! or *, the password is locked and login by password is disabled. The remaining fields cover the last password change date, minimum and maximum age, a warning period, and an expiry date; chage is a more convenient way to read them.

Reading and Setting Password Aging with chage

Parsing the raw day-count fields in /etc/shadow by hand is error-prone — they are stored as a number of days since 1970-01-01, not a calendar date. chage -l translates them into a readable report:

chage -l "$(id -un)"
Last password change                    : Jul 15, 2026
Password expires                        : never
Password inactive                       : never
Account expires                         : never
Minimum number of days between password change : 0
Maximum number of days between password change : 99999
Number of days of warning before password expires : 7

An ordinary user can read their own aging data with chage -l; reading another account's requires root. Setting a policy needs sudo:

sudo chage -M 90 -W 14 "$(id -un)"
sudo chage -l "$(id -un)"
  • -M 90 sets the maximum password age to 90 days — after that many days without a change, the password is treated as expired.
  • -W 14 sets a 14-day warning period before expiry, during which the user is reminded at login but can still authenticate normally.
  • -E <YYYY-MM-DD> sets a hard account expiration date, independent of the password itself — useful for a contractor or temporary account that must stop working entirely after a known date, even if the password was never expired.

Danger

Changing password-aging policy on a real account can lock out its own owner, including an administrator, if the new -M value is shorter than the time since the last password change. Check chage -l first, apply a policy change to a test account before a production one, and keep a second authenticated session or console access open in case a lockout needs to be reversed with sudo chage -M 99999 <username>.

What the User Actually Sees at Login

Once a password has expired (its age has passed -M, or the account itself has passed -E), the behavior differs depending on which limit was hit:

  • password expired (-M exceeded): at the next successful authentication, PAM forces the user to set a new password before the session is allowed to continue — the login prompt does not simply fail, it interrupts with "Your password has expired" and a mandatory password-change prompt.
  • account expired (-E exceeded): the login is refused outright with a message such as "Your account has expired," and no password prompt is even offered — a -E expiry is a hard stop, not something the user can resolve themselves.

This distinction is one interview question in itself: a locked-out user reporting "I can't log in" needs the administrator to check which of the two actually happened, using chage -l, before deciding whether the fix is "let them set a new password" or "the account itself needs its expiration date extended first."

Danger

Do not edit /etc/passwd, /etc/shadow, or /etc/group by hand in a plain text editor. One broken line can lock out every login, or break sudo entirely. Use vipw and vigr when a manual edit is unavoidable — they lock the file and validate its syntax on save. For everyday changes, use the useradd/usermod family described below instead.

/etc/group and /etc/gshadow: Groups

getent group sudo
getent group "$(id -gn)"

A representative result:

sudo:x:27:ali,dilnoza
ali:x:1000:

Four fields: group name, a password placeholder (x means gshadow is in use), the GID, and a comma-separated member list. Notice that the ali group's member list is empty, yet the ali account is still in that group — because its primary GID is recorded as 1000 in /etc/passwd. Primary group membership is not repeated in the member list.

Primary and Supplementary Groups

An account belongs to exactly one primary group at a time, and any number of supplementary groups. Keep the distinction clear:

  • The primary group comes from the GID field in /etc/passwd. A newly created file belongs to this group, unless it is created inside a setgid directory.
  • Supplementary groups come from the member list in /etc/group. They grant access, but they do not decide a new file's group on their own.

Seeing this directly:

id
id -gn
id -Gn

A representative result:

uid=1000(ali) gid=1000(ali) groups=1000(ali),27(sudo),1004(reporters)
ali
ali sudo reporters

id -gn shows only the primary group (ali); id -Gn shows every group. groups <username> returns the same list.

Note

A user who is added to a new group does not gain that group in an already-open shell session immediately. Group membership is computed at login time. If id <username> shows the new group but id -Gn in the current session does not, open a new login session. Do not paper over this with sudo chmod or a wider permission — the real cause is simply that membership has not been loaded yet.

If you need to create a file under a different primary group temporarily, newgrp <group> opens a new shell with that group as primary; exit returns you to the previous shell. This is not a permanent change.

System Accounts versus Human Accounts

Not every account represents a person. A web server, a database, or another application process should run under its own account — otherwise it runs as root and widens the attack surface unnecessarily. System and human accounts are separated by UID range, which is configured in /etc/login.defs:

grep -E '^(UID_MIN|UID_MAX|SYS_UID_MIN|SYS_UID_MAX|GID_MIN|GID_MAX)' /etc/login.defs

A representative result (Ubuntu/Debian):

UID_MIN                  1000
UID_MAX                 60000
SYS_UID_MIN              100
SYS_UID_MAX             999
GID_MIN                  1000
GID_MAX                 60000

Human accounts usually start at UID 1000; system accounts fall in the 100999 range. UID 0 is always root. This boundary is not a convention — it is a real setting that tools such as useradd -r read to decide which range to draw a number from. The RHEL family follows the same structure, though the exact numbers can differ, so read login.defs instead of assuming.

lslogins gives a convenient overview:

lslogins --user-accs
lslogins --system-accs

--user-accs lists human accounts, --system-accs lists system accounts. lslogins ships in the util-linux package and is normally available on both Ubuntu and the RHEL family.

Lab Environment

The commands below change system state: they create and delete accounts and groups. Run them only on a disposable Ubuntu Server LTS VM or container, and take a VM snapshot beforehand so you can return to a clean state afterward. These commands need sudo because account management requires superuser authority.

Confirm the starting state first:

getent passwd deploy || echo 'deploy does not exist'
getent group reporters || echo 'reporters does not exist'

If both names come back empty, the lab starts clean. If either name is already taken, pick a different, unused name for the examples below.

useradd versus adduser: Which One, and When

There are two distinct approaches, and they should not be confused:

  • useradd: a low-level command available on every distribution. It is not interactive and does exactly what its options say — no more, no less. It fits scripts and automation.
  • adduser: an interactive, high-level script on Debian and Ubuntu. It creates a home directory, prompts for a password, sets up a private group, and is generally more convenient for creating a human account by hand. On the RHEL family, adduser is often just a symlink to useradd, so it is not interactive there.

The two are not equivalent: useradd -m -s /bin/bash <name> and adduser <name> can produce a similar result, but adduser performs extra setup on its own. Use useradd in a portable script, and document the exact behavior you rely on.

Key useradd Options

Option Purpose
-m create the home directory and copy the /etc/skel template
-s <shell> set the login shell
-c "<comment>" set the GECOS comment
-g <group> set the primary group
-G <g1,g2> set the list of supplementary groups
-u <UID> request a specific UID
-d <path> set the home directory path
-r create it as a system account (UID from the system range)

useradd does not create a home directory by default; -m is required for that (some RHEL configurations may enable CREATE_HOME by default). With -m, the shell configuration files under /etc/skel are copied into the new home directory.

Creating a Human Account

sudo useradd -m -s /bin/bash -c "Dilnoza Karimova" -G reporters dkarimova
getent passwd dkarimova
sudo passwd dkarimova

useradd creates the account without a password, leaving login by password locked. passwd <username> sets a password and unlocks password login. -G reporters adds the user to the reporters supplementary group; that group must already exist (created below).

Verification:

id dkarimova
sudo lslogins dkarimova

id shows the UID and both primary and supplementary groups. Since this is a human account, the UID is expected to be above 1000.

Creating a System (Service) Account

An application account does not need interactive login and should come from the system UID range:

sudo useradd -r -s /usr/sbin/nologin -d /var/lib/reportd -c "report daemon" reportd
getent passwd reportd

A representative result:

reportd:x:999:999:report daemon:/var/lib/reportd:/usr/sbin/nologin

-r draws the UID from the system range, and -s /usr/sbin/nologin blocks interactive login. -m was deliberately not used here: an application account has no use for the shell configuration files in /etc/skel. If a runtime data directory is needed, create it explicitly with install -d, giving it a specific owner, group, and mode — the same tool used in Ownership.

usermod: Changing an Existing Account

usermod changes the comment, shell, primary group, and supplementary groups. The most dangerous, and most commonly misused, part is changing supplementary groups.

-aG: the Detail That Matters Most

sudo usermod -aG docker dkarimova
id dkarimova

-a (append) adds to the existing supplementary groups. Without -a, -G replaces all of the user's existing supplementary groups with only the ones listed. This can accidentally remove someone from sudo or another important group.

Danger

When adding a supplementary group, write usermod -aG almost without exception. Dropping the -a and running usermod -G docker <username> can remove the user from the sudo group and take away their administrator access. Record the current groups with id <username> before the change, and check again afterward.

Other useful changes:

sudo usermod -c "Dilnoza K." dkarimova     # change the comment
sudo usermod -s /usr/sbin/nologin dkarimova # block the shell
sudo usermod -L dkarimova                    # lock login by password
sudo usermod -U dkarimova                    # unlock it

-L prepends ! to the password hash to block password login, and -U reverses that. This does not automatically close SSH keys or other authentication methods — review every access path separately if the goal is to fully disable an account.

userdel: Removing an Account

sudo userdel dkarimova

This removes the account from /etc/passwd, /etc/shadow, and the group lists, but leaves the home directory in place. To also remove the home directory and mail spool:

sudo userdel -r dkarimova

Warning

Files owned by the account outside its home directory (for example, under /srv or /var) are not removed, and its UID no longer resolves to a name. If that UID is later assigned to a different account, the old files can appear to belong to the new one. Because of that, audit an account's files before deleting it: sudo find / -xdev -uid <UID> -printf '%p\n'. Before removing the home directory with -r, confirm no data that matters is left inside it — this step cannot be undone.

Managing Groups

Group commands mirror the account commands:

sudo groupadd reporters
sudo groupadd -g 2500 db
getent group reporters db

-g requests a specific GID; without it, groupadd automatically picks the next free one. Changing group properties:

sudo groupmod -n analysts reporters   # rename
sudo groupmod -g 2600 db              # change the GID

Removing a group:

sudo groupdel db

A group cannot be deleted while it is still some user's primary group — change that user's primary group first.

gpasswd: Managing Membership Precisely

gpasswd is a precise way to manage group members:

sudo gpasswd -a dkarimova reporters   # add one user
sudo gpasswd -d dkarimova reporters   # remove one user
sudo gpasswd -M ali,dkarimova reporters # set the full member list
getent group reporters

gpasswd -a adds one membership, just like usermod -aG, but because it targets a single group, it cannot accidentally affect other groups the way an unqualified -G can. gpasswd -M replaces the entire list — record the current members with getent group before using it.

Practical Scenario: A Service Account and a Team Group

Requirement: the reportd service runs under its own account; several human accounts in the reporters group can read and write report files; new files automatically pick up the reporters group and are group-writable. This scenario ties together useradd, groupadd, setgid, and umask in one practical task.

Do this only on a lab VM, after taking a snapshot.

1. Create the Group and the Service Account

sudo groupadd reporters
sudo useradd -r -s /usr/sbin/nologin -d /var/lib/reportd -g reportd \
    -G reporters reportd 2>/dev/null || \
    sudo useradd -r -s /usr/sbin/nologin -d /var/lib/reportd \
    -G reporters reportd
getent group reporters
id reportd

The first attempt sets a dedicated reportd group as primary; if it does not exist yet, useradd -r usually creates a same-named group for the user automatically, which is why the second form is included as a fallback. Either way, reportd ends up in the reporters supplementary group.

2. A Shared Directory with setgid

sudo install -d -o root -g reporters -m 2775 /srv/reports
stat -c '%A %a %U:%G %n' /srv/reports

install -d creates the directory with an explicit owner, group, and mode. The 2 in 2775 is setgid: new files inherit the reporters group. The 775 grants write access to group members. The difference between setgid and group write is covered in Special Permissions.

3. Using umask to Guarantee Group Write

setgid inherits the group, but whether the new file is actually group-writable depends on umask. Both the group members and the service should operate with umask 0002. For human members this is often already the default; for the service, set it in the unit file:

sudo systemctl edit reportd.service

Add the following to the editor:

[Service]
UMask=0002

How umask and setgid interact is covered in detail in umask.

4. Verification

sudo -u reportd -- sh -c 'umask; : > /srv/reports/probe.txt'
stat -c '%A %a %U:%G %n' /srv/reports/probe.txt
sudo rm -i -- /srv/reports/probe.txt

probe.txt is expected to be in the reporters group with mode 0664: setgid supplied the group, and umask 0002 left group write in place. This check only inspects metadata, not file content.

5. Rollback

In a lab, the most reliable rollback is returning to the VM snapshot. For a manual cleanup:

sudo userdel -r reportd
sudo groupdel reporters
sudo rm -rI -- /srv/reports

Run userdel -r first, then delete the group. Before removing /srv/reports, confirm with stat that it is only the lab directory.

Common Mistakes

Dropping -a from usermod -G

usermod -G <group> replaces the existing supplementary groups. When adding a group, write usermod -aG, or you risk removing the user from sudo or another important group.

Editing the passwd File by Hand

One mistake in a plain text editor can break login or sudo entirely. Use the useradd/usermod family, or the locking vipw/vigr tools.

Deleting an Account and Forgetting Its Files

userdel leaves files outside the home directory in place, and if the UID is reused, they can appear to belong to the new account. Audit with find -uid <UID> before deleting.

Mixing System and Human Accounts

Giving an application account an interactive shell and a password opens an unnecessary attack surface. Use -r and nologin for services; give human accounts an appropriate shell and password.

Confusing Primary and Supplementary Groups

A new file's group comes from the primary group (or a setgid directory), not from a supplementary group. Adding a supplementary group does not fix "files are created in the wrong group."

Expecting Group Membership Immediately

A new group does not apply to an already-open session. If id shows the new membership but the current shell does not, open a new login session — do not work around it with sudo or a wider permission.

Trusting a Numeric UID/GID Without Checking the Name

UID 1001 can be ali on one host and deploy on another. Verify the mapping with getent, as shown in Ownership, before creating accounts or groups, or before running chown.

Exercises

  1. List every field from getent passwd "$(id -un)" and getent group "$(id -gn)", and write down what each field means.
  2. From id, id -gn, and id -Gn, separate the primary group from the supplementary groups. Explain which one decides a new file's group.
  3. On a lab VM, create one human account and one service account. Compare UID range, home directory, and shell using getent passwd.
  4. Add a user to a supplementary group with usermod -aG, then remove them with gpasswd -d. Record the id and getent group output after each step. Do not test -G without -a on a real account.
  5. Combine a setgid directory, the reporters group, and umask 0002 to prove that a new file is created in the reporters group with mode 0664. Separate out how each component — primary group, setgid, umask — contributed to the result.

Verification criterion: in every answer, show the UID/GID number rather than just the name, distinguish primary from supplementary group, and identify exactly where a file's group came from.

After finishing the lab, return to the VM snapshot, or clean up the created accounts and groups with the rollback steps above. Check the target before cleaning up:

getent passwd reportd
getent group reporters

References

  • Linux man-pages: passwd(5): https://man7.org/linux/man-pages/man5/passwd.5.html
  • Linux man-pages: shadow(5): https://man7.org/linux/man-pages/man5/shadow.5.html
  • Linux man-pages: group(5): https://man7.org/linux/man-pages/man5/group.5.html
  • Linux man-pages: login.defs(5): https://man7.org/linux/man-pages/man5/login.defs.5.html
  • Linux man-pages: useradd(8): https://man7.org/linux/man-pages/man8/useradd.8.html
  • Linux man-pages: usermod(8): https://man7.org/linux/man-pages/man8/usermod.8.html
  • Linux man-pages: userdel(8): https://man7.org/linux/man-pages/man8/userdel.8.html
  • Linux man-pages: groupadd(8): https://man7.org/linux/man-pages/man8/groupadd.8.html
  • Linux man-pages: gpasswd(1): https://man7.org/linux/man-pages/man1/gpasswd.1.html
  • Debian manual: adduser(8): https://manpages.debian.org/stable/adduser/adduser.8.en.html
  • Linux man-pages: chage(1): https://man7.org/linux/man-pages/man1/chage.1.html