Search…
Linux from Scratch · Part 4

Users, groups, and permissions

In this series (15 parts)
  1. What is Linux and how it differs from other OSes
  2. Installing Linux and setting up your environment
  3. The Linux filesystem explained
  4. Users, groups, and permissions
  5. Essential command line tools
  6. Shell scripting fundamentals
  7. Processes and job control
  8. Standard I/O, pipes, and redirection
  9. The Linux networking stack
  10. Package management and software installation
  11. Disk management and filesystems
  12. Logs and system monitoring
  13. SSH and remote access
  14. Cron jobs and task scheduling
  15. Linux security basics for sysadmins

Every file in Linux has an owner, a group, and a set of permissions that determine who can do what with it. This is the foundation of Linux security. A misconfigured permission is one of the most common causes of both “I can’t access my file” frustrations and privilege escalation vulnerabilities.

Prerequisites

You should understand the Linux filesystem and how files are organized before diving into permissions.

Users and groups

Every user on a Linux system has:

  • A username (like pratik)
  • A UID (user ID, a number like 1000)
  • A primary group (usually the same name as the username)
  • A GID (group ID for the primary group)
  • A home directory (usually /home/username)
  • A login shell (usually /bin/bash)

This information is stored in /etc/passwd:

cat /etc/passwd | grep pratik

Output:

pratik:x:1000:1000:Pratik Tiwari:/home/pratik:/bin/bash

The fields are separated by colons:

username:password:UID:GID:comment:home:shell

The x in the password field means the actual password hash is stored in /etc/shadow, which only root can read:

sudo cat /etc/shadow | grep pratik

Output:

pratik:$6$abc123...xyz:19890:0:99999:7:::

The hash starts with $6$ which means SHA-512. This is the hashed and salted version of the password.

Managing users

# Create a new user
sudo useradd -m -s /bin/bash newuser

# Set their password
sudo passwd newuser

# Add user to a group
sudo usermod -aG docker newuser

# Delete a user and their home directory
sudo userdel -r newuser

Groups

Groups let you give the same permissions to multiple users. Every user has a primary group and can belong to additional (supplementary) groups.

# See your groups
groups

Output:

pratik adm cdrom sudo docker
# See all groups on the system
cat /etc/group | head -10

Output:

root:x:0:
daemon:x:1:
bin:x:2:
sys:x:3:
adm:x:4:pratik
sudo:x:27:pratik
docker:x:999:pratik
# Create a new group
sudo groupadd developers

# Add a user to the group
sudo usermod -aG developers pratik

The permission model

Every file has three sets of permissions for three categories of users:

graph LR
  F[File] --> O["Owner (u)"]
  F --> G["Group (g)"]
  F --> A["Others (o)"]
  O --> R1[Read]
  O --> W1[Write]
  O --> X1[Execute]
  G --> R2[Read]
  G --> W2[Write]
  G --> X2[Execute]
  A --> R3[Read]
  A --> W3[Write]
  A --> X3[Execute]
  style F fill:#f9a825,stroke:#f57f17,color:#000
PermissionOn a fileOn a directory
Read (r)Can view file contentsCan list directory contents
Write (w)Can modify file contentsCan create/delete files in directory
Execute (x)Can run the file as a programCan enter the directory (cd)

The ls -l output shows permissions as a 10-character string:

ls -l /etc/hostname

Output:

-rw-r--r-- 1 root root 7 Jun 15 10:00 /etc/hostname

Breaking down -rw-r--r--:

-    rw-    r--    r--
|    |      |      |
|    |      |      +-- others: read only
|    |      +-- group: read only
|    +-- owner: read + write
+-- file type (- = regular file)

Octal notation

Each permission set (rwx) maps to a 3-bit number:

PermissionBinaryOctal
---0000
—x0011
-w-0102
-wx0113
r—1004
r-x1015
rw-1106
rwx1117

So rw-r--r-- = 644 (6 for owner, 4 for group, 4 for others).

Common permission values:

OctalMeaningUse case
644Owner reads/writes, everyone readsConfig files, documents
755Owner full, everyone reads/executesScripts, programs, directories
700Owner onlyPrivate files, SSH keys
600Owner reads/writes onlyPrivate config files
777Everyone full access⚠ Almost never appropriate

Changing permissions with chmod

Symbolic notation

# Add execute for the owner
chmod u+x script.sh

# Remove write for group and others
chmod go-w file.txt

# Set exact permissions: owner rwx, group rx, others rx
chmod u=rwx,g=rx,o=rx program

# Add read for everyone
chmod a+r document.txt

Octal notation

# Set 755 (rwxr-xr-x)
chmod 755 script.sh

# Set 644 (rw-r--r--)
chmod 644 config.txt

# Set 600 (rw-------)
chmod 600 ~/.ssh/id_rsa

Changing ownership with chown

# Change owner
sudo chown newuser file.txt

# Change owner and group
sudo chown newuser:developers file.txt

# Change recursively
sudo chown -R www-data:www-data /var/www/

Special permission bits

There are three additional permission bits that go beyond the standard rwx model. These are important for security because they are common targets for privilege escalation.

