You reach the last lesson of the module with an outstanding debt. For six lessons you have been seeing strings like -rw-r----- and drwxr-xr-x in every ls -l, typing sudo in front of certain commands without entirely knowing why, and accepting that /etc/tramontana/app.conf belongs to root:tramontana without being able to explain what exactly that implies. This lesson settles that debt.

The Unix permission model is more than fifty years old and is still, by a long way, the most widely used security mechanism on the planet. It is surprisingly simple — nine bits and two identifiers — and surprisingly subtle: the same permission means different things on a file and on a directory, and that asymmetry is the source of most misunderstandings.

This is not optional material. A server with badly set permissions works perfectly until the day somebody reads a file they should not, or deletes one that was not theirs, or manages to run code with somebody else's privileges. By the end of this lesson you will be able to design the permissions of a complete deployment and justify every decision, which is exactly what is expected of a systems administrator.

Contents

  1. The Unix security model: user, group, others
  2. Reading the ls -l string character by character
  3. The three permissions on files and on directories
  4. Octal notation and symbolic notation
  5. chmod in both notations
  6. chown and chgrp
  7. Groups: why teamwork is not solved with "others"
  8. The umask
  9. Special bits: SUID, SGID and sticky
  10. Case study: the permissions of Tramontana Bookings

  1. The Unix security model: user, group, others

Every file on Linux has exactly one owner and exactly one owning group. From there, the system divides everybody into three categories, and each one has its own set of permissions:

Category Letter Who it is
User u The owner of the file
Group g The members of the owning group
Others o Every other user on the system

When a process tries to access a file, the kernel evaluates in this strict order:

flowchart TD
    A["A process requests access to a file"] --> R{"Is the user<br/>root?"}
    R -->|Yes| RA["ACCESS GRANTED<br/>(root bypasses the permissions)"]
    R -->|No| B{"Does the UID match<br/>the owner?"}
    B -->|Yes| C["Apply the USER permissions<br/>and look no further"]
    B -->|No| D{"Does the GID or one of<br/>their groups match?"}
    D -->|Yes| E["Apply the GROUP permissions<br/>and look no further"]
    D -->|No| F["Apply the OTHERS permissions"]

There is a detail in that diagram that surprises people and has to sink in: the first category that matches is the one applied, and no further checking happens. It is not cumulative.

The practical consequence, and it is counter-intuitive:

operator@srv-tramontana:~$ ls -l report.txt
----rw-r-- 1 operator tramontana 240 Aug 18 16:02 report.txt

That file belongs to operator, the owner has no permissions at all, and the group has read and write. The result: operator cannot read his own file, even though he belongs to the tramontana group. The kernel first checks whether he is the owner, sees that he is, applies --- and stops. It never gets as far as looking at the group permissions.

operator@srv-tramontana:~$ cat report.txt
cat: report.txt: Permission denied

You will rarely come across this in practice, but it explains the model better than any well-configured example: permissions are not added together, they are selected.

The exception is root, which skips the whole check. That is why sudo cat works on any file, and why permissions do not protect you against an administrator. The details of sudo are lesson 05-02.

Behind the names there are numbers. The system works with UID and GID:

operator@srv-tramontana:~$ id
uid=1001(operator) gid=1001(operator) groups=1001(operator),27(sudo)

operator@srv-tramontana:~$ id -u
1001
operator@srv-tramontana:~$ id -un
operator

The names are a convenience for people; the kernel only sees numbers. This matters when moving files between machines: if you use tar -p to copy a file owned by UID 1001 to another server where 1001 is somebody else, the file ends up belonging to that person.

  1. Reading the ls -l string character by character

operator@srv-tramontana:~$ ls -l /etc/tramontana/app.conf
-rw-r----- 1 root tramontana 512 Aug 18 08:47 /etc/tramontana/app.conf

The string -rw-r----- has 10 characters with a fixed structure:

 -    rw-    r--    ---
 │     │      │      │
 │     │      │      └── OTHERS: no permissions
 │     │      └───────── GROUP (tramontana): read only
 │     └──────────────── USER (root): read and write
 └────────────────────── TYPE: regular file

Character 1, the type. The seven you saw in Module 1:

Character Type
- Regular file
d Directory
l Symbolic link
c Character device
b Block device
s Socket
p Named pipe (FIFO)

Characters 2 to 10, three blocks of three. Within each block, always in the same order: r, w, x. A hyphen means that permission is absent.

Read it like this, out loud until it comes naturally:

-rw-r----- → "regular file; the owner reads and writes; the group only reads; everybody else nothing".

More examples from the server itself:

operator@srv-tramontana:~$ ls -l /opt/tramontana/app/executable /etc/passwd /tmp
-rwxr-xr-x 1 root root 50319872 Aug 18 08:30 /opt/tramontana/app/executable
-rw-r--r-- 1 root root     2891 Aug 18 09:00 /etc/passwd
drwxrwxrwt 9 root root     4096 Aug 18 16:10 /tmp
String Reading
-rwxr-xr-x File; the owner reads, writes and executes; group and others read and execute
-rw-r--r-- File; the owner reads and writes; everybody else only reads
drwxrwxrwt Directory; everybody can do everything... with a t at the end that changes the rules (section 9)

And the special case:

operator@srv-tramontana:~$ ls -l /usr/bin/python3
lrwxrwxrwx 1 root root 10 Aug  2 12:04 /usr/bin/python3 -> python3.12

lrwxrwxrwx: the permissions of a symbolic link are always these and mean nothing. As you saw in lesson 02-06, the ones that apply are the target's.

  1. The three permissions on files and on directories

Here is the point where most people get lost. The same three letters mean different things depending on the object.

Permission On a file On a directory
r (4) Read the content List the names it contains
w (2) Modify the content Create, delete and rename entries
x (1) Execute it as a program Traverse it: reach what is inside

Each of the three rows on the right-hand side deserves a demonstration, because all three are counter-intuitive.

x on a directory: traversing

x on a directory is sometimes called search permission. Without it you cannot reach anything inside, even if you know the exact name and even if the file inside grants you every permission.

operator@srv-tramontana:~$ mkdir -p test/inner
operator@srv-tramontana:~$ echo "content" > test/inner/data.txt
operator@srv-tramontana:~$ chmod 666 test/inner/data.txt
operator@srv-tramontana:~$ chmod 666 test          # rw- with no x

operator@srv-tramontana:~$ ls test
inner
operator@srv-tramontana:~$ cat test/inner/data.txt
cat: test/inner/data.txt: Permission denied

