We closed Module 4 with a list of debts, and this lesson settles the first and most uncomfortable one: svc-tramontana exists "by magic" and the tramontana group does not exist at all. You have been writing chown svc-tramontana:tramontana on files whose group nobody had ever created. That ends today. You are going to see what an identity really is in Linux — a number, not a name — which files are the source of truth and what each field contains, how person accounts and service accounts are created (they look nothing alike), and why the most expensive mistake in the whole module fits into a single missing letter: usermod -G instead of usermod -aG.
Contents
- The identity model: UID, GID and supplementary groups
- The four files of truth, field by field
- Account types and the UID range in
/etc/login.defs - Creating users:
useradd,adduserand/etc/skel - Passwords: setting, locking and the meaning of
!and* - Modifying and deleting accounts, and orphaned files
- Account expiry with
chage - Groups: creating, modifying and why you have to log in again
- Service accounts: why the app never runs as root
- Auditing: who is who and who has logged in
- Tramontana case: settling the previous module's debt
- The identity model: UID, GID and supplementary groups
The kernel does not know what operator is. The kernel only handles numbers:
- UID (User ID): identifies the user. The owner of a file is a number stored in the inode, not a string.
- GID (Group ID): identifies a group. Every user has one primary group (the one a new file they create inherits) and zero or more supplementary groups.
Names exist only for our convenience, and the number→name translation is done by the system library reading /etc/passwd and /etc/group. That is why, if you delete a user, ls -l starts showing raw numbers: the file keeps UID 1003 even though nobody is called that any more.
Read it like this: operator is 1000, its primary group is operator (1000) and it also belongs to adm and sudo as supplementary groups. That distinction matters:
The primary group is defined in field 4 of /etc/passwd and is the one assigned to the files the user creates; the supplementary ones are defined in field 4 of /etc/group and only widen access. A process's effective group can be changed on the fly with newgrp or sg.
On Ubuntu, by default each user gets their own group with the same name (the UPG scheme, User Private Group). It is not a whim: it lets you work with umask 002 without exposing your files to half the machine, because "the group" is only you.
- The four files of truth, field by field
/etc/passwd — who exists
Seven fields separated by :. It is readable by everybody (644) because any process needs to translate a UID into a name for a simple ls -l.
$ grep -E '^(root|operator|svc-tramontana):' /etc/passwd
root:x:0:0:root:/root:/bin/bash
operator:x:1000:1000:Tramontana Operator,,,:/home/operator:/bin/bash
svc-tramontana:x:997:1002:Tramontana Bookings Service:/opt/tramontana:/usr/sbin/nologin| # | Field | Example | Notes |
|---|---|---|---|
| 1 | Login name | operator |
Unique; lower case by convention |
| 2 | Password | x |
The x means "it is in /etc/shadow" |
| 3 | UID | 1000 |
What really matters |
| 4 | Primary GID | 1000 |
Points to /etc/group |
| 5 | GECOS | Tramontana Operator,,, |
Real name, office, phone numbers; edited with chfn |
| 6 and 7 | Home and shell | /home/operator, /bin/bash |
The home may not exist on system accounts; /usr/sbin/nologin for anyone who must not log in |
/etc/shadow — the secret
Nine fields. Permissions 640, ownership root:shadow: readable by root and by the shadow group, invisible to everyone else. That is where the hash lives, and a hash readable by anybody is a hash somebody will eventually break with a GPU.
$ sudo grep -E '^(operator|svc-tramontana):' /etc/shadow
operator:$y$j9T$Qk3rP0mZ7uY2xB1c$Fq8sT...ZR1:20310:0:90:7:14::
svc-tramontana:!:20310:0:99999:7:::| # | Field | Example value | Meaning |
|---|---|---|---|
| 1 | User | operator |
Links to passwd |
| 2 | Hash | $y$j9T$... |
See below |
| 3 | Last change | 20310 |
Days since 1970-01-01 |
| 4 and 5 | Minimum and maximum | 0 / 90 |
Days before it can be changed and days of validity |
| 6 and 7 | Warning and inactivity | 7 / 14 |
Advance warning and grace period after expiry before disabling |
| 8 and 9 | Account expiry and reserved | (empty) | Absolute date in days since 1970; the 9th is unused |
The hash has the format $id$parameters$salt$hash. On Ubuntu 24.04 the default algorithm is yescrypt ($y$), which replaced SHA-512 ($6$) because it resists GPU and ASIC attacks thanks to its memory cost:
| Prefix | Algorithm | Status |
|---|---|---|
$y$ |
yescrypt | Default on Ubuntu 24.04 |
$6$ |
SHA-512 | Still valid; legacy |
! or !$y$... |
Locked account | The hash may still be underneath |
* |
No usable password | Typical of system accounts |
| (empty) | No password: anybody gets in | A real danger, fix it now |
/etc/group and /etc/gshadow
$ getent group tramontana adm sudo
tramontana:x:1002:operator,luis,svc-tramontana
adm:x:4:syslog,operator
sudo:x:27:operatorFour fields: name, password (x), GID and list of supplementary members. Careful: anyone who has that group as their primary does not appear in that list. /etc/gshadow (640, root:shadow) holds the group password — an oddity almost nobody uses — and the group administrators, who can add members with gpasswd without being root.
- Account types and the UID range in
/etc/login.defs
/etc/login.defs| Type | UID | Examples | Traits |
|---|---|---|---|
| Superuser | 0 | root |
Skips every permission check |
| System / service | 1–999 | daemon, syslog, svc-tramontana (997) |
No login, no expiry, created with -r |
| Person | ≥ 1000 | operator (1000), luis (1001) |
Home, shell, password, expiry |
nobody |
65534 | nobody |
Minimal identity for unprivileged processes |
The one deciding those ranges is /etc/login.defs:
$ grep -E '^(UID_MIN|UID_MAX|SYS_UID_MIN|SYS_UID_MAX|ENCRYPT_METHOD|UMASK|PASS_MAX_DAYS)' /etc/login.defs
PASS_MAX_DAYS 99999
UID_MIN 1000
UID_MAX 60000
SYS_UID_MIN 100
SYS_UID_MAX 999
ENCRYPT_METHOD YESCRYPT
UMASK 022The important part: UID 0 is root not because of the name, but because of the number. If you create a second account with UID 0, you have two roots and no audit trail able to tell them apart in the inode. Never do it.
- Creating users:
useradd, adduser and /etc/skel
useradd, adduser and /etc/skeluseradd is the low-level tool of the shadow suite: it does exactly what you ask, not one thing more. Its key options:
| Option | Effect |
|---|---|
-m |
Creates the home and copies /etc/skel (without it, there is no home) |
-d PATH |
A home other than the default |
-s SHELL |
Login shell |
-u UID |
A specific UID |
-g GROUP |
Primary group |
-G g1,g2 |
Supplementary groups |
-c "TEXT" |
GECOS field; -N avoids creating the user's own group |
-r |
System account: UID from the low range, no expiry, no home unless -m |
$ sudo useradd -m -s /bin/bash -c "Luis Ferrer,Development,,," luis
$ sudo passwd luis
New password:
Retype new password:
passwd: password updated successfullyadduser is a Debian Perl script that wraps useradd and does what a human expects: it picks a free UID, creates the home with the right permissions, creates the user's own group, copies /etc/skel, asks for the password and fills in the GECOS interactively. An honest comparison:
| Aspect | useradd |
adduser |
|---|---|---|
| Portability | Universal on Linux | Debian/Ubuntu only |
| Interactive | No | Yes (ideal by hand) |
| Home | Only with -m |
Always |
| Password | Separately, with passwd |
It asks for it |
| Scripts | Yes, this is the right one | Requires --disabled-password and care |
A practical rule: by hand, adduser; in a script, useradd, because its behaviour does not depend on a distribution-supplied configuration file.
/etc/skel (which on Ubuntu contains .bashrc, .profile and .bash_logout) is the home template: everything inside it is copied when the account is created with -m. It is the place to impose a corporate .bashrc or the red production prompt from Module 3.
- Passwords: setting, locking and the meaning of
! and *
! and *passwd # your own: asks for the current one before changing it
sudo passwd luis # root changes anybody's without knowing the previous one
echo 'luis:ALongAndUniquePassword' | sudo chpasswd # in bulk, non-interactive
sudo passwd -e luis # force a change on the next login (= chage -d 0)Locking and unlocking:
sudo passwd -l luis # prepends '!' to the hash
sudo passwd -S luis # -> luis L 2026-08-18 0 99999 7 -1
sudo passwd -u luis # removes the '!' and restores the original hashThe L from passwd -S means "locked"; P would be a usable password and NP no password, which is an emergency.
| Content of field 2 | Meaning | Can you go back? |
|---|---|---|
$y$... |
Valid password | — |
!$y$... |
Locked: the hash is intact underneath | Yes, passwd -u |
! |
Locked and with no hash to restore | No, you have to set a new one |
* |
Never had a password; password login impossible | Not applicable |
| (empty) | Gets in without typing anything | Fix it immediately |
usermod -L does the same as passwd -l to the hash. The practical difference is not between them, but in what neither of them does: locking the password does not prevent logging in with an SSH key, nor does it kill open sessions. For a real staff departure you need the full combination:
sudo usermod -L -e 1 -s /usr/sbin/nologin luis # locks, expires the account and removes the shell
sudo pkill -u luis # closes whatever they had open-e 1 sets the expiry to 2 January 1970: the account is expired and PAM rejects access whichever way it comes.
- Modifying and deleting accounts, and orphaned files
usermod changes what already exists. And here is the module's classic mistake:
# WRONG: replaces ALL supplementary groups with 'tramontana'
sudo usermod -G tramontana operator
# RIGHT: adds without touching the ones it already had
sudo usermod -aG tramontana operatorWithout the -a (append), operator drops out of sudo and out of adm at the same instant. And if the root session was closed as well, you have just lost administration of the machine and it is time to boot into recovery mode. Always check before and after with id.
Other common uses:
sudo usermod -c "Luis Ferrer,Development,,," luis # GECOS
sudo usermod -l lferrer luis # rename the login (does not move the home)
sudo usermod -d /home/lferrer -m lferrer # move the home with its contentsDeleting:
sudo userdel luis # deletes the account, keeps the home
sudo userdel -r luis # also deletes the home and the mail spooluserdel -r does not find what the user left outside their home: a file in /srv, a cron job, a temporary file. Those files are left with a nameless UID and, worse still, the next user who receives that UID will inherit their ownership. A mandatory audit before reassigning:
$ sudo find / -xdev \( -nouser -o -nogroup \) 2>/dev/null
$ sudo find /srv /opt /var -xdev -uid 1001 2>/dev/null
/srv/tramontana/backups/temp/luis-report.csvCompliance warning. Staff onboarding and offboarding are not an isolated technical task: in a real environment they must follow an approved procedure (who authorises it, within what deadline access is revoked, what is done with the employee's files, which may contain guests' personal data). Document every onboarding and every offboarding with a date and the person who authorised it, and have the security or compliance officer (GDPR) validate the procedure before you apply it.
- Account expiry with
chage
chagechage handles fields 3 to 8 of /etc/shadow without you having to count days since 1970.
$ sudo chage -l operator
Last password change : Aug 18, 2026
Password expires : Nov 16, 2026
Password inactive : Nov 30, 2026
Account expires : never
Maximum number of days between password change : 90| Option | Effect |
|---|---|
-M 90 |
Maximum password validity |
-m 1 |
Minimum between changes (stops people rotating it five times to get back to the same one) |
-W 7 |
Warning days |
-I 14 |
Grace days after expiry |
-E 2026-12-31 |
Expiry date of the account (temporary contracts) |
-d 0 |
Forces a change at the next login |
The real password policy — minimum length, complexity, dictionary, history — does not live here: it is imposed by PAM with pam_pwquality, and it is the subject of 06-06. chage only sets the deadlines.
- Groups: creating, modifying and why you have to log in again
sudo groupadd -g 1002 tramontana # explicit GID, reproducible across machines
sudo groupmod -n tramontana-app tramontana # rename
sudo groupdel old-group # fails if it is somebody's primary group
sudo gpasswd -a luis tramontana # add a member
sudo gpasswd -d luis tramontana # remove them
sudo gpasswd -A operator tramontana # 'operator' can administer the group without being rootgpasswd -a and usermod -aG do the same thing; gpasswd has the advantage of not allowing the forgotten--a mistake, because its syntax is always additive.
And now the detail that costs people whole afternoons: adding somebody to a group does not affect their open sessions.
$ sudo usermod -aG tramontana operator
$ id operator # the database: 1002(tramontana) already shows
$ id # YOUR session: it does not show yet
uid=1000(operator) gid=1000(operator) groups=1000(operator),4(adm),27(sudo)The reason is that supplementary groups are set at login time, get recorded in the process's credentials and are inherited by its children. No configuration file is going to change a process that is already running. The ways out:
newgrp tramontana # opens a subshell with the new group (an emergency fix)
exec su - "$USER" # a clean re-login on the spotThe right thing to do is log out and log back in. And the same applies to services: if you change the groups of svc-tramontana, you have to restart the service, reloading is not enough.
- Service accounts: why the app never runs as root
An application exposed on port 8080 is the most likely way into your server. If it runs as root, any remote execution flaw is game over: the whole machine. If it runs as svc-tramontana, the attacker inherits an identity that cannot read /etc/shadow, cannot write to /etc, cannot install anything and cannot read other users' files.
What distinguishes a service account:
- A system UID (
-r, below 1000) and no expiry: a service account that expires brings the service down at three in the morning. - No login shell:
/usr/sbin/nologin(which prints a message and exits with an error) or/bin/false. - No usable password (
!or*inshadow) and no home of its own: it normally points at the application directory.
- Auditing: who is who and who has logged in
| Command | Answers |
|---|---|
id [user] |
UID, GID and groups according to the database |
groups [user] |
Just the group names |
getent passwd luis |
Queries all the sources (/etc/passwd, LDAP…), not just the file |
getent group tramontana |
The same for groups |
who / w |
Who is connected right now; w adds the load and what they are running |
last -n 5 |
Login history (it reads /var/log/wtmp) |
lastlog |
Last login of each account; it gives away dead accounts |
$ w | tail -2
operator pts/0 10.0.2.1 09:02 0.00s 0.09s 0.01s w
luis pts/1 10.0.2.1 08:47 12:03 0.04s 0.04s -bashgetent is always preferable to grep /etc/passwd: the day the company adds a corporate directory, grep will lie and getent will still be telling the truth.
- Tramontana case: settling the previous module's debt
The starting state on srv-tramontana: operator (1000) and luis (1001) exist; svc-tramontana and the tramontana group do not, even though half a module talked about them. We fix it in order, and with a prior backup of the identity files as the course's convention requires.
sudo cp -a /etc/passwd /etc/passwd.bak-$(date +%F) # prior backup, as always
sudo cp -a /etc/group /etc/group.bak-$(date +%F)
# 1) The shared group, with a fixed GID so it matches on every machine
sudo groupadd -g 1002 tramontana
# 2) The service account: system, no shell, no home of its own, no password
sudo useradd -r -u 997 -g tramontana \
-d /opt/tramontana -M \
-s /usr/sbin/nologin \
-c "Tramontana Bookings Service" svc-tramontana
# 3) The people who work with the application join the group
sudo usermod -aG tramontana operator
sudo usermod -aG tramontana luis-M stops the home being created: /opt/tramontana already exists and we do not want useradd dumping /etc/skel on top of it. We verify before touching anything else:
$ getent passwd svc-tramontana && getent group tramontana
svc-tramontana:x:997:1002:Tramontana Bookings Service:/opt/tramontana:/usr/sbin/nologin
tramontana:x:1002:operator,luisNow the ownership of the directories, which until today was a promise:
sudo chown -R svc-tramontana:tramontana /opt/tramontana
sudo chown -R operator:tramontana /srv/tramontana/backups
sudo chmod -R u=rwX,g=rX,o= /opt/tramontana
sudo chmod -R u=rwX,g=rwX,o= /srv/tramontana/backups
sudo chmod 2770 /opt/tramontana/shared/uploads
ls -ld /opt/tramontana /opt/tramontana/app /srv/tramontana/backupsdrwxr-x--- 5 svc-tramontana tramontana 4096 Aug 18 09:20 /opt/tramontana lrwxrwxrwx 1 svc-tramontana tramontana 22 Aug 12 18:02 /opt/tramontana/app -> releases/3.2.1 drwxrwx--- 5 operator tramontana 4096 Aug 18 04:20 /srv/tramontana/backups
Notice the o=: nobody outside the group sees anything, applying the convention of "a shared group instead of permissions for others". That 2 in 2770 on uploads is the SGID bit, which makes everything created inside belong to the tramontana group regardless of who creates it; we take it apart in the next lesson.
And the documentation, which is part of the job:
sudo tee -a /opt/tramontana/HISTORY >/dev/null <<'END'
2026-08-18 Identities formalised (operator)
- groupadd -g 1002 tramontana ; useradd -r -u 997 ... svc-tramontana
- operator and luis added to 'tramontana' (requires logging in again)
- chown svc-tramontana:tramontana /opt/tramontana ; operator:tramontana on backups
ENDCommon Mistakes and Tips
usermod -Gwithout-a. The most expensive mistake in the lesson: it replaces the whole list and can throw you out ofsudo. Before touching groups, save the output ofid user; afterwards, compare it.- Expecting a new group to work in the current session. It will not.
id userqueries the database, plainidqueries your session. Log out and back in, and restart the affected services. - Editing
/etc/passwdor/etc/shadowwithnano. Usevipwandvipw -s(andvigrfor the groups): they lock the file and validate the syntax on exit. A corruptshadowis a server nobody can get into. - Creating the service account with a shell. If
svc-tramontanahas/bin/bash, any route that manages to execute as that user gets an interactive shell.nologinis not cosmetic. - Confusing locking with disabling.
passwd -ldoes not prevent access with an SSH key. For a departure:usermod -L -e 1 -s /usr/sbin/nologinandpkill -u. And do not reuse the UID of a deleted account without auditing first withfind -uid. - Tip: set the service UIDs and GIDs (997, 1002) explicitly and write them down. The day you restore a backup on another machine, the numbers have to match or the permissions will slip out of place across the board.
Exercises
- A complete intern onboarding. Create the
internaccount withuseradd: its own home, a Bash shell, GECOS "Operations Intern", supplementary member oftramontana, a password that must be changed at first login and an account expiry of 31 December 2026. Verify everything without logging in as them. - Diagnosing a disaster. Somebody has run
sudo usermod -G tramontana operator. Describe the exact symptomoperatorwill see at their next login, how you would confirm it and how you repair it if you no longer havesudo. - Account audit. Write a command that lists, from
/etc/passwd, only the person accounts (UID ≥ 1000, excludingnobody) with their shell, and another that detects accounts with an empty password in/etc/shadow.
Solutions
1.
sudo useradd -m -s /bin/bash -G tramontana -c "Operations Intern,,," intern
echo 'intern:Provisional.2026!' | sudo chpasswd
sudo chage -d 0 -E 2026-12-31 intern # -d 0 forces a change; -E expires the account$ id intern
uid=1004(intern) gid=1004(intern) groups=1004(intern),1002(tramontana)
$ sudo chage -l intern | grep -i account
Account expires : Dec 31, 2026Here -G without -a is correct, because the account is being created and there are no previous groups to lose. The -a only matters in usermod.
2. The symptom: operator logs in normally, but any sudo answers operator is not in the sudoers file. This incident will be reported., and on top of that they lose the read access to the logs that adm gave them. Confirmation:
4(adm) and 27(sudo) are missing. Repair with privileges: sudo usermod -aG adm,sudo operator and log in again. Without sudo and without another administrator account there is no shortcut from inside: you have to reboot the VM, edit the GRUB entry to boot with init=/bin/bash, remount the root filesystem with mount -o remount,rw / and run usermod -aG adm,sudo operator. That recovery procedure is covered in depth in 07-01; the fact that it exists is precisely why physical access, or access to the VM's console, is in itself administrator access.
3.
$ awk -F: '$3 >= 1000 && $3 != 65534 { printf "%-10s uid=%-6s %s\n", $1, $3, $7 }' /etc/passwd
operator uid=1000 /bin/bash
luis uid=1001 /bin/bash
intern uid=1004 /bin/bashSilence in the output means there are none, which is exactly what you want to see: the "silence if all is well" convention applied to an audit.
Conclusion
There is no magic left in the identities on srv-tramontana. You know that a user is a number; you read the nine fields of /etc/shadow, you recognise a $y$ yescrypt hash and you tell !, * and the empty field apart; you separate system accounts from person accounts by the UID range in /etc/login.defs; you create accounts with useradd in scripts and with adduser by hand, knowing what /etc/skel contributes; you set deadlines with chage and leave the password policy for 06-06; and you have taken on board that usermod -G without -a is a quick way to lose the machine and that a new group does not exist until the session is reopened.
And, above all, you have settled the debt: the tramontana group (gid 1002) exists, svc-tramontana (uid 997) is a real system account with no shell and no password, operator and luis are in the group, /opt/tramontana and /srv/tramontana/backups have an owner and the change is written down in the HISTORY.
The other half of the equation is missing. You have identities, but sudo is still a magic word you type without knowing who decides what it lets you do, and the odd bits you glimpsed in 02-07 — that s instead of the x — are still unexplained, even though you have just used one by setting 2770 on uploads. In the next lesson, sudo and Special Permissions, both of those end: you will write a sudoers rule that lets operator restart and deploy the application without being root, you will really understand SUID, SGID and the sticky bit, you will replace a SUID with capabilities so that the app can listen on port 80, and you will give Luis access to the logs with an ACL without putting him in the adm group.
Linux Course: From Beginner to System Administrator
Module 1: Introduction to Linux
- What Is Linux?
- History of Linux
- Linux Distributions
- Installing Linux
- First Contact with the System
- The Linux File System Structure
Module 2: Basic Linux Commands
- Introduction to the Command Line
- Getting Help and System Documentation
- Navigating the File System
- File and Directory Operations
- Viewing and Editing Files
- Hard and Symbolic Links
- File Permissions and Ownership
Module 3: Advanced Command-Line Skills
- The Shell Environment: Variables, Aliases and History
- Using Wildcards and Regular Expressions
- Searching Files and Content: find, locate and grep
- Pipes and Redirection
- Text Processing: cut, sort, uniq, sed and awk
- Process Management
- Scheduling Tasks with Cron
- Networking Commands
Module 4: Shell Scripting
- Introduction to Shell Scripting
- Variables and Data Types
- Script Input, Output and Arguments
- Control Structures
- Functions and Libraries
- Debugging and Error Handling
- Production Scripts: Best Practices
Module 5: System Administration
- User and Group Management
- sudo and Special Permissions
- Package Management
- Disk Management
- systemd and Service Management
- System Logs: journald and syslog
- System Monitoring and Performance Tuning
- Backup and Restore
Module 6: Networking and Security
- Network Configuration
- SSH and Remote Access
- Firewalls and Perimeter Security
- Intrusion Detection Systems
- Secrets Management and TLS Certificates
- Securing Linux Systems
Module 7: Advanced Topics
- The Boot Process and System Recovery
- Advanced Diagnostics: strace, perf and eBPF
- Linux Kernel Tuning
- Virtualization with Linux
- Linux Containers and Docker
- Automation with Ansible
- High Availability and Load Balancing