setuid (Set User ID)

When set on an executable, the program runs with the permissions of the file owner, not the user who launched it.

ls -l /usr/bin/passwd

Output:

-rwsr-xr-x 1 root root 68208 Jun 15 10:00 /usr/bin/passwd

The s in the owner execute position means setuid is set. When any user runs passwd, it executes as root. This is necessary because passwd needs to write to /etc/shadow, which only root can modify.

⚠ A setuid binary owned by root that has a vulnerability can give an attacker root access. This is a classic privilege escalation vector.

setgid (Set Group ID)

On an executable, the program runs with the permissions of the file’s group. On a directory, new files created inside inherit the directory’s group instead of the creator’s primary group.

# Create a shared directory
sudo mkdir /shared
sudo chown root:developers /shared
sudo chmod 2775 /shared

# The 2 at the front is the setgid bit
ls -ld /shared

Output:

drwxrwsr-x 2 root developers 4096 Jun 15 10:00 /shared

The s in the group execute position means setgid. Any file created in /shared will belong to the developers group.

Sticky bit

On a directory, only the file owner (or root) can delete files, even if others have write permission on the directory. /tmp uses this:

ls -ld /tmp

Output:

drwxrwxrwt 15 root root 4096 Jun 15 10:00 /tmp

The t at the end is the sticky bit. Everyone can create files in /tmp, but you can only delete your own.

Special bits in octal

BitOctalPosition
setuid4First digit (e.g., 4755)
setgid2First digit (e.g., 2775)
sticky1First digit (e.g., 1777)

Example 1: Set permissions using symbolic and octal notation

Let’s create a project with proper permissions:

# Create a project directory
mkdir -p ~/project
cd ~/project

# Create files with different purposes
touch app.py config.yaml deploy.sh README.md

# Make deploy.sh executable
chmod u+x deploy.sh

# Make config.yaml owner-only (contains secrets)
chmod 600 config.yaml

# Make README world-readable
chmod 644 README.md

# Check our work
ls -l

Output:

total 0
-rw-r--r-- 1 pratik pratik 0 Jun 15 10:00 app.py
-rw------- 1 pratik pratik 0 Jun 15 10:00 config.yaml
-rwxr--r-- 1 pratik pratik 0 Jun 15 10:00 deploy.sh
-rw-r--r-- 1 pratik pratik 0 Jun 15 10:00 README.md

Now let’s verify who can do what. Create a test user and try to access the config:

# As root, create a test user
sudo useradd -m testuser

# Try to read the config as testuser
sudo -u testuser cat ~/project/config.yaml

Output:

cat: /home/pratik/project/config.yaml: Permission denied

The 600 permission means only the owner (pratik) can read it. This is exactly what you want for files containing database passwords, API keys, or other secrets.

Example 2: Trace what a permission string actually allows

Let’s decode a real permission problem. Suppose a web server cannot read files in /var/www/html:

# Check the web server's user
ps aux | grep nginx | head -2

Output:

www-data  1234  0.0  0.1  12345  5678 ?  S  10:00  0:00 nginx: worker process
root      1230  0.0  0.0  12345  2345 ?  Ss 10:00  0:00 nginx: master process

Nginx worker runs as www-data. Let’s check file permissions:

ls -la /var/www/html/

Output:

total 12
drwxr-x--- 2 root root 4096 Jun 15 10:00 .
drwxr-xr-x 3 root root 4096 Jun 15 10:00 ..
-rw-r----- 1 root root  612 Jun 15 10:00 index.html

Walk through the access check for www-data reading index.html:

  1. Is www-data the owner? No, owner is root.
  2. Is www-data in the group? The group is root. Check: groups www-data shows www-data. Not in the root group.
  3. Check “others” permission. The directory is drwxr-x---. Others have no permissions (---). www-data cannot even enter the directory.

Fix:

# Option 1: Change ownership
sudo chown -R www-data:www-data /var/www/html/

# Option 2: Add www-data to the correct group and set group read
sudo chgrp -R www-data /var/www/html/
sudo chmod 750 /var/www/html/
sudo chmod 640 /var/www/html/index.html

# Option 3: Open "others" read (least restrictive)
sudo chmod o+rx /var/www/html/
sudo chmod o+r /var/www/html/index.html

The best choice depends on your security requirements. Option 1 is simplest. Option 2 is more precise. Option 3 is the most permissive and generally not recommended for sensitive content.

# Verify the fix (using option 2)
ls -la /var/www/html/

Output:

total 12
drwxr-x--- 2 root www-data 4096 Jun 15 10:00 .
drwxr-xr-x 3 root root     4096 Jun 15 10:00 ..
-rw-r----- 1 root www-data  612 Jun 15 10:00 index.html

Now www-data can read the directory (group has r-x) and read the file (group has r—).

What comes next

With filesystem structure and permissions covered, you are ready for Essential command line tools, where you will learn the core commands for navigating, searching, and processing files.

For an attacker’s view of Linux permissions, see Linux privilege escalation, which shows how misconfigured permissions lead to security breaches.

Start typing to search across all content
navigate Enter open Esc close