The file is rw-rw-rw-: anybody can read it. But to get to it, the kernel has to traverse test, and there is no x there. Access denied.

And the other way round, x without r:

operator@srv-tramontana:~$ chmod 111 test          # --x--x--x

operator@srv-tramontana:~$ ls test
ls: cannot open directory 'test': Permission denied

operator@srv-tramontana:~$ cat test/inner/data.txt
content

You cannot see what is inside, but you can reach what is there if you know the exact name. It is a "blind" directory.

This is not a curiosity: it is a commonly used security pattern. A 711 directory lets a web server serve /var/www/site/page.html without anybody being able to list the directory's contents and discover what other files are there. The same applies to /home, which on many systems is 711: users can enter their own home directory, but not list other people's.

A rule to memorise: to reach /a/b/c/file, you need x on /, on a, on b and on c, and then the appropriate permission on file. A single missing x at any point along the way blocks everything below it.

r without x on a directory

operator@srv-tramontana:~$ chmod 444 test          # r--r--r--
operator@srv-tramontana:~$ ls test
inner
operator@srv-tramontana:~$ ls -l test
ls: cannot access 'test/inner': Permission denied
total 0
d????????? ? ? ? ?            ? inner

You can read the names, because the names are in the directory itself. But ls -l needs to consult the inode of each entry, and for that you have to traverse the directory. Hence that output full of question marks: ls knows the name and nothing else.

r without x on a directory is practically useless. In practice, directories carry r and x together or neither of them.

w on a directory: the permission that surprises

This is the most important one to understand, because it contradicts intuition.

operator@srv-tramontana:~$ chmod 777 test
operator@srv-tramontana:~$ sudo touch test/from-root.txt
operator@srv-tramontana:~$ sudo chmod 600 test/from-root.txt
operator@srv-tramontana:~$ ls -l test/from-root.txt
-rw------- 1 root root 0 Aug 18 16:25 test/from-root.txt

operator@srv-tramontana:~$ cat test/from-root.txt
cat: test/from-root.txt: Permission denied

operator@srv-tramontana:~$ rm test/from-root.txt
operator@srv-tramontana:~$ ls test/
inner

You have deleted a file belonging to root that you could not even read.

The explanation is consistent with what you know from lesson 02-06: deleting a file is not an operation on the file, it is an operation on the directory containing it. rm removes an entry from the directory's list of names. And to modify that list all you need is w on the directory.

The file's permissions control its content. The directory's permissions control the list of names. They are two different things.

A summary of what w allows on a directory:

