User management is basic Linux administration in the same way plumbing is basic house maintenance: you can ignore it right up until the day it leaks into everything else. Bad user and group hygiene creates permission problems, weak sudo rules, confusing service ownership, and messy offboarding. Good hygiene is not glamorous, but it prevents a lot of avoidable incidents.
This is the workflow I use for day-to-day account administration on Linux servers: creating users, understanding where account data lives, managing groups, granting sudo safely, locking accounts, and building service identities that do not accidentally become shell users. The commands are simple. The operational judgment around them matters more.
If you are hardening access paths overall, this pairs naturally with SSH key authentication on Linux, fail2ban for protecting SSH and web services, and my Linux server hardening checklist.
useradd vs adduser: know the difference
On many distributions, useradd is the low-level tool and adduser is the friendlier wrapper.
useradd
This is the direct system tool. It is script-friendly and consistent across many environments.
Example:
sudo useradd -m -s /bin/bash alice
sudo passwd alice
adduser
On Debian and Ubuntu, adduser is often more interactive and user-friendly.
sudo adduser alice
It prompts for password and account details and usually handles sane defaults for home directory creation.
Which I use
- for automation and scripts:
useradd - for quick manual admin on Debian-like systems:
adduseris fine
The important point is not to assume they are identical. adduser is often a higher-level helper, not a portable standard.
The useradd flags I actually care about
These are the options I use regularly.
-m create the home directory
sudo useradd -m bob
Without -m, the account may be created without a home directory depending on distro defaults. I do not like ambiguity there.
-s set the login shell
sudo useradd -m -s /bin/bash bob
For service accounts, I often use a non-login shell instead:
sudo useradd -r -s /usr/sbin/nologin backupsvc
-G add supplementary groups
sudo useradd -m -s /bin/bash -G sudo,adm bob
That adds bob to extra groups at creation time.
-u set a specific UID
sudo useradd -m -u 1050 appuser
I use this when UID consistency matters across systems, NFS mounts, or container host mappings.
-d set a custom home directory
sudo useradd -m -d /srv/appuser appuser
Useful when service data or restricted user homes need to live outside /home.
/etc/passwd: what it really contains
People new to Linux often think /etc/passwd holds the password hash. On modern systems it does not.
Example line:
alice:x:1001:1001:Alice Example:/home/alice:/bin/bash
Fields, left to right:
- username
- password placeholder (
xmeans the real hash is in/etc/shadow) - UID
- primary GID
- GECOS/comment field
- home directory
- login shell
A quick view:
getent passwd alice
I use getent rather than reading /etc/passwd directly when LDAP or other NSS sources may be involved.
/etc/shadow: sensitive by design
Password hashes and account aging live in /etc/shadow.
View one entry as root:
sudo getent shadow alice
This file is restricted for good reason. If an attacker gets the hash set, offline cracking becomes possible.
Passwords should be hashed, never stored in plaintext
That sounds obvious, but the practical lesson is broader: do not write temporary passwords into shell history, ticket systems, or internal chat if you can avoid it. Use passwd, expiring passwords, or SSH keys instead.
/etc/group and group format
Groups are simple once you read the format.
Example:
sudo:x:27:alice,bob
Fields:
- group name
- password placeholder
- GID
- comma-separated member list
To inspect one group cleanly:
getent group sudo
Or all groups for a user:
id alice
groups alice
Primary vs supplementary groups
Every user has one primary group and can belong to additional supplementary groups.
Check them:
id alice
Example output might show:
uid=1001(alice) gid=1001(alice) groups=1001(alice),27(sudo),4(adm)
Why this matters operationally
Primary group ownership affects new file creation defaults. Supplementary groups determine broader access. When an application mysteriously cannot read shared files, it is often because someone added the user to the wrong group or forgot to start a new session after the group change.
Setting and changing passwords with passwd
To set or change a user password:
sudo passwd alice
To force a password change at next login:
sudo passwd -e alice
To lock the password interactively without deleting the account:
sudo passwd -l alice
I use passwd for the actual credential management path because it updates account data safely and predictably.
usermod: the command I use for ongoing account changes
Change the login shell
sudo usermod -s /bin/bash alice
Add a user to supplementary groups
sudo usermod -aG docker,adm alice
The -a matters. Without it, -G replaces the user’s supplementary groups instead of appending.
That is a classic footgun.
Lock an account
sudo usermod -L alice
Unlock an account
sudo usermod -U alice
Change the home directory
sudo usermod -d /srv/alice -m alice
-m moves the content. Without it, you change the path in account metadata but leave the files behind.
Removing users: do not forget the home directory decision
Delete an account only:
sudo userdel alice
Delete the account and its home directory:
sudo userdel -r alice
I pause before using -r. It is correct for temporary or decommissioned accounts when I am certain there is nothing to retain. It is wrong when the home directory contains data still needed for audit or recovery.
A safer pattern during offboarding is often:
- lock the account
- archive or transfer data
- remove the account later
Creating and managing groups
Create a group:
sudo groupadd developers
Add a user to the group with gpasswd:
sudo gpasswd -a alice developers
Remove a user from the group:
sudo gpasswd -d alice developers
I use gpasswd sometimes because it makes group membership changes very explicit.
To change a user’s primary group:
sudo usermod -g developers alice
Be careful with primary group changes on systems where file ownership conventions matter.
id and groups: the fastest permission sanity checks
When a user says “I should have access,” I run these first:
id alice
groups alice
If the group is not there, no amount of app-side troubleshooting will fix the issue.
If the group was just added and the user is still in an old session, I remind them to log out and back in. Group memberships are not always visible to already-running sessions.
Checking effective access on real files
Group membership alone is not the whole permission story. I also verify the target path:
namei -l /srv/shared/reports
getfacl /srv/shared/reports
namei -l is especially helpful because it shows permissions on every path component. I have seen users added to the correct group but blocked by an earlier directory in the path tree that lacked execute permission.
su vs sudo: they are not interchangeable
su
su switches user identity, usually to another shell.
su - alice
With -, you get a login shell environment. Without it, the environment is less clean.
sudo
sudo runs a command with elevated privileges according to policy.
sudo systemctl restart nginx
Or as another user:
sudo -u postgres psql
My practical rule
- use
sudofor specific privileged actions - use
su -when I intentionally need a full shell as another user for troubleshooting
I do not encourage habitual root shells when targeted sudo commands are enough.
sudo -i and sudo -s
These show up often in admin habits too:
sudo -i
sudo -s
sudo -i gives something closer to a root login shell, while sudo -s starts a root shell with more of the current environment preserved. I use both sparingly. For auditability and safety, specific sudo command invocations are usually better.
Sudoers: edit with visudo, not with blind text editing
Always use:
sudo visudo
It checks syntax before saving. That matters. A broken sudoers file can lock you out of privilege escalation.
Simple sudo rule
alice ALL=(ALL:ALL) ALL
Passwordless rule
alice ALL=(ALL:ALL) NOPASSWD: ALL
I use NOPASSWD sparingly. It is convenient for automation accounts or tightly controlled admin contexts, but it expands the blast radius of a compromised session.
Restricting to specific commands
backupop ALL=(root) NOPASSWD: /usr/bin/rsync, /usr/bin/systemctl restart backup.service
I like command restriction when delegating operational tasks. It is cleaner than full sudo for narrow workflows.
wheel and sudo groups
Different distros use different admin groups.
- Debian/Ubuntu commonly use
sudo - RHEL-family commonly use
wheel
Examples:
sudo usermod -aG sudo alice
sudo usermod -aG wheel alice
Check your distro policy before assuming one exists.
Service accounts: keep them non-interactive by default
For daemons, I usually create dedicated system users with no shell and often no real home login purpose.
sudo useradd -r -s /usr/sbin/nologin -d /var/lib/myservice myservice
Flags here matter:
-rsystem account-s /usr/sbin/nologindisable interactive shell use-dset a service data directory if needed
I do not create full login users for background services unless the software genuinely requires it.
This also ties directly into writing secure systemd unit files, where User= and Group= should point to an account with exactly the access the service needs and no more.
Locking, expiring, and aging accounts
Lock an account
sudo usermod -L alice
Set an account expiration date
sudo chage -E 2026-12-31 contractor1
View aging information
sudo chage -l alice
I use expiration dates for temporary contractors and time-bounded access. It is much better than relying on memory.
Difference between locked and expired
- locked usually means password authentication is disabled
- expired means the account itself has passed a validity date or credential validity window
SSH key-based access and PAM policies can complicate this, so I verify actual login behavior after changes.
One subtle lesson here: locking a password with usermod -L or passwd -l does not automatically disable every form of access. If authorized SSH keys still exist and PAM policy allows the session, the account may remain usable. During offboarding, I check ~/.ssh/authorized_keys, sudo access, API tokens, and group memberships instead of assuming one lock command solved everything.
Login history: last and lastlog
Check recent login history:
last alice
Check the last login of all users:
lastlog
These are useful during audits and offboarding. I use them to answer questions like:
- has this account been used recently?
- is this service account unexpectedly interactive?
- did the user ever log in after we thought access was removed?
/etc/skel controls default home directory contents
When a new home directory is created, files are usually copied from /etc/skel.
Inspect it:
ls -la /etc/skel
This is where default .bashrc, .profile, or standard helper files come from.
I keep /etc/skel minimal. Putting too much logic there creates noise for every new user and can hide outdated shell customizations for years.
It is also a good place to standardize a few safe defaults. I sometimes add a basic shell profile, a comment pointing to internal documentation, or a skeleton .ssh/ directory with correct permissions but no keys. What I do not add is environment-specific clutter that will age badly.
nsswitch.conf: local files are not always the whole story
On enterprise systems, users may come from LDAP, Active Directory integration, or another NSS source.
Inspect lookup order:
grep '^passwd\|^group\|^shadow' /etc/nsswitch.conf
Example:
passwd: files systemd sss
group: files systemd sss
shadow: files sss
This matters because a missing entry in /etc/passwd does not always mean the user does not exist. getent is better than cat for that reason.
I am not turning this into a full LDAP or PAM guide, but the overview matters: account identity on Linux can be local, centralized, or mixed, and your troubleshooting approach should reflect that.
Home directory ownership and default umask checks
When a new account behaves strangely, I inspect the home directory immediately:
ls -ld /home/alice
sudo -u alice umask
Wrong ownership on the home directory breaks shell startup, SSH key loading, and file creation in subtle ways. I have seen accounts created by automation with the right UID but the wrong owner on the home path, which is irritating to diagnose if you forget to check the basics.
For SSH-specific issues, the permissions on .ssh and authorized_keys are often the whole problem. That is one reason I keep the SSH key troubleshooting guide handy even when the user-management side looked correct.
Common mistakes I see repeatedly
Forgetting -a with usermod -G
That wipes supplementary groups instead of adding one.
Creating service accounts with interactive shells
It expands the attack surface for no benefit.
Giving full NOPASSWD: ALL when a command-restricted rule would do
Convenience now, regret later.
Deleting accounts before preserving their data
Offboarding should be staged, not impulsive.
Editing /etc/sudoers directly without visudo
One typo can turn into a nasty recovery exercise.
My practical account management checklist
When I create or modify access, I usually verify with this sequence:
getent passwd alice
id alice
sudo chage -l alice
sudo -l -U alice
lastlog | grep '^alice\b'
For a service account:
getent passwd myservice
id myservice
sudo -u myservice test -r /etc/myservice/config.yml && echo ok
That catches most mistakes before the user or service does.
Conclusion
Linux user and group management is straightforward on paper and surprisingly consequential in practice. The commands are simple: useradd, usermod, groupadd, passwd, userdel. The hard part is using them with enough discipline to preserve security, predictable permissions, and clean operational boundaries.
My defaults are conservative for a reason: create real users with explicit homes and shells, create service accounts with nologin, use groups rather than ad hoc permission hacks, grant sudo as narrowly as practical, and lock or expire accounts before deleting them. Those habits scale better than improvised fixes, and they make future troubleshooting much less painful.