Every multi-user Linux system depends on two things working correctly: knowing who’s allowed to do what (users and groups), and knowing what’s allowed to be done to a given file or directory (permissions). Bash is the interface most administrators use to manage both — creating accounts, adjusting group membership, and setting exactly the right access level on files and directories. Getting this wrong is one of the most common causes of both broken deployments and real security incidents.
This guide covers user and group management, the permission model, and how to script both safely.
Understanding Linux Users and Groups
Every user on a Linux system has a numeric User ID (UID) and belongs to at least one group, identified by a Group ID (GID). This information lives in a few key files:
/etc/passwd— user account information (username, UID, GID, home directory, shell)/etc/shadow— encrypted password data (readable only by root)/etc/group— group definitions and membership
# View your own user info
id
# View info for a specific user
id username
# List all users
cut -d: -f1 /etc/passwd
# List all groups
cut -d: -f1 /etc/group
Creating and Managing Users
# Create a new user with a home directory
sudo useradd -m -s /bin/bash newuser
# Set a password for the user
sudo passwd newuser
# Create a user with a specific UID and primary group
sudo useradd -m -u 1050 -g developers -s /bin/bash newuser
# Modify an existing user (e.g., change shell)
sudo usermod -s /bin/zsh newuser
# Lock and unlock an account
sudo usermod -L newuser # lock
sudo usermod -U newuser # unlock
# Delete a user (and their home directory)
sudo userdel -r newuser
What useradd -m actually does: it creates an entry in /etc/passwd and /etc/shadow, creates the user’s home directory (copying default files from /etc/skel), and creates a matching primary group if group management is set to per-user groups (common on Debian/Ubuntu-based systems).
Managing Groups
# Create a group
sudo groupadd developers
# Add an existing user to a group (supplementary, not primary)
sudo usermod -aG developers newuser
# Remove a user from a group
sudo gpasswd -d newuser developers
# List members of a group
getent group developers
The -aG flag combination is important: -a means “append” and -G sets supplementary groups. Using -G without -a replaces all of a user’s supplementary group memberships — a classic and dangerous mistake if you only meant to add one group.
Understanding File Permissions
Every file and directory has three permission sets — for the owner, the group, and others — each of which can have read (r), write (w), and execute (x) permissions.
ls -l file.txt
-rwxr-xr-- 1 alice developers 1024 Jun 1 10:00 file.txt
Breaking this down:
-— file type (-for regular file,dfor directory,lfor symlink)rwx— owner (alice) can read, write, executer-x— group (developers) can read, executer--— others can only read
Changing Permissions with chmod
Symbolic Mode
chmod u+x script.sh # add execute for owner
chmod g-w file.txt # remove write for group
chmod o=r file.txt # set others to read-only
chmod a+r file.txt # add read for everyone (all)
chmod ug+rw,o-rwx file.txt # combine multiple changes
Numeric (Octal) Mode
Each permission has a numeric value: read = 4, write = 2, execute = 1. Add them together for each of owner/group/other.
chmod 755 script.sh # rwxr-xr-x — common for executable scripts
chmod 644 file.txt # rw-r--r-- — common for regular files
chmod 600 secret.key # rw------- — owner-only, for sensitive files
chmod 700 private_dir # rwx------ — owner-only directory access
| Value | Permission |
|---|---|
| 7 | rwx |
| 6 | rw- |
| 5 | r-x |
| 4 | r– |
| 0 | — |
Recursive Changes
chmod -R 750 /var/www/myapp
Be careful with -R — applying execute permission recursively to every file (not just directories) can accidentally make plain data files executable, which is rarely what you want. A safer pattern separates files and directories:
find /var/www/myapp -type d -exec chmod 750 {} \;
find /var/www/myapp -type f -exec chmod 640 {} \;
Changing Ownership with chown and chgrp
# Change owner
sudo chown alice file.txt
# Change owner and group together
sudo chown alice:developers file.txt
# Change only the group
sudo chgrp developers file.txt
# Recursive ownership change
sudo chown -R alice:developers /home/alice/project
Special Permissions
Beyond the basic rwx model, Linux has three special permission bits:
# SUID (Set User ID) — run as file owner rather than the invoking user
chmod u+s /usr/bin/some_binary
# SGID (Set Group ID) — new files in a directory inherit the directory's group
chmod g+s /shared/project_dir
# Sticky bit — only file owner (or root) can delete files, even with group write access
chmod +t /shared/tmp_dir
SGID on a shared directory is especially useful for team collaboration — it ensures every new file created inside automatically belongs to the right group, instead of defaulting to the creating user’s primary group.
sudo mkdir /shared/team_project
sudo chgrp developers /shared/team_project
sudo chmod 2775 /shared/team_project # the leading 2 sets SGID
Scripting User and Permission Management
Bulk User Creation from a List
#!/bin/bash
# create_users.sh — create multiple users from a text file (one username per line)
USERLIST="new_employees.txt"
while IFS= read -r username; do
[[ -z "$username" ]] && continue
if id "$username" &>/dev/null; then
echo "User $username already exists, skipping"
else
useradd -m -s /bin/bash "$username"
echo "$username:$(openssl rand -base64 12)" | chpasswd
echo "Created user: $username"
fi
done < "$USERLIST"
How this works: it reads usernames line by line, checks each one against the existing user database with id, and only creates accounts that don’t already exist — avoiding duplicate-user errors on repeated runs. A random password is generated for each new account with openssl rand, then applied using chpasswd.
Auditing File Permissions
#!/bin/bash
# find_world_writable.sh — flag potentially risky world-writable files
find /var/www -type f -perm -o+w -exec ls -l {} \;
-perm -o+w matches files where the “other” write bit is set — a common security misconfiguration worth flagging in an audit.
Verifying Expected Ownership Across a Deployment
#!/bin/bash
EXPECTED_OWNER="www-data"
TARGET_DIR="/var/www/myapp"
find "$TARGET_DIR" ! -user "$EXPECTED_OWNER" -exec echo "Unexpected owner: {}" \;
Real-World Use Cases
- Automated onboarding scripts that create accounts, set group membership, and configure home directory permissions for new team members.
- Deployment scripts that ensure application files are owned by a dedicated service account (not root) with the minimum necessary permissions.
- Nightly audit scripts that scan for overly permissive files (world-writable, SUID binaries in unexpected places) and report them.
- CI/CD pipelines that set correct file permissions before packaging an application for deployment.
Best Practices
- Follow the principle of least privilege — grant only the permissions actually needed, nothing more.
- Prefer
usermod -aGoverusermod -Gto avoid accidentally wiping existing group memberships. - Use
644for regular files and755for directories and executables as sensible defaults, tightening further for sensitive data. - Use SGID on shared team directories so group ownership is inherited automatically.
- Avoid
chmod 777— it grants full read/write/execute to everyone, which is almost never actually necessary and is a common security red flag. - Script account creation with idempotency in mind (check before creating) so scripts can be re-run safely.
Security Considerations
- SUID binaries are a significant attack surface — audit them regularly with
find / -perm -4000 -type f 2>/dev/nulland remove the bit from anything that doesn’t genuinely need it. - Never store plaintext passwords in scripts. Use
chpasswdwith generated or securely-sourced passwords, or better, use SSH key-based access and disable password authentication entirely where feasible. - Restrict
sudoaccess carefully via/etc/sudoers(edited withvisudo, never directly) rather than adding users broadly to thesudoorwheelgroup. - Regularly audit
/etc/passwdfor unexpected accounts with UID 0 (root-equivalent privileges) — any user account other than root with UID 0 is a serious red flag. - Lock unused or former-employee accounts (
usermod -L) rather than deleting them immediately, preserving an audit trail while removing access.
Optimization Tips
- Use
getent passwdandgetent groupinstead of directly parsing/etc/passwd//etc/group—getentcorrectly handles systems using LDAP, NIS, or other name service backends, not just local flat files. - For permission audits across large filesystems, use
findwith specific-permfilters rather than scripting a manualls -lparse loop — it’s dramatically faster. - Batch
chown/chmodoperations usingfind -exec ... +(batched execution) instead of\;(one process per file) for better performance on large directory trees.
Troubleshooting Common Issues
Problem: “Permission denied” even though ls -l shows the right permissions. Check the permissions of every parent directory in the path — you need execute permission on all parent directories to traverse into a file, not just permissions on the file itself.
Problem: A newly added group membership doesn’t seem to take effect. Group membership changes don’t apply to already-running sessions. The user needs to log out and back in, or run newgrp groupname to refresh their group list in the current shell.
Problem: chmod -R on a directory made scripts executable but broke regular data files. This happens when applying a single octal mode recursively to both files and directories. Use find with separate -type f and -type d rules instead.
Problem: SGID doesn’t seem to be inherited by new files. Confirm the bit is actually set on the directory (ls -ld dirname should show s in the group execute position) and that files are being created directly inside it, not moved in from elsewhere (which can sometimes preserve original ownership depending on the tool used).
Common Mistakes
- Using
usermod -Ginstead of-aG, wiping a user’s existing supplementary groups. - Defaulting to
chmod 777to “make an error go away” instead of diagnosing the actual permission need. - Editing
/etc/sudoersdirectly instead of usingvisudo, risking a syntax error that locks out sudo entirely. - Forgetting that group changes require a new login session to take effect.
- Applying recursive chmod without separating file and directory permission needs.
Frequently Asked Questions
What’s the difference between a primary group and supplementary groups? Every user has exactly one primary group (used for files they create by default) and can belong to any number of supplementary groups (used for additional access to shared resources).
How do I see what permissions a specific user effectively has on a file? There’s no single built-in command, but stat filename combined with id username lets you manually cross-reference ownership and group membership against the permission bits. Tools like namei -l help trace permission issues along a full path.
Is it safe to run scripts as root that manage users? Yes, this is standard practice (useradd, usermod, etc. require root), but the scripts themselves should be carefully reviewed, access-restricted, and avoid taking unsanitized external input directly into system commands.
What does the sticky bit actually do? On a directory, it ensures only the file’s owner (or root) can delete or rename that file, even if others have write access to the directory — commonly used on shared directories like /tmp.
Summary
Managing users and permissions well is foundational to running a secure, predictable Linux system. Bash gives you direct, scriptable control over account creation (useradd, usermod), group membership (groupadd, gpasswd), and the permission model (chmod, chown, special bits like SUID/SGID/sticky) — all of which can be automated for onboarding, deployment, and auditing. The core discipline is simple: grant the minimum access actually required, verify it with tools like find and getent, and script these operations idempotently so they’re safe to re-run.
References
- GNU Coreutils Manual — chmod, chown: https://www.gnu.org/software/coreutils/manual/coreutils.html
- man7.org useradd(8): https://man7.org/linux/man-pages/man8/useradd.8.html
- man7.org chmod(1): https://man7.org/linux/man-pages/man1/chmod.1.html
- Debian Administrator’s Handbook — Users and Groups: https://debian-handbook.info/browse/stable/sect.user-authentication.html