Operation What permission is needed?
Read a file r on the file, x along the path
Modify a file's content w on the file, x along the path
Create a file w + x on the directory
Delete a file w + x on the directory (the file's own are irrelevant)
Rename a file w + x on the directory
List the directory r on the directory
Enter it (cd) x on the directory

This behaviour is the whole reason for the sticky bit, which you will see in section 9, and it explains why /tmp, where everybody writes, is not chaos with people deleting each other's files.

  1. Octal notation and symbolic notation

The nine permission bits are expressed in two ways.

Octal

Each permission has a numeric value and they are added up per block:

Permission Value
r 4
w 2
x 1
- 0

One digit per block, three digits in total: user, group, others.

Octal Symbolic Sum
0 --- 0
1 --x 1
2 -w- 2
3 -wx 2+1
4 r-- 4
5 r-x 4+1
6 rw- 4+2
7 rwx 4+2+1

The ones that actually turn up on a system:

Octal String Typical use
644 rw-r--r-- A normal file, readable by everybody
600 rw------- A private file. SSH keys
640 rw-r----- Configuration with secrets, readable by a group
755 rwxr-xr-x An executable or a public directory
750 rwxr-x--- A directory for a specific group
700 rwx------ A private directory
711 rwx--x--x A directory that can be traversed but not listed
775 rwxrwxr-x A working directory shared by a group
777 rwxrwxrwx Everybody can do everything. Almost never correct

Translating in both directions

From symbolic to octal, block by block:

r w x   r - x   r - -
4+2+1   4+0+1   4+0+0
  7       5       4      ->  754

From octal to symbolic, breaking each digit down:

6 4 0
│ │ └── 0 = ---
│ └──── 4 = r--
└────── 6 = rw-       ->  rw-r-----

Practise with these until they come without thinking:

Octal Symbolic
644 rw-r--r--
755 rwxr-xr-x
600 rw-------
640 rw-r-----
664 rw-rw-r--
751 rwxr-x--x
400 r--------

And the other way round:

Symbolic Octal
rwxrwx--- 770
r-xr-x--- 550
rw-rw-rw- 666
--x--x--x 111
rwx------ 700

A mental shortcut: 7 is everything, 6 is read and write, 5 is read and execute, 4 is read only, 0 is nothing. With those five you cover 95% of real cases.

stat gives you both notations at once, which is very handy while you are learning:

operator@srv-tramontana:~$ stat -c '%a  %A  %n' /etc/tramontana/app.conf /opt/tramontana/app
640  -rw-r-----  /etc/tramontana/app.conf
755  drwxr-xr-x  /opt/tramontana/app

Symbolic

Symbolic notation is used to modify permissions without touching the rest. Its grammar is:

[who][operator][permissions]
Who Operator Permission
u user + add r read
g group - remove w write
o others = set exactly x execute
a all (ugo) X x only if it is already a directory or already had some x

That capital X is a little-known gem and you will see it in action in the next section.

  1. chmod in both notations

# Octal: sets all nine bits at once
operator@srv-tramontana:~$ chmod 640 data/houses.txt
operator@srv-tramontana:~$ ls -l data/houses.txt
-rw-r----- 1 operator operator 446 Aug 18 13:02 data/houses.txt

# Symbolic: changes only what you specify
operator@srv-tramontana:~$ chmod g+w data/houses.txt
operator@srv-tramontana:~$ ls -l data/houses.txt
-rw-rw---- 1 operator operator 446 Aug 18 13:02 data/houses.txt

operator@srv-tramontana:~$ chmod o=r data/houses.txt
operator@srv-tramontana:~$ ls -l data/houses.txt
-rw-rw-r-- 1 operator operator 446 Aug 18 13:02 data/houses.txt

operator@srv-tramontana:~$ chmod a-w data/houses.txt
operator@srv-tramontana:~$ ls -l data/houses.txt
-r--r--r-- 1 operator operator 446 Aug 18 13:02 data/houses.txt

Several rules can be combined with commas:

operator@srv-tramontana:~$ chmod u=rw,g=r,o= data/houses.txt
operator@srv-tramontana:~$ ls -l data/houses.txt
-rw-r----- 1 operator operator 446 Aug 18 13:02 data/houses.txt

When to use each notation:

Situation Notation
You know exactly what permissions you want Octal: chmod 640
You want to make one specific change without touching the rest Symbolic: chmod g+w
Scripts and documentation Octal: it is explicit and unambiguous
Recursive over mixed trees Symbolic with X

-v and --changes show what it does, useful for verifying:

operator@srv-tramontana:~$ chmod -v 644 data/houses.txt
mode of 'data/houses.txt' changed from 0640 (rw-r-----) to 0644 (rw-r--r--)

And --reference copies the permissions of another file:

operator@srv-tramontana:~$ chmod --reference=data/bookings.csv data/houses.txt

-R and the recursive problem

operator@srv-tramontana:~$ chmod -R 644 /home/operator/work

It looks reasonable and it has just broken every directory in the tree. By taking away their x, they can no longer be traversed:

operator@srv-tramontana:~$ ls work/2026
ls: cannot open directory 'work/2026': Permission denied

The problem is that files and directories need different permissions: files normally must not be executable, and directories always need x.

The correct solution is the capital X:

operator@srv-tramontana:~$ chmod -R u=rwX,g=rX,o= /home/operator/work

X applies x only to directories and to files that already had some execute bit. Data files do not become executable; directories keep their ability to be traversed. It is exactly what you want in 100% of recursive cases.

The classic alternative, if you prefer to separate them explicitly, uses find (lesson 03-03):

operator@srv-tramontana:~$ find /home/operator/work -type d -exec chmod 750 {} +
operator@srv-tramontana:~$ find /home/operator/work -type f -exec chmod 640 {} +

Why chmod -R 777 is never the answer

It appears in hundreds of forum answers as the remedy for "permission denied". It deserves a serious explanation of why it is wrong, because simply saying "it is insecure" convinces nobody who is in a hurry.

1. It does not fix the problem, it masks it. If something gave "permission denied", there was a reason: the wrong user, a badly assigned group, a missing x on a directory along the path. 777 makes the symptom disappear without you knowing what the cause was. It will come back somewhere else.

2. Any user on the system can modify it. And a server has more users than you think: service accounts such as www-data, nobody, postgres. If an attacker compromises the web service — which has no privileges — and your application is 777, they can rewrite the whole thing. You have handed them code execution as the user that runs the application.

3. With -R over directories, it is worse. 777 directories let anybody delete and replace files that are not theirs, for the reason you saw in section 3. An attacker can swap a binary for one of their own.

4. It marks files as executable. A .conf or a .csv with the execute permission is a red flag in any audit, and on some web servers an unfortunate configuration can cause an executable file to be run instead of served.

5. It is practically irreversible. After a chmod -R 777 /var, there is no way to know what permissions each file had. In some cases you have to reinstall packages to get them back.

The correct diagnosis when something gives "permission denied":

# 1. Who am I and which groups do I belong to?
operator@srv-tramontana:~$ id

# 2. What are the file's permissions and who owns it?
operator@srv-tramontana:~$ ls -l /path/to/file

# 3. And every directory along the way? (this is the one people forget)
operator@srv-tramontana:~$ namei -l /path/to/file
f: /path/to/file
drwxr-xr-x root     root     /
drwxr-x--- root     root     path
drwxr-xr-x root     root     to
-rw-r--r-- root     root     file

namei -l shows the permissions of every component of the path, and it is the tool that solves the most frequent case: the file is fine, but an intermediate directory will not let you through. In this example, path is drwxr-x--- and belongs to root:root: there is the blockage.

  1. chown and chgrp

chown changes the owner, chgrp the group:

operator@srv-tramontana:~$ sudo chown root /etc/tramontana/app.conf
operator@srv-tramontana:~$ sudo chgrp tramontana /etc/tramontana/app.conf

# Both at once, with a colon
operator@srv-tramontana:~$ sudo chown root:tramontana /etc/tramontana/app.conf

operator@srv-tramontana:~$ ls -l /etc/tramontana/app.conf
-rw-r----- 1 root tramontana 512 Aug 18 08:47 /etc/tramontana/app.conf

Forms of chown:

Syntax What it changes
chown user file Only the owner
chown user:group file Both
chown :group file Only the group (like chgrp)
chown user: file The owner, and the group becomes that user's primary group

Options:

Option What it does
-R Recursive
-v / --changes Reports the changes
--reference=file Copies the owner and group of another file
-h Acts on the symbolic link, not on its target
--from=usr:grp Changes only those that already had that owner

Important: chown requires root. A normal user cannot give a file away to somebody else:

operator@srv-tramontana:~$ chown student data/houses.txt
chown: changing ownership of 'data/houses.txt': Operation not permitted

The reason is concrete: if you could, you would be able to get around disk quotas by creating huge files and assigning them to somebody else. And you could create a SUID file and give it to root, which would be an immediate security hole.

chgrp, on the other hand, a normal user can do, but only if they are a member of the target group:

operator@srv-tramontana:~$ chgrp sudo data/houses.txt
operator@srv-tramontana:~$ ls -l data/houses.txt
-rw-r--r-- 1 operator sudo 446 Aug 18 13:02 data/houses.txt

operator@srv-tramontana:~$ chgrp adm data/houses.txt
chgrp: changing group of 'data/houses.txt': Operation not permitted

operator is a member of sudo, so it works; he is not a member of adm, so it does not.

--reference is very handy for replicating the ownership of a model file:

operator@srv-tramontana:~$ sudo chown --reference=/etc/tramontana/app.conf /etc/tramontana/new.conf

And -h matters with links:

operator@srv-tramontana:~$ sudo chown root:root ~/app-production      # changes the TARGET
operator@srv-tramontana:~$ sudo chown -h root:root ~/app-production   # changes the LINK

Since the owner of a symlink is irrelevant for access control, -h is rarely used; but it is worth knowing that without -h, chown on a link touches the target, which can have unexpected consequences when walking a tree with -R.

  1. Groups: why teamwork is not solved with "others"

Every user has a primary group (the one their new files get) and can belong to several secondary groups.

operator@srv-tramontana:~$ id
uid=1001(operator) gid=1001(operator) groups=1001(operator),27(sudo)

operator@srv-tramontana:~$ groups
operator sudo

operator@srv-tramontana:~$ id -Gn operator
operator sudo

On Ubuntu, every user has by default a group of their own with the same name (the UPG scheme, User Private Group). The reason is that it allows a more permissive umask to be used safely: since the user's group contains only them, granting group permissions exposes nothing.

Querying the system's groups:

operator@srv-tramontana:~$ getent group sudo
sudo:x:27:operator

operator@srv-tramontana:~$ getent group | tail -n 4
operator:x:1001:
tramontana:x:1002:operator
adm:x:4:syslog

The scenario

Marta sets out the real scenario: Luis Ferrer needs to be able to read the application's logs and write to the deployments directory; in the future another person will join with the same needs. How is this solved?

The wrong option: granting permissions to "others".

sudo chmod o+rx /var/log/tramontana
sudo chmod o+rwx /srv/tramontana/backups

What you have actually done: you have granted those permissions to every user on the system. Not just to Luis, but to www-data, to nobody, to any service account and to any account created in the future. If an attacker compromises an unprivileged service, they have access to your logs (which contain user names and usage patterns) and can write to your backups directory.

And in terms of managing it: if tomorrow you want to remove access from Luis alone, you cannot. "Others" does not distinguish between people.

The right option: a shared group.

# (This will be done formally in lesson 05-01)
sudo groupadd tramontana
sudo usermod -aG tramontana operator
sudo usermod -aG tramontana luis

sudo chgrp -R tramontana /var/log/tramontana /srv/tramontana/backups
sudo chmod -R g+rX /var/log/tramontana
sudo chmod -R g+rwX /srv/tramontana/backups
sudo chmod o= /var/log/tramontana /srv/tramontana/backups

The concrete advantages:

Criterion Permissions for "others" Shared group
Scope Everybody, service accounts included Only the members
Granting access to somebody new They already have it (badly) usermod -aG, one command
Removing access from one person Impossible gpasswd -d, one command
Auditing who has access Impossible to answer getent group tramontana
Compromised service accounts They have access They do not
Scales to more people No Yes

The rule is absolute: on a multi-user system, shared access is solved with groups. The "others" permission is for what genuinely must be public, and in most correct cases it should be 0.

One detail that surprises people and that you have to know: a process's groups are determined when the session starts. If somebody adds you to a group while you have a session open, your shell does not see it:

operator@srv-tramontana:~$ sudo usermod -aG tramontana operator
operator@srv-tramontana:~$ groups
operator sudo                    # <- tramontana does not appear
operator@srv-tramontana:~$ id -nG operator
operator sudo tramontana         # <- but the system already knows

You have to log out and log back in (or use newgrp tramontana for the current session). It is a classic source of "I gave them permissions and it still does not work".

Watch out with usermod -aG too: the -a is mandatory. Without it, usermod -G tramontana operator replaces all the secondary groups with that one, which would take operator out of the sudo group and leave him unable to administer the system. It is a mistake you make once in a lifetime.

  1. The umask

When you create a file, you do not choose its permissions: the system sets them. The umask is the mask that decides which ones are taken away.

The starting values are fixed by the kernel:

Object Base permissions Why
File 666 (rw-rw-rw-) Never executable by default: creating a file must not create a program
Directory 777 (rwxrwxrwx) It needs x to be traversable

The umask is subtracted from those values. More precisely, a bit operation is applied that removes the permissions marked in the mask.

operator@srv-tramontana:~$ umask
0022
operator@srv-tramontana:~$ umask -S
u=rwx,g=rx,o=rx

The calculation with umask 022:

Files:        666  -  022  =  644   (rw-r--r--)
Directories:  777  -  022  =  755   (rwxr-xr-x)

Checking it:

operator@srv-tramontana:~$ umask 022
operator@srv-tramontana:~$ touch test-022.txt && mkdir dir-022
operator@srv-tramontana:~$ ls -ld test-022.txt dir-022
drwxr-xr-x 2 operator operator 4096 Aug 18 17:02 dir-022
-rw-r--r-- 1 operator operator    0 Aug 18 17:02 test-022.txt

The calculation with umask 027:

Files:        666  -  027  =  640   (rw-r-----)
Directories:  777  -  027  =  750   (rwxr-x---)
operator@srv-tramontana:~$ umask 027
operator@srv-tramontana:~$ touch test-027.txt && mkdir dir-027
operator@srv-tramontana:~$ ls -ld test-027.txt dir-027
drwxr-x--- 2 operator operator 4096 Aug 18 17:04 dir-027
-rw-r----- 1 operator operator    0 Aug 18 17:04 test-027.txt

A comparison of the usual masks:

umask Files Directories Effect Where it is used
022 644 755 Everybody reads, only the owner writes The default on most distributions
002 664 775 The group writes too Ubuntu for normal users (with UPG)
027 640 750 "Others" sees nothing Servers with sensitive data
077 600 700 Only the owner Maximum privacy; root accounts
007 660 770 Full group access, nothing for others Team directories

An important technical nuance: the umask removes bits, it never adds them. That is why saying "it is subtracted" is a useful simplification but not an exact one. With umask 022 and base permissions 666, the result is 644. But if a program explicitly asks to create a file with permissions 600, the umask will not raise it to 644: the result will be 600. The umask can only be more restrictive than what is requested.

Another nuance: an odd digit in the umask has no visible effect on files, because files are born without x anyway. umask 023 and umask 022 produce identical files, but different directories (754 against 755).

Where the umask is defined:

# For the current session only
operator@srv-tramontana:~$ umask 027

# Permanently for your user
operator@srv-tramontana:~$ echo "umask 027" >> ~/.bashrc

# For the whole system
operator@srv-tramontana:~$ grep -r "UMASK" /etc/login.defs
UMASK		022

systemd services have their own UMask= directive in their unit file, and they do not inherit your shell's. You will see this in lesson 05-05.

The recommendation for srv-tramontana: umask 027 for the administrative accounts. With guest data on the system, the default of letting any user read new files is too permissive. With 027, everything you create is born with no access for "others", which is the principle of least privilege applied from the outset.

  1. Special bits: SUID, SGID and sticky

Besides the nine bits, there are three more. Here you are only going to learn to recognise them when you see them and what they do in one sentence. Studying them in depth, with their security implications and their correct use, is lesson 05-02.

Bit Octal Seen in ls -l as What it does
SUID 4000 s in the user's x The program runs with the privileges of its owner, not of whoever launches it
SGID 2000 s in the group's x On an executable: it runs with the file's group. On a directory: new files inherit the directory's group
Sticky 1000 t in the others' x On a directory: only the owner of a file can delete it

SUID: /usr/bin/passwd

operator@srv-tramontana:~$ ls -l /usr/bin/passwd
-rwsr-xr-x 1 root root 68208 Mar 23 13:47 /usr/bin/passwd

That s where the user's x should be is the SUID bit.

The problem it solves: passwords are stored encrypted in /etc/shadow, which is -rw-r----- owned by root:shadow. A normal user cannot even read it. But they must be able to change their own password, and that means writing to that file.

With SUID, when operator runs passwd, the process runs as root — the owner of the program — and can write to /etc/shadow. The program is written so that it only allows you to change your own password.

This is also why SUID programs are the attackers' favourite target: a flaw in a root-owned SUID binary is a direct privilege escalation. That is why there are few of them, they are audited, and finding an unexpected one on a server is cause for alarm.

SGID on directories: group inheritance

operator@srv-tramontana:~$ ls -ld /srv/tramontana/shared
drwxrws--- 2 root tramontana 4096 Aug 18 17:20 /srv/tramontana/shared

The s in the position of the group's x. On a directory, SGID makes everything created inside inherit the directory's group, instead of the primary group of whoever creates it.

Why it matters for Tramontana: without SGID, if Luis creates a file in the shared directory, the group will be luis and operator will not be able to reach it even though both are in tramontana. With SGID, the file is born with group tramontana and the team can work. It is the piece that makes a shared directory actually work.

The sticky bit: /tmp

operator@srv-tramontana:~$ ls -ld /tmp
drwxrwxrwt 9 root root 4096 Aug 18 17:22 /tmp

The t at the end. /tmp is 777: everybody writes. From what you learned in section 3, that would mean anybody can delete anybody else's temporary files, including those of system processes.

The sticky bit corrects that: in a directory with this bit, only the file's owner (or the directory's, or root) can delete or rename it. You can create your own files, but not touch other people's.

