Tutorial

Linux User and Group Management: The Complete Guide for Server Admins

Back to Blog
A modern alternative to cPanel, Plesk and CyberPanel — isolated, secure, AI-assisted.
Start free

Why User Management Is the Foundation of Linux Security

Every file on your server has an owner. Every process runs as a user. Every web application executes under an account with specific privileges. Linux multi-user architecture is not just a design choice. It is the primary mechanism that prevents a compromised PHP script from deleting your entire system, or a rogue developer from reading another client database.

Yet user management is where most server admins cut corners. Root SSH enabled everywhere. Developers sharing a single account. Service daemons running as root because it is easier. These shortcuts compound over time until one incident exposes exactly how much risk was quietly accumulating.

This guide covers everything: user and group creation, modification, deletion, file ownership, sudo configuration, ACLs, and the security practices that separate well-run servers from ones waiting for a breach.


1. Understanding Linux Users

UID Ranges: Who Is Who

Every user on a Linux system has a numeric User ID (UID). The kernel works with UIDs. The name-to-UID mapping happens in /etc/passwd.

  • UID 0 — root. The superuser. No restrictions apply.
  • UID 1-999 — System users. Created by packages for services (nginx, mysql, www-data, postgres). They typically have no login shell and no home directory in /home/.
  • UID 1000+ — Regular users. Real humans, developers, or isolated application accounts.

The Four User Files

/etc/passwd — The user database. World-readable. Seven colon-separated fields:

username:x:UID:GID:comment:home_dir:shell

# Example:
deployer:x:1001:1001:Deploy User:/home/deployer:/bin/bash
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
  • username — Login name
  • x — Password placeholder (actual hash is in /etc/shadow)
  • UID — Numeric user ID
  • GID — Primary group ID
  • comment — Full name or description (GECOS field)
  • home_dir — Home directory path
  • shell — Login shell (/bin/bash, /bin/sh, or /usr/sbin/nologin to block login)

/etc/shadow — Password hashes. Root-readable only. Nine fields:

username:HASH:last_change:min_age:max_age:warn_days:inactive:expire:reserved
  • hash — The actual password hash (algorithm prefix: $6$=SHA-512, $y$=yescrypt)
  • last_change — Days since epoch when password was last changed
  • min_age — Minimum days before password can be changed again
  • max_age — Maximum days before password must be changed (99999 = never)
  • warn_days — Days before expiry to warn user
  • inactive — Days after expiry before account is locked
  • expire — Absolute expiry date (days since epoch)

/etc/group — Group database:

groupname:x:GID:member1,member2,member3

/etc/gshadow — Group passwords and administrators. Rarely used in practice.

Useful User Inspection Commands

# Show current user UID, GID, and all groups
id

# Show another user info
id username

# Who you are right now
whoami

# Who is currently logged in
who

# Who is logged in and what they are doing
w

# Login history (last 20)
last -20

# Last login for all users
lastlog

2. Creating Users

useradd: The Low-Level Tool

useradd is available on all Linux distributions and gives you full control. Unlike adduser, it does not create a home directory by default unless you ask it to.

# Minimal: no home dir, no shell, useful for service accounts
useradd username

# Full user with home directory and bash shell
useradd -m -s /bin/bash username

# Full user, added to sudo group, with comment
useradd -m -s /bin/bash -G sudo -c "John Smith" john

All useradd flags:

FlagDescriptionExample
-mCreate home directoryuseradd -m alice
-s SHELLSet login shell-s /bin/bash
-G groupsSupplementary groups (comma-separated)-G sudo,docker
-u UIDSet specific UID-u 1500
-g GIDSet primary group by GID or name-g www-data
-d DIRSet home directory path-d /var/app/alice
-e DATEAccount expiry date (YYYY-MM-DD)-e 2026-12-31
-c COMMENTFull name / description-c "Web Deploy Bot"
-rCreate system user (UID under 1000, no home)useradd -r myservice
-MDo NOT create home directoryuseradd -M -r daemon

adduser: The Interactive Helper (Debian/Ubuntu)

adduser is a higher-level Perl wrapper that creates a home directory, sets up skeleton files from /etc/skel/, prompts for a password, and asks for full name and other details. It is friendlier for interactive use.

adduser alice
# Prompts: password, full name, room, phone, etc.

useradd vs adduser

