from useradd to a systematic audit
Running Linux servers with shared logins and unmaintained accounts risks security gaps that only surface after an incident. This article explains useradd, usermod and groupadd from the ground up, shows the principle of a dedicated unprivileged user per application and provides a practical workflow to systematically audit existing users and groups on a server.
Table of contents
- 1. Why user management decides a server's security
- 2. useradd fundamentals: options, defaults and home directory
- 3. usermod and userdel: maintaining and removing existing accounts
- 4. Managing groups with groupadd, groupmod and gpasswd
- 5. System users vs. regular users: UID and GID ranges
- 6. The principle of a dedicated unprivileged user per application
- 7. sudo and granular privilege assignment instead of shared root access
- 8. Password policies, account locks and chage
- 9. Auditing and cleaning up existing users and groups
- 10. Summary
- 11. FAQ
1. Why user management decides a server's security
A Linux server where several people share the same root password, or where old accounts of former employees are still active, is structurally insecure no matter how well the firewall and patch level are maintained. Every user and every group on a system represents a permission boundary. If that boundary is drawn carelessly, it creates exactly the gaps that get exploited for lateral movement through the system during a security incident. Clean user management is therefore not administrative housekeeping, it is one of the most effective security measures available.
In practice, the problem rarely shows up on day one, it accumulates over months or years of operation: a developer gets sudo rights for a deployment on short notice and they are never revoked again. A service runs as root out of convenience because that was faster to set up initially. These small shortcuts add up to a server whose actual permission structure nobody can fully explain anymore. The following sections cover the tools and principles that let you avoid this state from the start, or systematically clean it up after the fact.
2. useradd fundamentals: options, defaults and home directory
The useradd command is the fundamental tool for creating new accounts on Linux, but it works quite minimally without extra options: without -m no home directory is created, without -s the login shell often stays on a system default that does not match the actual intended use. Important options are -m for the home directory, -d for a different path, -s for the shell, -c for a comment with a plain text name, and -G for additional secondary groups right at creation time. The system wide defaults, such as which shell is set without -s, live in /etc/default/useradd and can be shown and adjusted with useradd -D.
Debian based systems additionally offer adduser, an interactive Perl script that wraps useradd with sensible prompts and automatically creates a home directory including skeleton files from /etc/skel. For automated deployments through Ansible or shell scripts, useradd is usually still the better choice because it runs deterministically without interaction and behaves identically across virtually every distribution. Important: every new user needs either an initial password or an explicitly disabled login, otherwise the account stays in an unclear intermediate state.
# Create a regular user with a home directory and bash shell
useradd -m -d /home/jdoe -s /bin/bash -c "Jane Doe" jdoe
# Set the password interactively (never pass it as a plaintext argument)
passwd jdoe
# Create a user directly with secondary groups
useradd -m -s /bin/bash -G sudo,docker jdoe
# Show system wide defaults for new users
useradd -D
# Adjust the home directory template before new users are created
ls -la /etc/skel/
# Create a user without a login shell for pure file access
useradd -m -s /usr/sbin/nologin sftpuser
3. usermod and userdel: maintaining and removing existing accounts
usermod changes properties of an already existing account without requiring a fresh creation. The most common use cases are adding a user to a group with usermod -aG group user, changing the shell with -s, and renaming with -l. The critical option here is -a for append: forget it, and usermod -G replaces all previously assigned secondary groups with the newly given list instead of adding to it. This mistake is one of the most common in Linux administration and quietly removes a user from important groups such as docker or sudo.
To fully remove an account, userdel is the tool, but by default it does not delete the home directory. Only userdel -r additionally removes the home directory and mail spool. Before deleting, it always pays off to check with find / -user username for orphaned files outside the home directory, for example in cron jobs or application directories. If you do not want to delete an account immediately but need it locked, use usermod -L to lock the password or usermod -e 1 to set an immediate expiration date in the past.
# Add a user to a group WITHOUT losing existing groups
usermod -aG docker jdoe
# WRONG: replaces all secondary groups without warning
usermod -G docker jdoe
# Lock an account immediately without deleting it
usermod -L -e 1 jdoe
# Before deleting: find orphaned files outside the home directory
find / -xdev -user jdoe -not -path "/home/jdoe/*" 2>/dev/null
# Remove a user including home directory and mail spool completely
userdel -r jdoe
# Check the current group memberships of a user
id jdoe
4. Managing groups with groupadd, groupmod and gpasswd
Groups combine multiple users into a shared permission unit and are the central tool for controlling file access without maintaining an ACL for every single case. groupadd deploy creates a new group, groupmod -n newname oldname renames it, groupdel removes it again, provided no user still has that group set as their primary group. Every user has exactly one primary group, usually a group of the same name created automatically at account creation, but any number of secondary groups through which additional privileges are granted.
For targeted adding and removing of individual members without touching the rest of the configuration, gpasswd -a user group for adding and gpasswd -d user group for removing are useful, functionally equivalent to usermod -aG but semantically clearer since it puts the group rather than the user at the center of the command. In server environments with clearly separated responsibilities, such as deploy, backup and monitoring, a well thought out group structure often replaces several individual sudo rules with a single group based rule.
5. System users vs. regular users: UID and GID ranges
Linux distinguishes between system users and regular users based on the numeric user ID, not on an explicit flag. On most distributions, UIDs from 0 to 999 are reserved for system users and services, while regular, interactive user accounts start at UID 1000. The exact threshold is defined in /etc/login.defs under UID_MIN and UID_MAX, or SYS_UID_MIN and SYS_UID_MAX, and can vary slightly by distribution, RHEL based systems historically set the boundary at 200 instead of 1000.
System users such as www-data, mysql or redis automatically get a UID from this reserved range through the useradd -r option, usually without their own home directory and without an interactive login shell. This separation is more than cosmetic: backup scripts, monitoring tools and security scanners rely on the UID boundary to distinguish real human accounts from technical service accounts. A system service with a UID above 1000 confuses these automations and makes later audits considerably harder.
# Ansible playbook: provision a dedicated system user per application
- name: Provision application service users
hosts: webservers
become: true
tasks:
- name: Create system group for PHP-FPM pool
ansible.builtin.group:
name: app-shop
system: true
state: present
- name: Create dedicated system user without login shell
ansible.builtin.user:
name: app-shop
group: app-shop
system: true
shell: /usr/sbin/nologin
home: /var/www/shop
create_home: false
state: present
- name: Assign application directory to the dedicated user
ansible.builtin.file:
path: /var/www/shop
owner: app-shop
group: app-shop
mode: "0750"
recurse: true
6. The principle of a dedicated unprivileged user per application
The web server runs as www-data, MySQL as mysql, Redis as redis: this pattern is no coincidence, it is a central security principle. Every application should run under its own unprivileged user that can only access the files and resources this one application actually needs. If a PHP-FPM pool gets compromised through a vulnerability, the dedicated user limits the damage to exactly this application scope instead of automatically handing the attacker access to every other service on the same server.
This principle becomes especially important with multiple applications on the same server, for instance several Magento stores with their own PHP-FPM pools. If every store runs under the same www-data user, a compromised store can access the files of every other store, because file system permissions provide no separation. The solution: a dedicated system user per application, its own PHP-FPM pool with matching user and group entries, and file permissions that grant write access only to that specific user and its associated group. The additional administrative overhead is small compared to the risk of a shared identity.
7. sudo and granular privilege assignment instead of shared root access
A shared root password across multiple administrators is problematic both for security and traceability: there is no individual attribution of who performed which action, and when a team member leaves, the entire password has to be changed. sudo solves both problems by letting every administrator work with their own account while receiving root privileges only temporarily for specifically permitted commands. Every action is logged in /var/log/auth.log or, depending on the distribution, in the journal, including the username and the executed command.
Instead of adding users wholesale to the sudo or wheel group, granular rules in /etc/sudoers.d/ allow only the commands actually needed for a role. A deployment account usually only needs privileges for systemctl restart php8.4-fpm and a handful of targeted commands, not unrestricted root access. Editing must always go through visudo, which validates the syntax before saving and prevents a broken sudoers file that would otherwise lock out every sudo access on the server.
8. Password policies, account locks and chage
Password policies only take effect once they are technically enforced instead of merely written down in a document. The file /etc/login.defs defines system wide default values for password aging, such as PASS_MAX_DAYS for the maximum validity period and PASS_MIN_DAYS to prevent switching back to an old password too quickly. These values, however, only apply to newly created accounts, existing accounts must be adjusted individually with chage.
chage -l user shows the current password history of an account, including the last change date and expiration date. chage -M 90 user enforces a password change interval of 90 days, chage -E 2026-12-31 user sets a hard expiration date for temporary accounts such as intern or freelancer access. Complexity itself is enforced by the PAM module pam_pwquality, configured in /etc/security/pwquality.conf, which enforces minimum length, character classes and a dictionary check before a new password is accepted at all.
# /etc/login.defs (excerpt): configure password aging system wide
PASS_MAX_DAYS 90
PASS_MIN_DAYS 7
PASS_WARN_AGE 14
# UID/GID boundaries for regular users
UID_MIN 1000
UID_MAX 60000
SYS_UID_MIN 100
SYS_UID_MAX 999
GID_MIN 1000
GID_MAX 60000
SYS_GID_MIN 100
SYS_GID_MAX 999
# Encryption scheme for new password hashes
ENCRYPT_METHOD SHA512
9. Auditing and cleaning up existing users and groups
On servers that have grown over years in production, accounts accumulate whose reason for existing nobody can explain anymore: former employees, test accounts from a migration project, services that were decommissioned long ago. A regular audit starts with a complete look at /etc/passwd and /etc/group, filtered for accounts with a UID above 1000 that have an interactive shell. lastlog additionally shows which accounts have not logged in since when, a strong indicator of an orphaned account.
Equally important is checking which accounts are members of privileged groups such as sudo, wheel or docker, since access to the Docker daemon effectively equals root privileges. getent group sudo lists all members of a group directly without manually searching through the file. A simple, repeatable audit script that automates these checks and outputs the results in a structured way turns an occasional manual review into a reliable, repeatable process that applies the same criteria even months later.
{
"audit_date": "2026-07-12",
"host": "shop-prod-01",
"findings": [
{
"user": "ex-intern",
"uid": 1042,
"shell": "/bin/bash",
"last_login": "2025-11-03",
"sudo_member": false,
"recommendation": "Lock account and delete after confirmation"
},
{
"user": "deploy-old",
"uid": 1015,
"shell": "/bin/bash",
"last_login": "2024-06-12",
"sudo_member": true,
"recommendation": "Remove from sudo group immediately, then lock"
},
{
"user": "app-shop",
"uid": 998,
"shell": "/usr/sbin/nologin",
"last_login": "never",
"sudo_member": false,
"recommendation": "No action needed, dedicated system user"
}
]
}
The findings of such an audit fall into recurring cleanup patterns. The following table sets risky everyday practices against the recommended alternatives, applicable both when creating new accounts and when retroactively cleaning up existing servers.
| Task | Risky practice | Recommended approach | Benefit |
|---|---|---|---|
| Starting an application | Service runs as root | Dedicated system user per app | Limits damage in case of compromise |
| Extending groups | usermod -G group |
usermod -aG group |
Existing groups are preserved |
| Sharing root access | Shared root password | Individual accounts with sudo | Logged, attributable actions |
| Detecting orphaned accounts | Never checked | lastlog + audit script |
Inactive accounts become visible |
| Granting sudo rights | Blanket membership in sudo/wheel | Granular rules in /etc/sudoers.d | Least privilege instead of full access |
These five patterns cover the bulk of security issues that typically surface in a server audit. Applying them consistently not only reduces the attack surface, it also builds the foundation for traceable compliance evidence if a client or auditor requests insight into the access structure.
Mironsoft
Server hardening, user management and deployment infrastructure
Time to finally structure server access cleanly?
We audit existing users and groups on your servers, remove orphaned accounts and set up dedicated, unprivileged service users with granular sudo rules for your deployment stack.
User audit
Complete inventory of all accounts, groups and sudo rights
Service user setup
Dedicated system users per application with matching file permissions
sudo hardening
Granular sudoers rules instead of blanket group privileges
10. Summary
Clean Linux user and group management starts with the correct use of useradd, usermod and groupadd and ends with a recurring audit process that uncovers orphaned accounts and excessive privileges. System users in the UID range below 1000 cleanly separate technical service accounts from regular human accounts. The principle of a dedicated, unprivileged user per application, as demonstrated by www-data or mysql, limits the damage of a compromised application to exactly that one application.
Individual sudo rules instead of shared root passwords create traceability for every administrative action. Password policies via chage and PAM ensure accounts do not remain valid indefinitely. The biggest lever, however, is not any single tool but consistency: a server that is systematically checked for orphaned accounts and excessive group privileges once a quarter stays manageable for years, while a server that is never checked eventually becomes something nobody can fully explain anymore.
Managing Linux users and groups, the essentials at a glance
Core tools
useradd, usermod and groupadd for creating and maintaining accounts. Do not forget -aG instead of -G when changing groups.
UID ranges
System users mostly below UID 1000, regular users from UID 1000. Boundaries live in /etc/login.defs.
Dedicated service user
Run every application under its own unprivileged user, analogous to www-data or mysql.
Regular auditing
lastlog, getent group and a repeatable audit script uncover orphaned accounts and excessive privileges.