Linux decides who can read, change or run each file based on its owner, its group and a set of permission bits. In this tutorial you will create user accounts and a group, read and change permissions with chmod and chown, build a shared directory where a team can collaborate safely, fine-tune access with ACLs, and give a user limited sudo rights. The examples use Ubuntu 24.04; differences for Debian 12 and Rocky Linux 9 are noted where they matter.
Prerequisites
To follow this guide you need:
- A server running Ubuntu 24.04 LTS (Debian 12 and Rocky Linux 9 work with the noted changes), for example a CubePath VPS.
- A non-root user with
sudoprivileges, logged in over SSH.
The examples create two users, alice and bob, and a group called developers. Use your own names if you prefer.
Step 1 - Creating and managing user accounts
Each account has an entry in /etc/passwd (name, UID, home directory, shell) and a password hash in /etc/shadow, which only root can read.
On Ubuntu and Debian, adduser is the friendly tool: it creates the home directory, copies the skeleton files from /etc/skel and asks for a password:
sudo adduser alice
useradd is the low-level command available on every distribution (on Rocky Linux, adduser is simply a link to it). Use -m to create the home directory and -s to set the shell, then set a password:
sudo useradd -m -s /bin/bash bob
sudo passwd bob
Check the new accounts with id, which shows the UID, the primary group and all supplementary groups:
id alice
uid=1001(alice) gid=1001(alice) groups=1001(alice),100(users)
A few everyday changes:
| Task | Command |
|---|---|
| Change a user's shell | sudo usermod -s /bin/zsh alice |
| Lock an account (keeps files) | sudo usermod -L bob |
| Unlock it | sudo usermod -U bob |
| Expire an account on a date | sudo usermod -e 2026-12-31 bob |
| Delete a user and home directory | sudo userdel -r bob |
Note
usermod -Lonly locks the password. A user with an SSH key can still log in. To block every login, also expire the account withsudo usermod -e 1 bob, or set its shell to/usr/sbin/nologin.
Step 2 - Creating groups and adding members
Groups let you grant access to several users at once. Create the developers group:
sudo groupadd developers
Add both users as supplementary members. The -a flag (append) is essential: without it, -G replaces all of the user's existing supplementary groups:
sudo usermod -aG developers alice
sudo usermod -aG developers bob
Verify the membership:
getent group developers
developers:x:1003:alice,bob
To remove a user from a group, use gpasswd:
sudo gpasswd -d bob developers
Group changes apply to new login sessions. A user who is already logged in has to log out and back in (or start a new SSH session) before id shows the new group.
Step 3 - Reading file permissions
List a file with ls -l:
ls -l /etc/passwd
-rw-r--r-- 1 root root 2247 Sep 20 10:12 /etc/passwd
The first column has ten characters: the file type (- file, d directory, l symbolic link) followed by three sets of rwx, for the owner, the group and others. After the link count come the owner (root) and the group (root).
The bits mean different things on files and directories:
| Bit | Value | On a file | On a directory |
|---|---|---|---|
r | 4 | Read the contents | List the names inside |
w | 2 | Modify the contents | Create, delete and rename entries |
x | 1 | Run it as a program | Enter it and access entries by name |
Adding the values of each set gives the numeric (octal) form. rw-r--r-- is 644, rwxr-xr-x is 755, and rwx------ is 700.
stat shows both notations at once:
stat -c '%A %a %U:%G %n' /etc/passwd /etc/shadow
-rw-r--r-- 644 root:root /etc/passwd
-rw-r----- 640 root:shadow /etc/shadow
On Rocky Linux, /etc/shadow has mode 000 and is readable only through root's special privileges.
Step 4 - Changing permissions with chmod
chmod accepts either numbers or symbolic changes. Create a test file as your user to experiment:
touch ~/report.txt
chmod 640 ~/report.txt
ls -l ~/report.txt
-rw-r----- 1 sammy sammy 0 Sep 24 11:02 /home/sammy/report.txt
Symbolic mode is handy for changing one bit without touching the rest. u, g, o and a stand for user (owner), group, others and all:
chmod g+w ~/report.txt
chmod o-rwx ~/report.txt
The same syntax makes a script executable for its owner, for example chmod u+x ~/backup.sh.
Typical values:
| Mode | Use |
|---|---|
600 | Private files: SSH keys, .env files with secrets |
644 | Regular files other users may read |
700 | Private directories, such as ~/.ssh |
755 | Scripts and directories everyone may enter |
To apply permissions to a whole tree, do not use chmod -R 755, which makes every file executable. Use the capital X, which adds execute only to directories (and to files that are already executable):
chmod -R u=rwX,g=rX,o= ~/myapp
Step 5 - Changing ownership with chown
Only root can give a file to another user, so chown needs sudo. The syntax is owner:group:
sudo chown alice:developers /home/sammy/report.txt
Change just the group with chown :group (or the older chgrp), and use -R for directories:
sudo chown -R www-data:www-data /var/www/example
Check the result:
ls -l /home/sammy/report.txt
-rw-rw---- 1 alice developers 0 Sep 24 11:02 /home/sammy/report.txt
Step 6 - Building a shared team directory with setgid
A common need is a directory where every member of a group can create and edit files. Plain permissions are not enough: new files get the creator's primary group, so other members cannot edit them. The setgid bit on a directory solves this by making new files inherit the directory's group.
Create the directory, hand it to the developers group and set mode 2770. The leading 2 is setgid, 770 gives full access to owner and group and nothing to others:
sudo mkdir -p /srv/project
sudo chown root:developers /srv/project
sudo chmod 2770 /srv/project
ls -ld /srv/project
drwxrws--- 2 root developers 4096 Sep 24 11:10 /srv/project
The s in the group position confirms setgid is on. Test it by creating a file as alice:
sudo -u alice touch /srv/project/notes.md
ls -l /srv/project
-rw-r--r-- 1 alice developers 0 Sep 24 11:12 notes.md
The file belongs to the developers group even though alice's primary group is alice. Whether other members can also edit it depends on the creator's umask, covered in the next step. Files created through sudo use sudo's default umask of 0022, which is why this test file has no group write bit. When alice logs in with her own SSH session on Ubuntu, her umask is 0002 and her new files are -rw-rw-r--, so bob can edit them.
Two related special bits are worth knowing:
- Sticky bit (
1, shown ast): in a shared writable directory, users can only delete their own files./tmpuses it (drwxrwxrwt). Set it withsudo chmod +t /srv/projectif members must not delete each other's files. - setuid (
4, shown assin the owner position): a program runs with its owner's privileges, as/usr/bin/passwddoes. Never set it on your own scripts.
List every setuid file on the system to know what is there:
sudo find / -xdev -perm -4000 -type f
Step 7 - Understanding the default umask
The umask removes permissions from newly created files. Files start from 666 and directories from 777, and the umask bits are subtracted.
umask
0002
On Ubuntu 24.04 and Rocky Linux 9, regular users have 0002 (new files 664, directories 775) and root has 0022 (644 and 755). On Debian 12 regular users get 0022 by default, so group members cannot edit each other's files in the shared directory. To give a user 0002, add this line to their ~/.profile:
echo 'umask 0002' >> ~/.profile
For a service that should create private files, set the umask in its systemd unit instead, with UMask=0077 in the [Service] section.
Step 8 - Fine-grained access with ACLs
Standard permissions allow only one owner and one group. Access Control Lists add entries for extra users or groups. Suppose carol, who is not a developer, needs read-only access to /srv/project.
The acl tools are installed by default on Ubuntu; on Debian 12 or Rocky Linux install them with sudo apt install acl or sudo dnf install acl.
Create the user and grant read and traverse access on the existing tree:
sudo adduser carol
sudo setfacl -R -m u:carol:rX /srv/project
Add a default ACL so files created in the future inherit the same rule:
sudo setfacl -d -m u:carol:rX /srv/project
Inspect the result:
getfacl /srv/project
# file: srv/project
# owner: root
# group: developers
# flags: -s-
user::rwx
user:carol:r-x
group::rwx
mask::rwx
other::---
default:user::rwx
default:user:carol:r-x
default:group::rwx
default:mask::rwx
default:other::---
In ls -l, a + after the permission string tells you a file has ACLs. Remove carol's entries with sudo setfacl -R -x u:carol /srv/project and sudo setfacl -d -x u:carol /srv/project.
Step 9 - Granting sudo access
Full administrator rights come from group membership: the sudo group on Ubuntu and Debian, wheel on Rocky Linux:
sudo usermod -aG sudo alice
Often a user only needs one or two privileged commands. Put such rules in a separate file under /etc/sudoers.d/ and always edit it with visudo, which checks the syntax before saving so a typo cannot break sudo:
sudo visudo -f /etc/sudoers.d/developers
This rule lets members of developers restart and check the Nginx service, and nothing else:
%developers ALL=(root) /usr/bin/systemctl restart nginx, /usr/bin/systemctl status nginx
Give the file safe permissions and verify what a user may run:
sudo chmod 0440 /etc/sudoers.d/developers
sudo -l -U bob
User bob may run the following commands on server:
(root) /usr/bin/systemctl restart nginx, /usr/bin/systemctl status nginx
Always use full paths in sudo rules, and never allow commands that can spawn a shell or edit arbitrary files (editors, less, find, bash), because those give full root access in practice.
Step 10 - Setting password aging
For accounts that still use passwords, chage controls expiry. Show the current policy for bob:
sudo chage -l bob
Require a change every 90 days, with a 7-day warning, and force a new password at the next login:
sudo chage -M 90 -W 7 bob
sudo chage -d 0 bob
These settings apply to password logins and to sudo prompts. Users who log in with SSH keys are not blocked by an expired password, but will have to change it the next time sudo asks for it.
Troubleshooting
Permission denied on a file that looks readable: the user also needs x on every parent directory. Check the whole path with namei -l /srv/project/notes.md.
A user does not get the new group's access: group membership is read at login. Ask the user to open a new session and check with id.
Group members cannot edit each other's files in the shared directory: the creator's umask removed group write. Check with umask and set 0002 as in Step 7, then fix existing files with sudo chmod -R g+w /srv/project. If files must always be group-writable regardless of umask, add a default ACL for the group: sudo setfacl -d -m g:developers:rwX /srv/project.
sudo: parse error in /etc/sudoers.d/...: the file was edited without visudo. Fix it with sudo visudo -f /etc/sudoers.d/developers, or, if sudo no longer works at all, with pkexec visudo or from the server's web console as root.
Conclusion
You created users and groups, read and changed permissions in both notations, built a shared directory with setgid, added an ACL for a user outside the group, and delegated specific commands through sudo. Next, you can harden SSH so each of these users logs in with their own key, review group memberships periodically with getent group, and apply the same ownership model to your application directories under /srv or /var/www.