useraddadduser
TypeBinary (all distros)Script (Debian/Ubuntu)
Home directoryOnly with -mAlways created
PasswordNot set (use passwd after)Prompts during creation
ScriptingBetter (non-interactive)Harder (interactive)
Skeleton copyYes (with -m)Yes

Setting and Managing Passwords

# Set password interactively
passwd username

# Set password non-interactively (scripts)
echo "username:newpassword" | chpasswd

# Check password aging info
chage -l username

# Force password change on next login
chage -d 0 username

# Set maximum password age to 90 days
chage -M 90 username

# Set warning 14 days before expiry
chage -W 14 username

# Set account expiry
chage -E 2026-12-31 username

3. Modifying Users

After creation, usermod handles all user modifications.

# Add user to supplementary group (ALWAYS use -a to append!)
usermod -aG docker alice
usermod -aG sudo,www-data alice

# Change login shell
usermod -s /bin/zsh alice

# Disable shell (user cannot log in interactively)
usermod -s /usr/sbin/nologin alice

# Lock account (prepends ! to password hash)
usermod -L alice

# Unlock account
usermod -U alice

# Move home directory to new location
usermod -d /new/home/alice -m alice

# Rename user (does NOT rename home directory automatically)
usermod -l newname oldname

# Set account expiry date
usermod -e 2026-06-30 alice

# Change primary group
usermod -g newgroup alice

# Change UID
usermod -u 1500 alice
Critical Warning: -G without -a destroys group memberships
usermod -G docker alice removes alice from ALL other groups and adds only docker.
usermod -aG docker alice appends docker to alice existing groups.
Always use -aG when adding to groups, never -G alone.

4. Deleting Users

Before You Delete

Deleting a user is irreversible. Before running userdel, check for:

# Find all files owned by this user
find / -user alice 2>/dev/null

# Check for running processes
ps aux | grep alice

# Check for cron jobs
crontab -u alice -l

# Check mail spool
ls -la /var/mail/alice

The Commands

# Remove user account only (home directory and files remain)
userdel alice

# Remove user + home directory + mail spool
userdel -r alice

# Force deletion even if user is logged in (dangerous)
userdel -f alice

Orphaned Files

After deleting a user, files they owned become orphaned and show a numeric UID instead of a name in ls -la. Find and reassign or delete them:

# Find all files with no valid owner
find / -nouser 2>/dev/null

# Find all files with no valid group
find / -nogroup 2>/dev/null

# Change orphaned files to a new owner
find / -nouser 2>/dev/null -exec chown newowner {} \;

5. Groups

Primary vs Supplementary Groups

Every user has one primary group (set in /etc/passwd) and can belong to many supplementary groups. New files get the primary group as their group owner. Supplementary groups grant access to resources owned by those groups.

# Create a group
groupadd developers

# Create group with specific GID
groupadd -g 2000 developers

# Rename a group
groupmod -n newname oldname

# Delete a group (fails if it is a primary group for any user)
groupdel developers

# Add user to group
gpasswd -a alice developers

# Remove user from group
gpasswd -d alice developers

# Show user groups
groups alice

# List all members of a group
getent group developers

Common Groups and Their Purpose

GroupPurpose
sudoFull sudo access (Ubuntu/Debian)
wheelFull sudo access (RHEL/CentOS/Fedora)
www-dataWeb server processes (nginx, apache)
dockerDocker socket access (no sudo needed)
admRead system logs in /var/log/
systemd-journalRead journald logs
sshSSH login (if AllowGroups configured)
shadowRead /etc/shadow
diskRaw disk access (use carefully)

6. sudo: Controlled Root Access

The Golden Rule: Never Edit /etc/sudoers Directly

A syntax error in /etc/sudoers locks everyone out of sudo. Always use visudo, which validates syntax before saving.

# Open sudoers safely
visudo

# Edit with a specific editor
EDITOR=nano visudo

Sudoers Syntax

# Format: WHO WHERE=(AS_WHO) COMMANDS
# WHO: username or %groupname
# WHERE: hostname or ALL

# Full sudo for a user
alice ALL=(ALL:ALL) ALL

# Full sudo without password (only for automation accounts)
deployer ALL=(ALL) NOPASSWD: ALL

# Group-based sudo
%developers ALL=(ALL:ALL) ALL

# Specific commands only: best practice for service accounts
capistrano ALL=(ALL) /usr/bin/nginx -s reload

Drop-in Files (Preferred Approach)

Instead of editing the main sudoers file, create files in /etc/sudoers.d/. Cleaner, easier to audit, and mistakes only affect that one file:

