Users, Groups and Permissions in Practice
Managing accounts and access on Linux: users and groups, ownership, permission bits, umask, special permissions and sudo.
This page builds on
Users, Permissions and Access Control, which explains accounts, root and sudo, the rwx bits
for user, group and others, and octal notation. Here you do the
work: manage accounts and groups, set permissions precisely, and
hand out administrator rights in small, controlled pieces.
Where accounts are stored
Linux keeps local accounts in three plain-text files under
/etc. Tools such as useradd and
passwd edit them for you; you read them to understand
what those tools did. (In companies, accounts often come from a
directory service such as LDAP (Lightweight Directory Access
Protocol) or Active Directory as well;
getent passwd ana looks a user up in every configured
source, not only in the local file.)
/etc/passwd
/etc/passwd is the local account
database: one line per account with seven colon-separated fields —
name, password placeholder, user ID (UID), primary group ID (GID),
comment, home directory and login shell. Despite the name it no
longer holds passwords, and every user can read it, because programs
such as ls need it to turn UIDs into names.
$ grep -E '^(root|www-data|ana):' /etc/passwd
root:x:0:0:root:/root:/bin/bash www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin ana:x:1001:1001:Ana Lima,,,:/home/ana:/bin/bash
| Field | Example | Meaning |
|---|---|---|
| 1 Name | ana |
The login name |
| 2 Password | x |
"The hash is in /etc/shadow" |
| 3 UID | 1001 |
User ID: what the kernel actually uses. 0 is root; regular users start at 1000 on current distributions, lower numbers are system accounts for services |
| 4 GID | 1001 |
The primary group's ID |
| 5 Comment | Ana Lima,,, |
Full name and optional contact details (historically called GECOS) |
| 6 Home | /home/ana |
Home directory |
| 7 Shell | /bin/bash |
Program started at login;
/usr/sbin/nologin refuses interactive logins
|
Service accounts such as www-data have a
nologin shell on purpose: the web server runs as that
user, but nobody can log in as it. cut pulls single
fields out of such lines, as described on
Pipes, Redirection and Filters:
cat <<'EOF' > accounts.txt
root:x:0:0:root:/root:/bin/bash
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
ana:x:1001:1001:Ana Lima,,,:/home/ana:/bin/bash
ben:x:1002:1002:Ben Okafor,,,:/home/ben:/bin/bash
EOF
echo "--- name and shell:"
cut -d: -f1,7 accounts.txt
echo "--- accounts that can log in with bash:"
grep ':/bin/bash$' accounts.txt | cut -d: -f1
--- name and shell: root:/bin/bash www-data:/usr/sbin/nologin ana:/bin/bash ben:/bin/bash --- accounts that can log in with bash: root ana ben
/etc/shadow
/etc/shadow holds the password
hashes and password-aging settings, one line per account. It is
readable only by root (on Debian and Ubuntu also by the
shadow group: mode 640, owner
root:shadow). On RHEL the mode is 000 and only root can
read it. Splitting the hashes off from
/etc/passwd keeps them away from ordinary users, who
could otherwise copy them and try to crack them offline.
$ sudo grep '^ana:' /etc/shadow
ana:$y$j9T$Qm1r…$4hV0…:20362:0:99999:7:::
The fields: name, hash, date of the last password change (in days
since 1 January 1970), minimum and maximum days between changes,
warning days, and three fields for inactivity and expiry. The hash
starts with its algorithm: $y$ is yescrypt (the default
on current Debian, Ubuntu and Fedora), $6$ is
SHA-512-crypt, a salted and iterated scheme built on SHA-512 (Secure
Hash Algorithm; the default on Red Hat Enterprise Linux, RHEL). A
hash field starting with ! means the password is
locked; * means the account never had a usable
password. sudo chage -l ana shows the aging settings in
readable form.
/etc/group
/etc/group lists the groups: name, password
placeholder, GID, and the members for whom it is a supplementary
group, comma-separated:
sudo:x:27:ana ana:x:1001: ben:x:1002: developers:x:1003:ana,ben
Notice that ana is not listed as a member of the group
ana: a user's primary group comes from field 4 of
/etc/passwd, not from this list.
Primary and supplementary groups
Every user has exactly one
primary group: the group
recorded in the user's /etc/passwd line, which becomes
the group owner of every file the user creates. Most distributions
create a personal group with the same name as the user for this (a
"user private group"). On top of that a user can be in any number of
supplementary groups, which grant access to files
owned by those groups but do not affect new files.
id shows both, with numbers and names;
groups shows only the names. Both describe your current
login session:
$ id
$ groups
$ id ben
uid=1001(ana) gid=1001(ana) groups=1001(ana),27(sudo),1003(developers) ana sudo developers uid=1002(ben) gid=1002(ben) groups=1002(ben),1003(developers)
gid= is the primary group, groups= lists
all of them. Group membership is read at login: after someone adds
you to a group, you only get the new rights in a new login session
(log out and in again, or start a new SSH (Secure Shell)
connection). newgrp developers opens a subshell with
the new group as a stopgap.
Creating and managing accounts
These commands change the system, so they run as root. The session below creates a user for a new colleague, adds her to a team group, and removes a former colleague (illustrative, Ubuntu 24.04):
$ sudo useradd -m -s /bin/bash -c "Carla Rossi" carla
$ sudo passwd carla
New password:
Retype new password:
passwd: password updated successfully
$ sudo usermod -aG developers carla
$ id carla
uid=1004(carla) gid=1004(carla) groups=1004(carla),1003(developers)
$ sudo userdel -r dave
| Command | What it does |
|---|---|
useradd -m -s /bin/bash -c "Name" user |
Create an account; -m creates the home
directory (copied from /etc/skel),
-s sets the shell, -c the comment
|
passwd user |
Set or change a password (passwd alone changes
your own); passwd -l locks,
-u unlocks. Until a password is set, a new
account is locked (! in
/etc/shadow)
|
usermod -aG group user |
Add the user to a supplementary group |
usermod -s /usr/sbin/nologin user |
Change the login shell (here: disable interactive logins) |
usermod -L user / -U |
Lock / unlock the password (SSH keys still work; also use
chage -E 0 or a nologin shell)
|
gpasswd -d user group |
Remove the user from one group |
userdel -r user |
Delete the account; -r also removes the home
directory and mail spool
|
groupadd group / groupdel group
|
Create / delete a group |
| Debian / Ubuntu | RHEL / Fedora | |
|---|---|---|
useradd without -m |
Creates no home directory; default shell
/bin/sh
|
Creates the home directory; default shell
/bin/bash
|
| Friendly wrapper |
adduser carla asks for name and password
interactively
|
adduser is the same program as
useradd
|
| Administrators' group | sudo |
wheel |
Changing ownership: chown and chgrp
chown user:group file changes owner and group in one
go; chown user file only the owner,
chown :group file or chgrp group file only
the group. Giving a file away requires root, otherwise users could
dodge disk quotas or plant files in someone else's name; the owner
may change the group to another group they belong to.
$ sudo chown carla:developers /srv/app/deploy.sh
$ sudo chgrp -R developers /srv/app
$ ls -l /srv/app/deploy.sh
-rwxr-x--- 1 carla developers 812 Oct 1 09:30 /srv/app/deploy.sh
Permission bits in depth
rwx. In
octal, each set is one digit (r = 4, w = 2, x = 1); an optional
leading digit holds the special bits.
What r, w and x mean on a directory
A directory is a list of names pointing to files. Its permission bits control that list, not the files themselves, and that explains every surprise:
-
rlets you read the list: see which names are in it. -
xlets you pass through: enter it withcdand reach the files inside by name. Withoutxyou cannot open a file inside, even one you own. -
wlets you change the list: create, rename and delete entries. It only works together withx. Deleting a file is a change to the directory, so it depends on the directory'sw, not on the file's permissions.
You can try each case on a directory of your own:
mkdir box
echo "hello" > box/file.txt
chmod u=r box
echo "--- r only: list, but not open"
ls box
cat box/file.txt
chmod u=x box
echo "--- x only: open by name, but not list"
ls box
cat box/file.txt
chmod u=rx box
echo "--- r and x, no w: cannot create or delete"
touch box/new.txt
rm box/file.txt
chmod u=rwx box
--- r only: list, but not open file.txt cat: box/file.txt: Permission denied --- x only: open by name, but not list ls: cannot open directory 'box': Permission denied hello --- r and x, no w: cannot create or delete touch: cannot touch 'box/new.txt': Permission denied rm: cannot remove 'box/file.txt': Permission denied
With only r, ls -l shows the names with
question marks in place of the details, because reading a file's
details also needs x. The x-only case is
useful: a web server can reach
/home/ana/public_html/index.html when each directory on
the way grants others x, without being able to list
anything.
chmod in practice
chmod accepts octal modes, which set all bits at once,
and symbolic changes, which adjust only the bits you name: who
(u user, g group, o others,
a all), an operator (+ add,
- remove, = set exactly) and the bits.
stat -c '%a %A %n' prints the mode in octal and
symbolic form next to the name:
touch deploy.sh notes.txt
chmod 644 deploy.sh; stat -c '%a %A %n' deploy.sh
chmod u+x deploy.sh; stat -c '%a %A %n' deploy.sh
chmod go-r deploy.sh; stat -c '%a %A %n' deploy.sh
chmod 750 deploy.sh; stat -c '%a %A %n' deploy.sh
chmod a=r,u+w notes.txt; stat -c '%a %A %n' notes.txt
644 -rw-r--r-- deploy.sh 744 -rwxr--r-- deploy.sh 700 -rwx------ deploy.sh 750 -rwxr-x--- deploy.sh 644 -rw-r--r-- notes.txt
Symbolic mode is safer when you only want to add or remove one
thing, because it leaves the other bits alone; octal is clearer when
you want to know exactly what the result is. For recursive changes
and the capital X trick, see the warning on
chmod and chown, and never solve an access problem with chmod 777.
Default permissions: umask
The umask (user file-creation mask) is
a per-process setting that lists the permission bits to
remove from every new file and directory. Programs create
files with mode 666 (rw-rw-rw-) and directories with
777, and the umask takes bits away: with 022, write permission is
removed for group and others, giving 644 files and 755 directories.
umask 022
touch report.txt; mkdir shared-docs
stat -c '%a %A %n' report.txt shared-docs
umask 027
touch payroll.txt; mkdir hr
stat -c '%a %A %n' payroll.txt hr
umask 077
touch id_backup; mkdir private
stat -c '%a %A %n' id_backup private
umask -S
644 -rw-r--r-- report.txt 755 drwxr-xr-x shared-docs 640 -rw-r----- payroll.txt 750 drwxr-x--- hr 600 -rw------- id_backup 700 drwx------ private u=rwx,g=,o=
Two details. The umask only ever removes bits, so it never makes a
new file executable; that is why scripts need an explicit
chmod +x. And it is not simple subtraction: 666 with
umask 027 is 640, not 639 — each bit in the mask is switched off.
umask without arguments prints the current mask,
umask -S the permissions that remain. It is set per
shell, so a change lasts until you close the terminal; the
system-wide default comes from /etc/login.defs or the
shell startup files, and services can have their own (UMask=
in a systemd unit). Debian and root typically use 022; Ubuntu,
Fedora and RHEL typically give ordinary users 002 (each user has a
private group, so group-write gives nobody else access).
Special permissions: setuid, setgid and sticky
Three extra bits sit in front of the normal nine. In octal they form
a fourth, leading digit; in ls -l they replace the
x of the user, group or others position.
| Bit | Octal | Shown as | On a program | On a directory |
|---|---|---|---|---|
| setuid | 4000 | rws------ |
Runs with the file owner's rights | (ignored on Linux) |
| setgid | 2000 | ---rws--- |
Runs with the file's group rights | New files inherit the directory's group |
| sticky | 1000 | --------t |
(no effect on Linux) | Only a file's owner (or the directory's owner, or root) may delete or rename it |
A capital S or T means the special bit is
set but the underlying x is not, which is almost always
a mistake on a program.
setuid
Setuid (set user ID) is a permission
bit on an executable file that makes it run with the privileges of
the file's owner instead of the user who started it. The classic
example is passwd: an ordinary user must be able to
change their own password, which means writing
/etc/shadow, which only root may do.
/usr/bin/passwd is owned by root and setuid, so it runs
as root — and is written carefully to change only the caller's own
entry (illustrative):
$ ls -l /usr/bin/passwd
$ ls -ld /tmp
-rwsr-xr-x 1 root root 64152 May 30 2024 /usr/bin/passwd drwxrwxrwt 18 root root 4096 Oct 1 09:41 /tmp
Every setuid-root program is a potential way to become root if it
has a bug, so the list should be short and known.
sudo find / -xdev -perm -4000 -type f lists them; a
setuid file in a home directory or under /tmp is a red
flag. Linux ignores setuid on scripts (only compiled programs honor
it), and a file system mounted with nosuid ignores it
entirely.
setgid on shared directories
Setgid on a directory is the everyday use. Normally a new file gets the creator's primary group, so in a team folder every file ends up in a different personal group and colleagues cannot edit each other's work. With setgid set on the folder, new files and subdirectories inherit the folder's group instead (and new subdirectories inherit the setgid bit too):
$ sudo mkdir /srv/projects
$ sudo chgrp developers /srv/projects
$ sudo chmod 2770 /srv/projects # rwxrws---: group members only, setgid
$ touch /srv/projects/plan.md
$ ls -l /srv/projects
-rw-rw-r-- 1 ana developers 0 Oct 1 10:02 plan.md
The file belongs to group developers, not to
ana. Combine it with a umask of 002 for team members so
the group also gets write permission on new files.
The sticky bit
The sticky bit is a permission bit
on a directory that allows only a file's owner, the directory's
owner or root to delete or rename files in it, even when others have
write permission on the directory. That is what makes a
world-writable directory such as /tmp (mode 1777) safe
to share: everyone may create files there, nobody may remove someone
else's.
All three special bits can be set, in octal or symbolically
(u+s, g+s, +t), on files and
directories you own. Setting setuid on an empty file of your own is
harmless and shows what the bit looks like:
mkdir team-folder dropbox
touch program
chmod 2770 team-folder
chmod 1777 dropbox
chmod 4755 program
stat -c '%a %A %n' team-folder dropbox program
chmod u-s program
echo "--- setuid removed, then added back symbolically:"
stat -c '%a %A %n' program
chmod u+s program
stat -c '%a %A %n' program
2770 drwxrws--- team-folder 1777 drwxrwxrwt dropbox 4755 -rwsr-xr-x program --- setuid removed, then added back symbolically: 755 -rwxr-xr-x program 4755 -rwsr-xr-x program
sudo and the sudoers file
sudo runs one command as root (or as another user)
after checking a rule, asks for your own password, and logs
the command; its basics are on
root and sudo. The rules live in the
sudoers policy: the file
/etc/sudoers plus drop-in files in
/etc/sudoers.d/, which define who may run which
commands as which user on which hosts. Each rule reads who
where=(as whom) what:
# Members of the admin group: anything, as anyone (Debian/Ubuntu) %sudo ALL=(ALL:ALL) ALL # The same on RHEL/Fedora %wheel ALL=(ALL) ALL # Ben may only restart the web server, as root ben ALL=(root) /usr/bin/systemctl restart nginx
A % marks a group. The narrow rule for Ben is least
privilege in practice: he can do the one task his job needs, and
nothing else. Being in the sudo or
wheel group means full root access, so membership
should be as rare as the root password.
Always edit these files with visudo. It opens a
temporary copy, checks the syntax when you save and refuses to
install a broken file; a syntax error saved directly into
/etc/sudoers can stop sudo from working at
all, and on a server without a root password that locks every
administrator out. If visudo reports an error it asks
What now?: press e to fix it, or x to
discard your changes. Never press Q, which saves the
broken file anyway. Put your own rules in a drop-in file, which
survives package upgrades:
$ sudo visudo -f /etc/sudoers.d/webteam # edit a drop-in file
$ sudo visudo -c # check all sudoers files
$ sudo -l -U ben # what may ben run?
/etc/sudoers: parsed OK
/etc/sudoers.d/README: parsed OK
/etc/sudoers.d/webteam: parsed OK
Matching Defaults entries for ben on web01:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty
User ben may run the following commands on web01:
(root) /usr/bin/systemctl restart nginx
An administrator checks another user with
sudo -l -U ben; Ben himself would type
sudo -l and see the same list. Files in
/etc/sudoers.d/ whose names contain a dot or end in
~ are skipped, so name them webteam, not
webteam.conf.