How to tell upper case from lower case

A subtlety that turns up in certification exams and is worth recognising:

What you see What it means
lower-case s The special bit and the corresponding x are both set
upper-case S The special bit is set but the x is missing
lower-case t Sticky and x for others
upper-case T Sticky without x for others

An upper-case S or T usually indicates a configuration mistake: you have put a special bit on something that cannot be executed or traversed, so it will do nothing useful.

You can check all three with stat, where they appear as a fourth digit:

operator@srv-tramontana:~$ stat -c '%a  %A  %n' /usr/bin/passwd /tmp
4755  -rwsr-xr-x  /usr/bin/passwd
1777  drwxrwxrwt  /tmp

4755 and 1777: the first digit is the special bits.

Let me repeat the boundary: how and when to use these bits, their risks, how to audit a system's SUID binaries and how they relate to sudo is the material of lesson 05-02. Here you only need to recognise them in an ls -l and not be frightened.

  1. Case study: the permissions of Tramontana Bookings

Marta asks you for a report with the permission design of the deployment and its justification. Let us go path by path.

The starting point is the principle of least privilege: every account has exactly the permissions it needs for its role, and not one more.

The actors in the system:

Actor What it is What it needs
root Administration Everything, by definition
operator Administrative account (in the sudo group) To administer via sudo
tramontana The application's group (created in 05-01) Access to the deployment
svc-tramontana The service account that runs the app The minimum needed to work
Others www-data, nobody, future accounts Nothing