# Create a drop-in file safely
visudo -f /etc/sudoers.d/alice

# Permissions must be 440 (world-unreadable)
chmod 440 /etc/sudoers.d/alice

Useful sudo Commands

# What can I do with sudo?
sudo -l

# Run as another user
sudo -u www-data php artisan migrate

# Get a root shell
sudo -i

# Repeat last command with sudo
sudo !!

# Run with preserved environment
sudo -E command

# Invalidate cached sudo credentials
sudo -k

7. File Ownership and Permissions

For a deeper look at exactly why 755 and 644 are the defaults you see everywhere, see Linux File Permissions Explained.

Reading ls -la Output

drwxr-xr-x  2  alice  developers  4096  Mar 15 10:23  project/
-rw-r--r--  1  alice  developers  1234  Mar 15 10:20  config.yml
-rwxr-x---  1  root   www-data    8192  Mar 10 09:15  deploy.sh

The permission string drwxr-xr-x breaks down as:

  • d — Type: d=directory, -=file, l=symlink, b=block device
  • rwx — Owner permissions (read, write, execute)
  • r-x — Group permissions
  • r-x — Others permissions

Changing Ownership

# Change owner
chown alice file.txt

# Change owner and group
chown alice:developers file.txt

# Change group only
chgrp developers file.txt

# Recursive change
chown -R alice:developers /var/www/mysite/

chmod: Numeric and Symbolic

# Numeric (octal)
chmod 755 script.sh      # rwxr-xr-x
chmod 644 config.yml     # rw-r--r--
chmod 600 .ssh/id_rsa    # rw-------
chmod 700 /home/alice/   # rwx------

# Symbolic
chmod u+x script.sh      # Add execute for owner
chmod g-w file.txt       # Remove write from group
chmod o-r private.txt    # Remove read from others
chmod a+r public.txt     # Add read for all

# Recursive
chmod -R 755 /var/www/

Numeric Permissions Reference

NumberBinaryPermissionsMeaning
7111rwxRead + Write + Execute
6110rw-Read + Write
5101r-xRead + Execute
4100r--Read only
3011-wxWrite + Execute
2010-w-Write only
1001--xExecute only
0000---No access

Common Permission Patterns

PatternUse CaseWhy
755Directories, scripts, web rootsOwner full access, others can read and traverse
644Config files, HTML, PHP filesOwner can edit, others read-only
600Private keys, .env files, secretsOwner only, nothing leaks to others
700Home directoriesComplete privacy from other users
640Config with group accessOwner edits, group reads, others nothing
775Shared team directoriesOwner and group full, others read only
777Never use thisGives everyone full access, a security hole

Special Permission Bits

SUID (4000) — When set on an executable, it runs as the file owner (not the calling user). Used by /usr/bin/passwd to write /etc/shadow as root.

# Set SUID
chmod u+s /usr/bin/myprogram
chmod 4755 /usr/bin/myprogram

# Security audit: find all SUID files on the system
find / -perm -4000 2>/dev/null

SGID (2000) — On a directory, new files inherit the directory group instead of the creator primary group. Essential for team-shared directories.

# Set SGID on shared directory
chmod g+s /var/www/shared/
chmod 2775 /var/www/shared/

Sticky Bit (1000) — On a directory, only the file owner (or root) can delete or rename files, even if others have write permission. This is how /tmp works.

# Set sticky bit
chmod +t /tmp/shared/
chmod 1777 /tmp/

umask: Default Permission Mask

# Check current umask
umask
# 0022 = new files get 644, new directories get 755

# Change umask for current session
umask 0027
# 0027 = new files get 640, new directories get 750

# Set persistently for all users
echo "umask 0027" >> /etc/profile

8. ACLs: Access Control Lists

Standard Unix permissions allow exactly one owner and one group per file. ACLs extend this to grant specific access to additional users and groups without changing ownership.

# Install ACL tools if needed
apt install acl

# View ACLs on a file or directory
getfacl /var/www/project/

# Grant specific user read+write access
setfacl -m u:bob:rw /var/www/project/config.yml

# Grant group execute access
setfacl -m g:developers:rx /var/scripts/

# Set default ACL on directory (inherited by new files)
setfacl -d -m u:deployer:rwx /var/www/project/

# Remove ACL for a user
setfacl -x u:bob /var/www/project/config.yml

