Run cat /etc/passwd on a fresh server and you will find thirty accounts on a machine one person uses. None of them are people. Understanding why is the fastest route to understanding how Linux security actually works — because almost every permission decision on the system comes down to two numbers.

Users are numbers

A username is a convenience for humans. The kernel only deals in the UID, a user ID number, and the GID, a group ID. Files store the numeric owner; ls -l looks the names up for display.

id
# uid=1000(kevin) gid=1000(kevin) groups=1000(kevin),27(sudo),998(docker)

# Show numbers instead of names
ls -ln

This has a consequence that catches people out, though not the one most people expect. Copy files to another machine preserving ownership and both tar and rsync write the owner’s name into the archive alongside the number, and both resolve by name on the way out: restore onto a machine where kevin holds 1001 rather than 1000 and the files still come back owned by kevin. The numbers travel only if you ask for themtar --numeric-owner, rsync --numeric-ids — and almost nobody passes those flags. What bites instead is a name the target machine has never heard of. With nothing to resolve, the raw number is written, and the file now belongs to nobody.

A bind-mounted volume in a container is the case with no name resolution at all, which is why it produces such baffling permission errors: the process inside runs as UID 999, and on the host UID 999 is some unrelated service account. Nothing translates between them. On an ordinary transfer, though, the useful step afterwards is not a blanket chown — that re-asserts an ownership which is usually already correct — but asking which numbers failed to resolve: find /srv /home -xdev -printf '%u %g\n' | sort | uniq -c. Every row that comes back as digits rather than a name is a file whose owner does not exist here, and the next useradd -r will hand that number to somebody. Restore as root, keep the names, then run that. Restoring a Linux Server works through it as stage 2.

Three kinds of account

UIDKindExamples
0rootroot
1–999System and service accountswww-data, sshd, postgres, nobody
1000+Human usersYou, and anyone else who logs in

Service accounts exist so that a compromised program is contained. Nginx runs as www-data, not as root, so an attacker who takes over the web server gets whatever www-data can reach — usually a document root and little else — rather than the whole machine. It is the single most effective structural defence on a Linux server, and it works silently, which is why it goes unnoticed.

You can see the intent in the account itself:

grep www-data /etc/passwd
# www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin

The shell is /usr/sbin/nologin. That account cannot log in at all, by design. When you create an account for a service of your own, do the same — it is one flag and it removes an entire category of risk.

Reading /etc/passwd and /etc/shadow

Despite the name, /etc/passwd contains no passwords. It is world-readable, seven colon-separated fields per line:

kevin:x:1000:1000:Kevin:/home/kevin:/bin/bash
  |   |   |    |     |       |          |
  |   |   |    |     |       |          `- login shell
  |   |   |    |     |       `------------ home directory
  |   |   |    |     `-------------------- description
  |   |   |    `-------------------------- primary GID
  |   |   `------------------------------- UID
  |   `----------------------------------- password placeholder
  `--------------------------------------- username

The x means “the hash is in /etc/shadow“. That file is readable only by root, which is the entire point of the split: programs need to look up usernames and home directories constantly, and none of them need the hashes.

# Password status for one account
sudo passwd -S kevin

# Real humans, by UID range
awk -F: '$3 >= 1000 && $3 < 65534 {print $1}' /etc/passwd

# Accounts with a usable login shell
grep -v -e nologin -e /bin/false /etc/passwd

That last command is a worthwhile audit on any machine you have inherited: it lists every account that could actually be logged into. Anything on it you cannot account for deserves investigation.

Groups, primary and supplementary

Every user has one primary group, which is the group assigned to files they create. They may also belong to any number of supplementary groups, which grant additional access.

# Add someone to a group
sudo usermod -aG docker kevin

# Check
groups kevin
id kevin

The -a is not optional. usermod -G docker kevin without it replaces every supplementary group with just docker — which typically removes the user from sudo at the same time. If that user was your only administrative account and you are working over SSH, you have locked yourself out of administration on that machine. Always -aG.

The second trap: group membership is read at login. Add yourself to a group and your current shell does not have it. Log out and back in, or start a new session with newgrp docker. “I added myself to the docker group and still get permission denied” is nearly always this.

Groups that are really administrative access

GroupWhat it grantsEffectively root?
sudo / wheelRun commands as root via sudoYes, openly
dockerTalk to the Docker daemonYes — see below
admRead all log filesNo, but reads everything
diskRaw access to block devicesYes, in practice
shadowRead password hashesEffectively yes

The docker one deserves stating without hedging. A member of the docker group can start a container that mounts the host’s root filesystem and edit any file on it as root. That is not a bug — it is what a container runtime does — but it means adding someone to docker is the same decision as giving them passwordless sudo. Make it deliberately. The same is true of disk: raw block device access means reading and writing any file, permissions notwithstanding.

The name differs by family: Debian and Ubuntu use sudo, Red Hat and Arch use wheel. Check which one your /etc/sudoers actually grants before adding anyone to either.

What root actually is

Root is not a special account with a flag set. Root is UID 0, and the kernel skips permission checks for UID 0. That is the whole mechanism. Rename the account, create a second account with UID 0, and it is equally root — which is why an unexpected UID 0 in /etc/passwd is a serious finding:

# There should be exactly one line here
awk -F: '$3 == 0 {print $1}' /etc/passwd

Most distributions now discourage logging in as root at all. Ubuntu ships with the root password locked; you become root through sudo instead. That is better for three reasons: sudo logs who ran what, it can be granted narrowly rather than all at once, and it removes the shared password nobody changes when someone leaves.

# One command as root
sudo systemctl restart nginx

# A root shell (prefer not to linger here)
sudo -i

# Who has sudo, and what they may run
sudo -l
getent group sudo

The sudo reference covers granting narrow permissions, which is worth doing when someone needs to restart one service rather than administer the machine.

Creating accounts

# A human user (Debian and Ubuntu: interactive, sets up the home directory)
sudo adduser alice
sudo usermod -aG sudo alice

# A human user, portable across distributions
sudo useradd -m -s /bin/bash alice
sudo passwd alice

# A service account: no home, no login, no password
sudo useradd --system --no-create-home --shell /usr/sbin/nologin myapp

adduser is a friendly wrapper around useradd and only exists on Debian-family systems. useradd is universal but does nothing you did not ask for — notably, without -m it creates a user with no home directory, which produces a confusing broken login later.

Removing accounts

# Lock the account without deleting anything (do this first)
sudo usermod -L alice
sudo usermod -s /usr/sbin/nologin alice

# Find what they own before deleting
sudo find / -user alice -not -path '/proc/*' 2>/dev/null

# Delete, keeping the home directory
sudo userdel alice

# Delete, removing the home directory too
sudo userdel -r alice

Lock first, delete later. Deleting an account leaves its files owned by a UID with no name, and if you later create another user the numbers get recycled — the new person silently inherits the old one’s files. Locking is instant, reversible, and buys you time to find out what they owned.

Also check for scheduled jobs and SSH keys, both of which outlive a casual account removal:

sudo crontab -u alice -l
sudo cat /home/alice/.ssh/authorized_keys

Things that catch people out

SymptomCause
Added to a group, still deniedNot logged out and back in
User lost sudo after a group changeusermod -G without -a
Copied files owned by the wrong personThe name resolved to a different number on the target — or the copy was made with --numeric-owner or --numeric-ids. See Restoring a Linux Server
Files owned by a bare numberThe account was deleted — or the files arrived from another machine and that name does not exist here
New user cannot log inuseradd without -m, or a nologin shell
Container cannot write to a mounted directoryContainer UID does not match the host owner
Service runs as root unnecessarilyNo dedicated service account was created

Quick reference

You wantCommand
Who am I, and in what groupsid
Members of a groupgetent group docker
Add to a group, safelysudo usermod -aG GROUP USER
List real human accountsawk -F: '$3>=1000 && $3<65534 {print $1}' /etc/passwd
Check for extra UID 0 accountsawk -F: '$3==0 {print $1}' /etc/passwd
Lock an accountsudo usermod -L USER
What a user ownssudo find / -user USER
Create a service accountsudo useradd --system --shell /usr/sbin/nologin NAME

There is a much longer version of what happens when one of these accounts actually arrives. The Life of a Login follows the seven stages between someone typing a password and having a usable shell: the three binaries sshd has become, PAM’s stacks and why a key-based login skips one of them entirely, which password hash your machine is really using, the account checks that run even when authentication did not, session classes and the one sentence that explains every broken systemctl --user, where PATH comes from, and what survives your logout.

Related reading

  • Restoring a Linux Server — the long one: which of the two identifiers actually travels, and everything else a restore decides that the backup did not
  • File permissions — what those UIDs and GIDs are checked against
  • sudo — becoming root properly, and granting narrow access
  • The Linux filesystem — where /etc/passwd and home directories sit
  • Securing a new server — creating the first non-root account
  • ssh — keys, and which account they belong to
  • Migrating a server — moving to a new machine, and the ownership step it prints when the files arrive