/opt/tramontana/app — the code

operator@srv-tramontana:~$ sudo chown -R root:tramontana /opt/tramontana/releases
operator@srv-tramontana:~$ sudo chmod -R u=rwX,g=rX,o= /opt/tramontana/releases
operator@srv-tramontana:~$ sudo chmod 755 /opt/tramontana/releases/3.2.1/executable

operator@srv-tramontana:~$ ls -ld /opt/tramontana/releases/3.2.1
drwxr-x--- 4 root tramontana 4096 Aug 18 08:30 /opt/tramontana/releases/3.2.1
operator@srv-tramontana:~$ ls -l /opt/tramontana/releases/3.2.1/executable
-rwxr-xr-x 1 root tramontana 50319872 Aug 18 08:30 executable

Directories 750, files 640, the executable 755.

The justification:

  • Owner root: the service must not be able to modify its own code. If an attacker compromises the application by exploiting a flaw, they cannot rewrite the executable to persist on the system. This is the most important decision in the whole design.
  • Group tramontana with r-x: the team can read the code and traverse the directories to diagnose problems.
  • Nothing for others: no unrelated service account has any business seeing the application's code.
  • u=rwX,g=rX in the recursive command: directories get x and data files do not, thanks to the capital X.

/etc/tramontana/app.conf — the configuration with credentials

operator@srv-tramontana:~$ sudo chown root:tramontana /etc/tramontana/app.conf
operator@srv-tramontana:~$ sudo chmod 640 /etc/tramontana/app.conf
operator@srv-tramontana:~$ sudo chmod 750 /etc/tramontana

operator@srv-tramontana:~$ ls -ld /etc/tramontana /etc/tramontana/app.conf
drwxr-x--- 2 root tramontana 4096 Aug 18 08:30 /etc/tramontana
-rw-r----- 1 root tramontana  512 Aug 18 08:47 /etc/tramontana/app.conf

640, and the directory 750.

The justification:

  • It contains the database password. With the usual 644 of /etc, any user on the system could read it. That includes www-data and any compromised account. It is the difference between a contained incident and a leak of the entire database.
  • The service only needs to read, never to write. No w for the group.
  • The directory is 750 too: there is no point protecting the file if the directory allows listing and traversal. Remember section 3: you need both.
  • No .bak copy may be left with looser permissions. This is a real and frequent failure: the configuration is copied to app.conf.bak and the copy is born with the umask of the moment, perhaps 644. You protect the original and leave the secret exposed in the copy. Always check the permissions of your backups.
operator@srv-tramontana:~$ sudo cp -p /etc/tramontana/app.conf /etc/tramontana/app.conf.bak-$(date +%F)
operator@srv-tramontana:~$ ls -l /etc/tramontana/
-rw-r----- 1 root tramontana 512 Aug 18 08:47 app.conf
-rw-r----- 1 root tramontana 512 Aug 18 08:47 app.conf.bak-2026-08-18

cp -p preserves the permissions, so the copy is born just as protected. Without -p it would have been born with the umask, which is exactly what has to be avoided here.