# Remove ALL ACLs from file
setfacl -b /var/www/project/config.yml

# Copy ACL from one file to another
getfacl source_file | setfacl --set-file=- target_file

When ACLs are set, ls -la shows a + at the end of the permission string, indicating extended ACLs are present on that file or directory.


9. Security Best Practices

Disable Root SSH Login

This pairs directly with proper SSH key authentication — user accounts and SSH access control are two sides of the same problem.

# Edit /etc/ssh/sshd_config and set:
# PermitRootLogin no
# PasswordAuthentication no
# AllowUsers alice bob deployer
# AllowGroups sshusers

# Then reload the SSH daemon configuration

Lock Unused Accounts

# Disable shell for service accounts
usermod -s /usr/sbin/nologin www-data
usermod -s /usr/sbin/nologin mysql
usermod -s /usr/sbin/nologin nginx

# Immediately expire old contractor account
usermod -e 1 legacy_contractor

# Check which users can actually log in
awk -F: '$7 != "/usr/sbin/nologin" && $7 != "/bin/false" {print $1, $7}' /etc/passwd

Password Complexity with libpam-pwquality

# Install
apt install libpam-pwquality

# In /etc/security/pwquality.conf:
# minlen = 12
# dcredit = -1   (at least 1 digit)
# ucredit = -1   (at least 1 uppercase)
# lcredit = -1   (at least 1 lowercase)
# ocredit = -1   (at least 1 special character)
# maxrepeat = 3  (no more than 3 consecutive same chars)

Account Lockout After Failed Attempts

# Ubuntu 22.04+ uses pam_faillock
# In /etc/security/faillock.conf:
# deny = 5          (lock after 5 failures)
# unlock_time = 900 (unlock after 15 minutes)
# fail_interval = 900

# Check locked status
faillock --user alice

# Unlock manually
faillock --user alice --reset

Security Audit Commands

# Find all UID 0 accounts (should only be root)
awk -F: '$3 == 0 {print $1}' /etc/passwd

# Find accounts with empty passwords (critical vulnerability)
awk -F: '$2 == "" {print $1}' /etc/shadow

# List all sudoers entries
grep -E "^[^#]" /etc/sudoers

# Login history
last -20

# Failed login attempts
lastb -20

# Last login for all users (spot dormant accounts)
lastlog | grep -v "Never"

10. Practical Scenarios

Scenario 1: Set Up a Web Developer

# Create user with SSH access and web group membership
useradd -m -s /bin/bash -G www-data -c "Jane Developer" jane
passwd jane

# Set up SSH key authentication
mkdir -p /home/jane/.ssh
chmod 700 /home/jane/.ssh
# Paste the developer public key:
# echo "ssh-rsa AAAA..." >> /home/jane/.ssh/authorized_keys
chmod 600 /home/jane/.ssh/authorized_keys
chown -R jane:jane /home/jane/.ssh

# Give jane ownership of her site directory
chown -R jane:www-data /var/www/jane-site/
chmod -R 755 /var/www/jane-site/
chmod g+s /var/www/jane-site/  # New files inherit www-data group

Scenario 2: Create a Service Account for a Daemon

# System user: no home dir, no shell, low UID
useradd -r -s /usr/sbin/nologin -c "My App Service" myapp

# Create required directories
mkdir -p /var/lib/myapp /var/log/myapp /var/run/myapp
chown myapp:myapp /var/lib/myapp /var/log/myapp /var/run/myapp
chmod 750 /var/lib/myapp /var/log/myapp

# In the systemd unit file, set:
# User=myapp
# Group=myapp

Scenario 3: Shared Directory for a Team

# Create a group for the team
groupadd webteam
usermod -aG webteam alice
usermod -aG webteam bob
usermod -aG webteam charlie

# Create the shared directory
mkdir -p /var/shared/project
chown root:webteam /var/shared/project

# SGID: new files inherit webteam group
# Sticky: only owner can delete their own files
chmod 2775 /var/shared/project
chmod +t /var/shared/project

Scenario 4: Restrict User to SFTP Only (No Shell)

# Create user with no login shell
useradd -m -s /usr/sbin/nologin ftpuser
passwd ftpuser

# In /etc/ssh/sshd_config, add:
# Match User ftpuser
#     ForceCommand internal-sftp
#     ChrootDirectory /var/www/ftpuser
#     PasswordAuthentication yes
#     AllowAgentForwarding no
#     AllowTcpForwarding no