/var/log/tramontana/ — the logs

operator@srv-tramontana:~$ sudo chown -R svc-tramontana:adm /var/log/tramontana
operator@srv-tramontana:~$ sudo chmod 750 /var/log/tramontana
operator@srv-tramontana:~$ sudo chmod 640 /var/log/tramontana/*.log

operator@srv-tramontana:~$ sudo ls -ld /var/log/tramontana
drwxr-x--- 2 svc-tramontana adm 4096 Aug 18 09:00 /var/log/tramontana
operator@srv-tramontana:~$ sudo ls -l /var/log/tramontana
-rw-r----- 1 svc-tramontana adm 18432 Aug 18 09:14 access.log
-rw-r----- 1 svc-tramontana adm  6348 Aug 18 09:02 errors.log

Directory 750, files 640.

The justification:

  • The owner is the service account, because it is the one that has to write to the logs. It is the only path in the deployment where the service needs write permission.
  • The adm group is the Debian and Ubuntu convention for "whoever can read the system logs". It fits with the rest of /var/log.
  • Nothing for others, and this matters: access.log contains user names, IP addresses and activity patterns. It is information that helps an attacker prepare their next step, and it can also constitute personal data.
  • The directory needs w for the service because logrotate and the application itself will create new files there.

/srv/tramontana/backups — the backups

operator@srv-tramontana:~$ sudo chown -R root:tramontana /srv/tramontana/backups
operator@srv-tramontana:~$ sudo chmod 2770 /srv/tramontana/backups
operator@srv-tramontana:~$ sudo chmod 640 /srv/tramontana/backups/*.tar.gz

operator@srv-tramontana:~$ ls -ld /srv/tramontana/backups
drwxrws--- 3 root tramontana 4096 Aug 18 11:20 /srv/tramontana/backups

2770: 770 plus the SGID bit.

The justification:

  • The group needs to write, because the operators generate and rotate copies.
  • The SGID (2) makes everything created inside inherit the tramontana group. Without it, a copy created by Luis would have group luis and the rest of the team could not manage it. It is the use case from section 9, applied.
  • Nothing for others. The backups contain everything: the code, the configuration with credentials and, depending on what is being backed up, guest data. A readable backups directory is a way of granting access to everything else while bypassing the permissions you have just designed.

Summary table for the report

Path Owner:Group Octal One-line justification
/opt/tramontana/releases/*/ root:tramontana 750 The service cannot modify its own code
/opt/tramontana/releases/*/executable root:tramontana 755 Executable by the service, not writable
/opt/tramontana/app (link) root:tramontana 777 (irrelevant) A symlink's permissions do not count
/etc/tramontana/ root:tramontana 750 Protect the directory, not just the file
/etc/tramontana/app.conf root:tramontana 640 It contains credentials
/var/log/tramontana/ svc-tramontana:adm 750 The service writes; adm reads
/var/log/tramontana/*.log svc-tramontana:adm 640 They contain user activity data
/srv/tramontana/backups root:tramontana 2770 The group writes; SGID for inheritance
/home/operator/scripts operator:operator 750 The operator's personal scripts

A final verification of the design:

operator@srv-tramontana:~$ sudo stat -c '%a  %U:%G  %n' \
    /opt/tramontana/releases/3.2.1 \
    /etc/tramontana/app.conf \
    /var/log/tramontana \
    /srv/tramontana/backups
750  root:tramontana  /opt/tramontana/releases/3.2.1
640  root:tramontana  /etc/tramontana/app.conf
750  svc-tramontana:adm  /var/log/tramontana
2770  root:tramontana  /srv/tramontana/backups

A necessary warning

This design is a teaching exercise on fictional data. In a real environment, bookings.csv and the access logs contain guests' personal data: names, stay dates and behavioural patterns. That brings them under the GDPR and Spanish data protection legislation.

In a real deployment:

  • The permission design must be reviewed by the information security officer or the data protection officer before going into production, not by the administrator on their own initiative.
  • It may be mandatory to encrypt the data at rest, not merely to restrict access through permissions.
  • Records of access to personal data may have to be auditable and retained for a set period.
  • Unix permissions are the first level of control, not the only one: in demanding environments they are combined with ACLs (lesson 05-02) and with mandatory access control through AppArmor or SELinux (lesson 06-06).

The permissions you have designed are necessary and correct. They are not sufficient on their own when personal data is involved, and presenting them as such would be a professional mistake.

Common Mistakes and Tips

chmod -R 777 to "fix" a permission denied. It masks the cause, exposes everything to any account on the system and is almost irreversible. Diagnose with id, ls -l and namei -l.

chmod -R 644 over a tree. It leaves directories without x and breaks every one of them. Use chmod -R u=rwX,g=rX,o=.

Forgetting the x on the directories along the path. The file is fine and it still does not work. namei -l /full/path resolves it in a second.

Believing that permissions add up. Only the first matching category is applied. An owner with no permissions has no access even if the group has them.

Protecting a file and leaving the .bak copy wide open. Use cp -p and check with ls -l afterwards.

Granting access to a colleague with o+r. You are granting it to the whole system. Groups, always.

usermod -G without -a. It replaces all the secondary groups. It can take you out of the sudo group and leave you unable to administer.

Not understanding why the new group "does not work". Groups are applied when the session starts. Log out and log back in.

Tip: adopt umask 027 on servers. Everything you create is born already closed to "others". It is least privilege from the outset.

Tip: namei -l is the most underrated tool in this lesson. Memorise it.

Tip: stat -c '%a %A %U:%G %n' is the verification format. It gives you octal, symbolic and ownership on one line, perfect for reports.

Tip: before any chmod -R or chown -R, save the current state. With getfacl -R directory > permissions.bak you can go back. It is the "back up before editing" convention applied to permissions.

Exercises

Exercise 1: translating and reading

Without running anything:

  1. Translate to octal: rwxr-x---, rw-rw-r--, r--------, rwsr-xr-x.
  2. Translate to symbolic: 750, 644, 2775, 1777.
  3. Explain exactly what the user luis (a member of tramontana, not of adm) can do with each of these, and why:
drwxr-x---  3 root  tramontana  4096 Aug 18 09:00 /srv/data
-rw-r-----  1 root  tramontana   512 Aug 18 09:00 /srv/data/config.txt
-rw-r-----  1 root  adm         2048 Aug 18 09:00 /srv/data/record.log
  1. Could luis delete record.log? Justify your answer.

Exercise 2: diagnosing a permission denied

The application will not start. The service log shows:

FATAL: cannot read /etc/tramontana/app.conf: Permission denied

The service runs as svc-tramontana. Check the following and give a reasoned diagnosis:

$ id svc-tramontana
uid=997(svc-tramontana) gid=997(svc-tramontana) groups=997(svc-tramontana)

$ namei -l /etc/tramontana/app.conf
f: /etc/tramontana/app.conf
drwxr-xr-x root root       /
drwxr-x--- root tramontana etc/tramontana
-rw-r----- root tramontana app.conf

Identify the exact cause, propose the fix with the specific command, and explain why it must not be solved with chmod 644.

Exercise 3: a permissions report for Marta

Marta has had a query from the client and needs a report. Prepare, for srv-tramontana:

  1. A table with the current permissions of the five Tramontana paths, in octal and with the owner.
  2. The identification of any path whose permissions you consider incorrect, with your proposed fix.
  3. A paragraph, written for a non-technical reader, explaining what this design protects and what it does not protect.

Solutions

Solution 1

1. To octal:

Symbolic Calculation Octal
rwxr-x--- (4+2+1)(4+0+1)(0) 750
rw-rw-r-- (4+2)(4+2)(4) 664
r-------- (4)(0)(0) 400
rwsr-xr-x SUID + (4+2+1)(4+0+1)(4+0+1) 4755

In the last one, the s in the position of the user's x indicates SUID, which contributes the 4 as a fourth digit. The nine normal bits are 755.

2. To symbolic:

Octal Symbolic
750 rwxr-x---
644 rw-r--r--
2775 rwxrwsr-x (SGID)
1777 rwxrwxrwt (sticky)

In 2775, the 2 is SGID and shows up as an s in the group's x. In 1777, the 1 is the sticky bit and appears as a t in the position of the others' x. Both are lower case because the corresponding x is present as well.

3. What luis can do:

  • /srv/data (750, root:tramontana): luis is not root, so the user permissions do not apply to him. He is a member of tramontana, so the group ones apply: r-x. He can list the directory (ls) and traverse it (cd, reach what is inside). He cannot create, delete or rename anything inside it, because he lacks w.

  • config.txt (640, root:tramontana): the group permissions apply, r--. He can read it with cat. He cannot modify it. And he can get to it because he has x on the directory, a necessary condition that is checked first.

  • record.log (640, root:adm): luis is neither root nor a member of adm, so the others permissions apply to him: ---. He cannot read it. The fact that he can list the directory and therefore see that the file exists gives him no access whatsoever to its content.

4. Can luis delete record.log?

No, but not for the reason it appears at first sight.

Deleting a file does not depend on the file's permissions: it depends on having w and x on the directory containing it. If /srv/data were 770, luis would indeed be able to delete record.log even though he cannot read it, because he would be modifying the directory's list of names, not the file.

In this specific case he cannot, because /srv/data is 750: the group has r-x, with no w. It is the absence of w on the directory that stops him, not the log's permissions.

This distinction is what makes the sticky bit necessary in directories like /tmp, where everybody has w.

Solution 2

The exact cause. namei -l shows the whole chain and the blockage is on the first relevant line:

drwxr-x--- root tramontana etc/tramontana

The directory /etc/tramontana is 750, owned by root:tramontana. The service account:

uid=997(svc-tramontana) gid=997(svc-tramontana) groups=997(svc-tramontana)

svc-tramontana does not belong to the tramontana group. It is only in its own group. When evaluating access to /etc/tramontana:

  • Is it the owner, root? No.
  • Does it belong to the tramontana group? No.
  • The others permissions apply: ---.

Without x on /etc/tramontana, it cannot traverse it, so it never even gets as far as evaluating the permissions of app.conf. And even if it did, the file would also give it --- as others.

The important detail: the configuration file is perfectly configured. The problem is the group membership of the service account. Without namei -l it is easy to stare at the file's 640 and miss the real blockage, which is one level higher up.

The fix:

operator@srv-tramontana:~$ sudo usermod -aG tramontana svc-tramontana

operator@srv-tramontana:~$ id svc-tramontana
uid=997(svc-tramontana) gid=997(svc-tramontana) groups=997(svc-tramontana),1002(tramontana)

# The service does not pick up the new group until it restarts
operator@srv-tramontana:~$ sudo systemctl restart tramontana

operator@srv-tramontana:~$ sudo -u svc-tramontana cat /etc/tramontana/app.conf | head -n 1
# Tramontana Bookings configuration

The -a in usermod -aG is essential: without it, it would replace the secondary groups instead of adding to them.

And the systemctl restart is necessary because, as you saw in section 7, a process's groups are fixed when it starts. Adding the account to the group does not affect a process that is already running.

Why not chmod 644:

chmod 644 /etc/tramontana/app.conf would indeed make the service start. And it would be a serious security failure.

The file contains the database password. With 644, any user on the system can read it: www-data, nobody, any service account belonging to another application, any user created in the future and any compromised process that manages to run under an unprivileged account.

The concrete scenario: an attacker finds a minor vulnerability in another service on the same server and manages to run commands as www-data. With 640 and the group set correctly, they can do nothing with Tramontana's credentials. With 644, they read the database password, connect directly and walk away with all the guest data. A contained incident becomes a data breach.

Besides, 644 does not fix the cause. The service account is still not in the group to which all the application's other resources belong. The same error will come back with the logs, with the backups directory and with every new resource, and it will be patched each time by opening up permissions, until you end up with a completely exposed deployment.

The general principle: when something gives "permission denied", the right question is not "how do I open this up", but "who should have access and how do I give it to them alone". The answer is almost always group membership, not a more permissive chmod.

Solution 3

1. The current state:

operator@srv-tramontana:~$ sudo stat -c '%a  %A  %U:%G  %n' \
    /opt/tramontana/app \
    /etc/tramontana/app.conf \
    /var/log/tramontana \
    /srv/tramontana/backups \
    /home/operator/scripts
777  lrwxrwxrwx  root:root  /opt/tramontana/app
640  -rw-r-----  root:tramontana  /etc/tramontana/app.conf
750  drwxr-x---  svc-tramontana:adm  /var/log/tramontana
770  drwxrwx---  root:tramontana  /srv/tramontana/backups
775  drwxrwxr-x  operator:operator  /home/operator/scripts
Path Octal Owner:Group Assessment
/opt/tramontana/app 777 root:root Correct: it is a link, its permissions do not apply
/etc/tramontana/app.conf 640 root:tramontana Correct
/var/log/tramontana 750 svc-tramontana:adm Correct
/srv/tramontana/backups 770 root:tramontana Could be better: SGID missing
/home/operator/scripts 775 operator:operator Incorrect: readable and traversable by everybody

2. Proposed fixes:

# /srv/tramontana/backups: add SGID so that the copies inherit the group
operator@srv-tramontana:~$ sudo chmod 2770 /srv/tramontana/backups
operator@srv-tramontana:~$ ls -ld /srv/tramontana/backups
drwxrws--- 3 root tramontana 4096 Aug 18 11:20 /srv/tramontana/backups

Without SGID, a copy created by another member of the team would be born with their personal group and the rest could not manage it. With SGID, everything created inside inherits tramontana. It is the requirement for a shared directory to work as a team directory in practice.

# /home/operator/scripts: close off access for others
operator@srv-tramontana:~$ chmod 750 /home/operator/scripts
operator@srv-tramontana:~$ chmod -R u=rwX,go= /home/operator/scripts
operator@srv-tramontana:~$ chmod 750 /home/operator/scripts/*.sh

With 775, any user on the system could read the administration scripts. That is not trivial: the scripts reveal internal paths, service names, backup procedures and sometimes — through carelessness — credentials. It is free reconnaissance material for an attacker.

An additional check that is always worth making:

# Look for copies of the configuration with loose permissions
operator@srv-tramontana:~$ sudo ls -l /etc/tramontana/
-rw-r----- 1 root tramontana 512 Aug 18 08:47 app.conf
-rw-r----- 1 root tramontana 512 Aug 18 08:47 app.conf.bak-2026-08-18

Both at 640. Correct, thanks to the cp -p.

3. The report for Marta:

Access permission design — Tramontana Bookings

The server controls who can see and modify each file through a permission system that gives every file an owner, a working group and specific rights for each of them. We have reviewed and adjusted the five elements of the deployment.

What the current design protects. The application code belongs to the administrator and the application can run it but not modify it: if somebody managed to exploit a flaw in the program, they could not alter the program itself in order to install themselves permanently. The configuration file, which contains the database password, is readable only by the administrator and by the Tramontana team's accounts; no other account on the server can see it, not even those of other services that may be installed. The activity logs, which include customer usage data, are equally restricted. The backups are accessible only to the team, and we have added a setting so that any copy generated by any member of the team is automatically accessible to the rest, preventing a copy from becoming unusable because of a permissions problem. Finally, we have closed off access to the administration scripts, which until now could be read by any account on the server and which describe the internal workings of the system.

What it does not protect. This mechanism controls access from inside the server and between different accounts. It does not protect against three things: anybody with administrator access can read everything, because that is the nature of the role; the data is stored unencrypted, so anybody obtaining physical access to the disk or to a backup could read it without needing credentials; and it does not prevent somebody with legitimate access from copying information off the server.

Recommendation. Given that the system stores guest names, stay dates and amounts — personal data subject to the GDPR — this design should be reviewed and validated by the data protection officer before being considered final. It is likely that the regulations will require, in addition to the access control we already have, encryption of the stored data and an auditable record of access. I suggest we discuss it at the next meeting and plan those two measures as the next phase.

That report does what is expected of a professional: it describes what has been done in comprehensible language, it is honest about the limits of the solution, and it escalates to the right person a decision that is not technical but a matter of regulatory compliance.

Conclusion

You close Module 2 with the missing piece: the one that turns a server that works into a server that is also secure.

  • The Unix model gives every file one owner and one group, and divides the rest of the world into three categories. Permissions are not added together: the first matching category is selected.
  • You can read -rw-r----- character by character, and you recognise the seven file types by their first letter.
  • You understand that r, w and x mean different things on files and on directories: that x means traversing, that r without x is almost useless, and that w on a directory lets you delete files that are not yours, because deleting is an operation on the list of names, not on the file.
  • You translate between octal and symbolic in both directions, and you know the values that really turn up: 644, 640, 600, 755, 750, 700, 711.
  • You use chmod in both notations, and you know that the correct recursive form is u=rwX,g=rX,o= with a capital X, never -R 644 and never -R 777.
  • You know why chmod -R 777 is not a solution and what the correct diagnosis is: id, ls -l and above all namei -l.
  • You handle chown and chgrp, you know that chown requires root and why, and you know about --reference and -h.
  • You are clear that teamwork is solved with a shared group and not with permissions for "others", that usermod -aG needs the -a, and that groups are applied when the session starts.
  • You calculate the umask and its effect on the bases 666 and 777, and you know that 027 is the one to recommend on a server with sensitive data.
  • You recognise SUID, SGID and sticky in an ls -l, you know what each one does in one sentence, and you know that studying them in depth is lesson 05-02.
  • And you have designed and justified the complete permissions of the Tramontana Bookings deployment, including the most professional part of all: saying clearly what the design protects and what it does not, and escalating to the right person what goes beyond your remit.

Take stock of the whole module. You started with a blinking prompt and no idea how to type a command fluently. Now you handle the command line with shortcuts and history, you resolve questions with the system's own documentation without depending on a search engine, you walk through and characterise an unknown server with five commands, you create, copy, move, package and verify files with reproducible procedures, you read and edit content with less, nano and just enough vim not to get locked in, you understand what an inode is and you have designed a deployment pattern based on links, and you can decide who gets access to what and why. That is no longer "knowing commands": it is having judgement.

In Module 3: Advanced Command-Line Skills all of this multiplies. You will learn to customise your environment with variables, aliases and persistent history, to describe sets of files with wildcards and patterns with regular expressions, to find anything anywhere with find, locate and grep, to chain programs together with pipes and to redirect their input and output — that mechanism you have used in passing and will finally understand completely — to process text with cut, sort, uniq, sed and awk until you turn access.log and bookings.csv into the reports Marta asks for, to control the system's processes and to schedule tasks with cron so that they run on their own in the small hours. It is the module where you stop running commands one at a time and start composing them, which is exactly what the Unix philosophy of the first module promised. Update your VM snapshot and I will see you there.

Linux Course: From Beginner to System Administrator

Module 1: Introduction to Linux

Module 2: Basic Linux Commands

Module 3: Advanced Command-Line Skills

Module 4: Shell Scripting

Module 5: System Administration

Module 6: Networking and Security

Module 7: Advanced Topics

Module 8: Practical Projects

© Copyright 2026. All rights reserved