# Chroot directory must be owned by root (SSH requirement)
mkdir -p /var/www/ftpuser
chown root:root /var/www/ftpuser
chmod 755 /var/www/ftpuser

# Create a writable subdirectory for the user
mkdir -p /var/www/ftpuser/uploads
chown ftpuser:ftpuser /var/www/ftpuser/uploads

Scenario 5: Audit Who Logged In Last Month

# Last 30 days login history
last -s -30days

# Count logins per user
last -s -30days | awk '{print $1}' | sort | uniq -c | sort -rn

# Failed attempts last 30 days
lastb -s -30days 2>/dev/null | awk '{print $1}' | sort | uniq -c | sort -rn

# All logins from a specific IP
last | grep "203.0.113.42"

Quick Reference Cheat Sheet

User Commands

CommandPurpose
useradd -m -s /bin/bash usernameCreate user with home and bash
passwd usernameSet or change password
usermod -aG group usernameAdd user to group (safe append)
usermod -s /usr/sbin/nologin usernameDisable login shell
usermod -L usernameLock account
usermod -U usernameUnlock account
userdel -r usernameDelete user and home directory
chage -l usernameShow password expiry info
chage -M 90 usernameSet 90-day password expiry
id usernameShow UID, GID, and groups
last -20Last 20 logins

Group Commands

CommandPurpose
groupadd groupnameCreate group
groupmod -n newname oldnameRename group
groupdel groupnameDelete group
gpasswd -a user groupAdd user to group
gpasswd -d user groupRemove user from group
groups usernameList user groups
getent group groupnameList group members

Permission Commands

CommandPurpose
chmod 755 fileSet permissions (numeric)
chmod u+x fileAdd execute for owner
chmod -R 644 dir/Recursive permissions
chown user:group fileChange owner and group
chown -R user:group dir/Recursive ownership change
chmod g+s dir/SGID: inherit group on new files
chmod +t dir/Sticky: only owner can delete
getfacl fileView ACL entries
setfacl -m u:user:rwx fileSet ACL for specific user
umaskView default permission mask
find / -nouserFind orphaned files
find / -perm -4000Find SUID files (security audit)

Frequently Asked Questions

What is the difference between useradd and adduser?

useradd is a low-level binary available on every Linux distribution and does not create a home directory unless you pass -m. adduser is a friendlier Perl wrapper (Debian/Ubuntu) that always creates a home directory, copies skeleton files, and prompts interactively for a password and details.

Why should I always use usermod -aG instead of usermod -G?

usermod -G replaces all of a user's supplementary group memberships with only the groups listed. Forgetting one existing group silently removes their access to it. usermod -aG appends the new group without touching existing memberships, which is what almost everyone actually wants.

What permission should I use for a private SSH key or .env file?

600 (rw-------). Only the owner can read or write the file, and no other user or group has any access at all. SSH will actually refuse to use a private key if its permissions are more permissive than this.

Do I need ACLs if I already use standard Unix permissions?

Only when a file needs different access for more than one user or group beyond the standard owner/group/other model. For example, granting one specific contractor read access to a directory without changing its group ownership. Most servers never need ACLs; they matter most on shared team directories with mixed access requirements.


User Management Is Your First Line of Defense

Most breaches do not start with exotic zero-days. They start with a compromised account that had too much access, an orphaned service running as root, or a developer who was never removed after they left. User management is operational security. It is the difference between a contained incident and a full system compromise.

The principles are straightforward (and pair well with our broader 30-step server hardening checklist):

  • Every user and service gets exactly the access it needs, nothing more
  • Root access is earned through sudo, not handed out wholesale
  • Unused accounts are locked or deleted
  • File permissions enforce isolation between users and services
  • Regular audits catch what creeps in over time

If you manage a multi-tenant server, hosting sites for multiple clients or teams, these principles need to be enforced at the system level, not left to convention. Panelica builds this isolation in by default: every user gets their own Linux user account with cgroups v2 limits, namespace isolation, SSH chroot jails, and PHP-FPM pools. The kind of configuration you would spend hours setting up manually, automated and maintained for you.

User management done right is not overhead. It is the foundation everything else stands on.

Security-first hosting panel

Run your servers on a modern panel.

Panelica is a modern, security-first hosting panel — isolated services, built-in Docker and AI-assisted management, with one-click migration from any panel.

Zero-downtime migration Fully isolated services Cancel anytime
Share:
How secure is your hosting panel?