In Module 1 you drew the map: you know what /etc is, what distinguishes /opt from /srv and why /var holds what grows. But knowing a map and walking the terrain are different things. This lesson is the walk.
Moving around the file system looks trivial — cd to go, ls to look — and yet it is where the most time gets lost over a career. The difference between someone who navigates fluently and someone who does not lies in concrete details: knowing how to get back to the previous directory with two keys, reading the output of ls -l at a glance, understanding exactly what each timestamp on a file is telling you, and not confusing "this takes up 4 KB" with "this declares 18 bytes".
A warning before we start: until the end of this lesson you are not going to modify anything. Every command you will see is read-only. That is deliberate: first you learn to look, and in the next lesson you learn to touch.
Contents
pwdand the working directorycdin all its forms- Absolute and relative paths in practice
.and..: real directory entrieslsin depth- Reading
ls -lcolumn by column tree: seeing the structure at a glancestatand the three timestampsfile: what a file really isduanddf: how much it takes and how much is left- Never getting lost: habits of orientation
- Guided practice: a tour of the Tramontana tree
pwd and the working directory
pwd and the working directoryEvery process on the system has a current working directory (cwd): the point in the tree from which it interprets relative paths. Your shell is a process and it has one too.
pwd (print working directory) always answers with an absolute path, starting from /.
When you log in, your working directory is your personal directory, which on srv-tramontana is /home/operator. That is why the prompt shows ~: it is the abbreviation for the home directory.
pwd has two options that only make sense once symbolic links are involved (lesson 02-06):
| Option | Behaviour |
|---|---|
-L (default) |
Shows the logical path, the way you got there, with links unresolved |
-P |
Shows the real physical path, resolving every link |
operator@srv-tramontana:/opt/tramontana/app$ pwd
/opt/tramontana/app
operator@srv-tramontana:/opt/tramontana/app$ pwd -P
/opt/tramontana/releases/2026-08-18That difference will matter when Tramontana adopts the deployment pattern based on links. For now, take away that pwd -P tells you where you really are.
cd in all its forms
cd in all its formscd (change directory) changes the working directory. It is a builtin, and in lesson 02-01 you saw why it has to be one.
| Form | What it does |
|---|---|
cd /absolute/path |
Goes to that path, wherever you are |
cd subdirectory |
Descends into a child of the current directory |
cd .. |
Goes up to the parent directory |
cd ../.. |
Goes up two levels |
cd (no arguments) |
Goes to your home directory |
cd ~ |
The same thing, explicitly |
cd ~operator |
To that user's home directory |
cd - |
Returns to the previous directory |
cd / |
To the root |
A complete walk:
operator@srv-tramontana:~$ pwd
/home/operator
operator@srv-tramontana:~$ cd /var/log/tramontana
operator@srv-tramontana:/var/log/tramontana$ pwd
/var/log/tramontana
operator@srv-tramontana:/var/log/tramontana$ cd ..
operator@srv-tramontana:/var/log$ pwd
/var/log
operator@srv-tramontana:/var/log$ cd
operator@srv-tramontana:~$ pwd
/home/operatorNotice how the prompt reflects the current directory. It is your permanent orientation instrument and it costs nothing to look at.
The two forms that save the most time, and that many people do not know about:
cd - toggles between the last two directories, like a "back" button. When you use it, Bash prints where it has gone:
operator@srv-tramontana:~$ cd /etc/tramontana
operator@srv-tramontana:/etc/tramontana$ cd /var/log/tramontana
operator@srv-tramontana:/var/log/tramontana$ cd -
/etc/tramontana
operator@srv-tramontana:/etc/tramontana$ cd -
/var/log/tramontanaIt is perfect when you are comparing the configuration against the logs: two keys to jump from one side to the other.
~user is the path to another user's home directory, resolved by the shell:
operator@srv-tramontana:~$ echo ~
/home/operator
operator@srv-tramontana:~$ echo ~student
/home/student
operator@srv-tramontana:~$ echo ~root
/rootWatch out for ~root: it is /root, not /home/root. The administration account has its home outside /home precisely so that it remains accessible even if /home is on a partition that has not been mounted, something that matters in system recovery (lesson 07-01).
One behaviour to bear in mind: if cd fails, you do not move.
operator@srv-tramontana:~$ cd /etc/tramontanas
-bash: cd: /etc/tramontanas: No such file or directory
operator@srv-tramontana:~$ pwd
/home/operatorThe prompt did not change and pwd confirms it. This is the detail that made the ; of the previous lesson dangerous: cd fails, but the next command runs anyway, in the wrong place.
- Absolute and relative paths in practice
You saw the definition in Module 1. Here you are going to use it.
- Absolute: starts with
/. It means the same thing from anywhere. - Relative: does not start with
/. It is interpreted from the current directory.
The Tramontana tree you will use as a reference:
/
├── etc/
│ └── tramontana/
│ └── app.conf
├── opt/
│ └── tramontana/
│ └── app/
│ ├── executable
│ ├── templates/
│ └── version.txt
├── srv/
│ └── tramontana/
│ └── backups/
├── var/
│ └── log/
│ └── tramontana/
│ ├── access.log
│ └── errors.log
└── home/
└── operator/
├── scripts/
└── data/
├── bookings.csv
└── houses.txtTranslation exercises. You are in /var/log/tramontana. How do you get to each destination in both ways?
| Destination | Absolute path | Relative path |
|---|---|---|
errors.log (same dir.) |
/var/log/tramontana/errors.log |
errors.log or ./errors.log |
The directory /var/log |
/var/log |
.. |
/etc/tramontana/app.conf |
/etc/tramontana/app.conf |
../../../etc/tramontana/app.conf |
/opt/tramontana/app |
/opt/tramontana/app |
../../../opt/tramontana/app |
| Your home directory | /home/operator |
~ or ../../../home/operator |
A practical check of the third row:
Three .. because you have to go up from tramontana to log, from log to var, and from var to /. Count the levels in the prompt and they come out on their own.
When to use each one:
| Context | Use | Why |
|---|---|---|
| Interactive work, close to where you are | Relative | Shorter, less typing |
| Scripts (Module 4) | Absolute, always | The script may be run from any directory |
| Cron jobs (03-07) | Absolute, always | Cron starts in a directory you do not control |
| Service configuration | Absolute | The service does not share your working directory |
| Documenting a procedure | Absolute | Whoever reads it does not know where you were |
A mnemonic: relative for your fingers, absolute for machines.
. and ..: real directory entries
. and ..: real directory entries. is the current directory and .. is the parent. But they are not a shell trick: they are real entries inside every directory, written to disk.
operator@srv-tramontana:~$ ls -la /home/operator/data
total 16
drwxr-xr-x 2 operator operator 4096 Aug 18 08:11 .
drwxr-x--- 5 operator operator 4096 Aug 18 08:11 ..
-rw-r--r-- 1 operator operator 418 Aug 17 19:40 houses.txt
-rw-r--r-- 1 operator operator 1204 Aug 18 07:55 bookings.csvThe first two lines are genuine entries, with their permissions, their owner and their date. The definitive check:
operator@srv-tramontana:~$ ls -ldi /home/operator/data /home/operator/data/.
262148 drwxr-xr-x 2 operator operator 4096 Aug 18 08:11 /home/operator/data
262148 drwxr-xr-x 2 operator operator 4096 Aug 18 08:11 /home/operator/data/.The same number, 262148. That number is the inode, and the fact that it matches proves that . and the directory are literally the same object. Inodes are the topic of lesson 02-06; hold on to this fact.
Two practical consequences:
./ is mandatory for running a program from the current directory. As you saw in 02-01, the current directory is not in the PATH:
operator@srv-tramontana:~/scripts$ report.sh
-bash: report.sh: command not found
operator@srv-tramontana:~/scripts$ ./report.sh
Generating bookings report..../ protects against names starting with a hyphen, as you saw with the -- separator. rm ./-report.txt always works.
A detail about the root directory: in /, the .. entry points to itself. cd /.. leaves you in /. The tree ends there.
ls in depth
ls in depthls is the command you will run more times than any other in your life. It is worth knowing well.
| Option | What it does |
|---|---|
-l |
Long format: permissions, owner, size, date |
-a |
Shows everything, including hidden files, . and .. |
-A |
Like -a but without . and .. (almost always more useful) |
-h |
Readable sizes (K, M, G). Requires -l |
-t |
Sorts by modification date, most recent first |
-S |
Sorts by size, largest first |
-r |
Reverses the order |
-R |
Recursive: descends into subdirectories |
-d |
Shows the directory itself, not its contents |
-i |
Shows the inode number |
-1 |
One item per line (useful with pipes) |
-F |
Adds a symbol according to the type: / dir., * executable, @ link |
--color=auto |
Colours by type (already set up as an alias on Ubuntu) |
Without options, ls is terse because the old shell was:
Combinations that really do get used:
# The most common: everything, long, readable sizes
operator@srv-tramontana:~$ ls -lah /var/log/tramontana
total 32K
drwxr-x--- 2 root adm 4.0K Aug 18 09:00 .
drwxrwxr-x 12 root syslog 4.0K Aug 18 00:00 ..
-rw-r----- 1 root adm 18K Aug 18 09:14 access.log
-rw-r----- 1 root adm 6.2K Aug 18 09:02 errors.log
# What changed last, right at the top: for diagnosis
operator@srv-tramontana:~$ ls -lt /etc/tramontana
total 4
-rw-r----- 1 root tramontana 512 Aug 18 08:47 app.conf
# The newest at the bottom: better when there are many files, because it ends up next to the prompt
operator@srv-tramontana:~$ ls -ltr /var/log
...
-rw-r----- 1 syslog adm 124832 Aug 18 09:15 syslog
# The largest first: for finding what is filling a disk
operator@srv-tramontana:~$ ls -lhS /var/log/tramontana
-rw-r----- 1 root adm 18K Aug 18 09:14 access.log
-rw-r----- 1 root adm 6.2K Aug 18 09:02 errors.logls -ltr deserves a comment: by sorting from oldest to newest, the last thing modified ends up just above the prompt, where your eyes already are. In a directory with two hundred files, that is the difference between seeing the answer and having to look for it.
-d is the least intuitive option and the most necessary. Without it, ls -l on a directory lists its contents; with it, it describes the directory itself:
operator@srv-tramontana:~$ ls -l /etc/tramontana
total 4
-rw-r----- 1 root tramontana 512 Aug 18 08:47 app.conf
operator@srv-tramontana:~$ ls -ld /etc/tramontana
drwxr-x--- 2 root tramontana 4096 Aug 18 08:30 /etc/tramontanaThe first tells you what is inside; the second tells you who can get in. When you reach permissions in lesson 02-07, ls -ld will be your main tool.
-R walks the whole tree. Use it with care: ls -R / produces hundreds of thousands of lines.
operator@srv-tramontana:~$ ls -R /opt/tramontana
/opt/tramontana:
app
/opt/tramontana/app:
executable templates version.txt
/opt/tramontana/app/templates:
confirmation.html invoice.html
- Reading
ls -l column by column
ls -l column by columnYou need to be able to read this output at a glance, because it contains almost all the relevant information about a file:
operator@srv-tramontana:~$ ls -l /var/log/tramontana/access.log
-rw-r----- 1 root adm 18432 Aug 18 09:14 /var/log/tramontana/access.logTaken apart:
| Column | Value | Meaning |
|---|---|---|
| 1 (1st character) | - |
Type: regular file. d directory, l link, c/b device, s socket, p pipe |
| 1 (the rest) | rw-r----- |
Permissions: three groups of three. Lesson 02-07 |
| 2 | 1 |
Number of hard links. Lesson 02-06 |
| 3 | root |
Owner |
| 4 | adm |
Owning group |
| 5 | 18432 |
Size in bytes (with -h, in readable units) |
| 6 | Aug 18 09:14 |
Date of last modification (mtime) |
| 7 | access.log |
Name |
A detail about the sixth column: ls shows the time if the file was modified in the last six months, and the year if it is older. That asymmetry is disconcerting the first time:
operator@srv-tramontana:~$ ls -l /etc/hostname /var/log/tramontana/access.log
-rw-r--r-- 1 root root 16 Jun 12 2025 /etc/hostname
-rw-r----- 1 root adm 18432 Aug 18 09:14 /var/log/tramontana/access.logIf you want complete, unambiguous dates:
operator@srv-tramontana:~$ ls -l --time-style=full-iso /var/log/tramontana/access.log
-rw-r----- 1 root adm 18432 2026-08-18 09:14:37.482910233 +0200 /var/log/tramontana/access.logThe second column, the link count, has a behaviour that takes people by surprise: on directories it is never 1.
operator@srv-tramontana:~$ ls -ld /opt/tramontana/app
drwxr-xr-x 4 root root 4096 Aug 18 08:30 /opt/tramontana/appIt is 4 because the following count: the app entry in /opt/tramontana, the . entry inside app, and the .. entries of its two subdirectories. The link count of a directory is 2 + the number of subdirectories it contains. It is a trick for counting subdirectories without going in, and it will become completely clear in lesson 02-06.
And the size of a directory (4096) is not what it contains: it is what the list of names itself takes up. To find out what it contains there is du, in section 10.
tree: seeing the structure at a glance
tree: seeing the structure at a glancetree shows the hierarchy drawn out. It does not come installed by default on Ubuntu Server:
operator@srv-tramontana:~$ tree
Command 'tree' not found, but can be installed with:
sudo apt install tree
operator@srv-tramontana:~$ sudo apt install -y tree(Package management is covered in lesson 05-03; here we install it and move on.)
operator@srv-tramontana:~$ tree /opt/tramontana
/opt/tramontana
└── app
├── executable
├── templates
│ ├── confirmation.html
│ └── invoice.html
└── version.txt
3 directories, 4 filesEssential options:
| Option | What it does |
|---|---|
-L n |
Limits the depth to n levels |
-d |
Directories only |
-a |
Includes hidden files |
-h |
Shows readable sizes |
-F |
Adds / to directories |
--du |
Adds up the size of each branch |
-L is practically mandatory on large directories:
operator@srv-tramontana:~$ tree -L 1 /
/
├── bin -> usr/bin
├── boot
├── dev
├── etc
├── home
├── lib -> usr/lib
├── media
├── mnt
├── opt
├── proc
├── root
├── run
├── sbin -> usr/sbin
├── srv
├── sys
├── tmp
├── usr
└── varThere you see something interesting: bin, lib and sbin appear with an arrow. They are symbolic links to directories inside /usr, the result of the /usr-merge reorganisation carried out by modern distributions. Lesson 02-06 explains exactly what that arrow means.
A very useful combination for documenting the structure of a project:
operator@srv-tramontana:~$ tree -d -L 2 /var
/var
├── backups
├── cache
├── lib
├── log
│ ├── apt
│ └── tramontana
├── spool
└── tmp
stat and the three timestamps
stat and the three timestampsls -l gives a summary. stat gives everything:
operator@srv-tramontana:~$ stat /home/operator/data/bookings.csv
File: /home/operator/data/bookings.csv
Size: 1204 Blocks: 8 IO Block: 4096 regular file
Device: 8,2 Inode: 262151 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 1001/operator) Gid: ( 1001/operator)
Access: 2026-08-18 09:20:11.104283761 +0200
Modify: 2026-08-18 07:55:02.882910233 +0200
Change: 2026-08-18 07:55:02.882910233 +0200
Birth: 2026-08-10 12:03:44.000000000 +0200What it adds over ls -l:
- The inode number (262151) and the device it lives on.
- The permissions in octal and symbolic form at once (
0644/-rw-r--r--), very useful for lesson 02-07. - The numeric UID and GID as well as the names.
- The blocks actually occupied.
- And the timestamps with nanosecond precision.
The three classic timestamps get confused constantly. This table settles the confusion:
| Timestamp | Name | It is updated when... | It is not updated when... |
|---|---|---|---|
| atime | Access | The contents are read (cat, less) |
You only look at its metadata with ls |
| mtime | Modification | The contents change | The permissions or the owner change |
| ctime | Inode change | The contents or the metadata change | You can never set it by hand |
The most widespread mistake is believing that ctime means creation time. It does not: it is change time, the moment the inode changed. Changing a file's permissions updates its ctime but not its mtime.
A demonstration:
operator@srv-tramontana:~$ stat -c '%y %z' data/houses.txt
2026-08-17 19:40:10.000000000 +0200 2026-08-17 19:40:10.000000000 +0200
operator@srv-tramontana:~$ chmod 640 data/houses.txt
operator@srv-tramontana:~$ stat -c '%y %z' data/houses.txt
2026-08-17 19:40:10.000000000 +0200 2026-08-18 09:31:55.771820102 +0200The mtime (%y) did not move: the contents did not change. The ctime (%z) did: the inode was modified when the permissions changed. This distinction is the basis of intrusion detection (lesson 06-04): an attacker can forge the mtime, but not the ctime.
The fourth line, Birth (birth time), is relatively modern. It exists on ext4 and other current file systems, but not every tool exposes it and it cannot be queried portably.
A detail about atime worth knowing: updating it every time a file is read would mean one disk write per read, which would be a performance disaster. That is why Linux mounts file systems with the relatime option, which only updates atime if it is older than mtime or if more than 24 hours have passed. The consequence: the atime you see is approximate. Do not use it for precise auditing.
stat accepts custom formats with -c:
operator@srv-tramontana:~$ stat -c '%n: %s bytes, perms %a, owner %U:%G' data/*
data/houses.txt: 418 bytes, perms 640, owner operator:operator
data/bookings.csv: 1204 bytes, perms 644, owner operator:operatorThat form is the one you will use in the Module 4 scripts to generate reports.
file: what a file really is
file: what a file really isOn Windows, the extension determines the type. On Linux the extension is just part of the name: it means nothing to the system. What determines the type is the content, and the one who interprets it is the program that opens it.
file examines the first bytes of a file and compares them against a database of known signatures (the magic numbers):
operator@srv-tramontana:~$ file /home/operator/data/*
/home/operator/data/houses.txt: UTF-8 Unicode text
/home/operator/data/bookings.csv: CSV text
operator@srv-tramontana:~$ file /opt/tramontana/app/executable
/opt/tramontana/app/executable: ELF 64-bit LSB pie executable, x86-64,
dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, ...
operator@srv-tramontana:~$ file /etc/tramontana /dev/null /dev/sda
/etc/tramontana: directory
/dev/null: character special (1/3)
/dev/sda: block special (8/0)Definitive proof that the extension is not in charge:
operator@srv-tramontana:~$ cp data/houses.txt /tmp/houses.jpg
operator@srv-tramontana:~$ file /tmp/houses.jpg
/tmp/houses.jpg: UTF-8 Unicode textIt is called .jpg and it is still text. file is not fooled by the name.
Cases where file saves you from a problem:
# What is this file somebody sent me compressed with?
operator@srv-tramontana:~$ file /srv/tramontana/backups/backup-2026-08-17
/srv/tramontana/backups/backup-2026-08-17: gzip compressed data, from Unix
# Why does the script fail with a strange error?
operator@srv-tramontana:~$ file scripts/report.sh
scripts/report.sh: Bourne-Again shell script, ASCII text executable,
with CRLF line terminatorsThat last example is a classic: with CRLF line terminators means the file has Windows line endings. Bash tries to run /bin/bash\r and fails with an incomprehensible message. file gives you the diagnosis in a second.
Useful options:
| Option | What it does |
|---|---|
-b |
Only the type, without the file name |
-i |
Returns the MIME type (text/plain, application/gzip) |
-L |
Follows symbolic links |
A professional habit: run file over any unknown file before opening it. In lesson 02-05 you will see why running cat on a binary wrecks your terminal; file is the check that prevents it.
du and df: how much it takes and how much is left
du and df: how much it takes and how much is leftTwo commands that get confused and that answer different questions:
df(disk free): how much space is left on the mounted file systems. It asks the file system.du(disk usage): how much space one specific directory takes up. It walks and adds up.
operator@srv-tramontana:~$ df -h
Filesystem Size Used Avail Use% Mounted on
tmpfs 389M 1.4M 388M 1% /run
/dev/sda2 23G 6.7G 15G 30% /
tmpfs 1.9G 0 1.9G 0% /dev/shm
tmpfs 5.0M 0 5.0M 0% /run/lock
/dev/sda1 1.1G 6.4M 1.1G 1% /boot/efiThere you see the 23 GB disk at 30 % that you know from the installation. The tmpfs lines are file systems in RAM, not on disk: that is why /run and /dev/shm disappear on reboot.
An essential option that almost nobody uses:
operator@srv-tramontana:~$ df -hi /
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/sda2 1.5M 112K 1.4M 8% /-i shows the inodes, not the bytes. A disk can have free space and still say "No space left on device" if the inodes have run out, typically because of millions of tiny files. It is an incident that baffles anyone who does not know it exists, and df -hi is the diagnosis in a second.
du answers the opposite question:
| Option | What it does |
|---|---|
-s |
Summary: only the total, with no breakdown |
-h |
Readable |
-c |
Adds a total line at the end |
-d n |
Goes down n levels in the breakdown |
--apparent-size |
The declared size, not the space occupied on disk |
-x |
Does not cross into other file systems |
Without -s, du breaks down every subdirectory, which on / produces an avalanche. With -d 1 you get a one-level breakdown, which is what you want for hunting culprits:
operator@srv-tramontana:~$ sudo du -h -d 1 /var/log
4.0K /var/log/apt
26K /var/log/tramontana
8.0K /var/log/private
1.3M /var/logThe last line is the total for /var/log, including the loose files that are not in any subdirectory.
The difference between the declared size and the space occupied explains a discrepancy that causes confusion:
operator@srv-tramontana:~$ echo "test" > /tmp/test.txt
operator@srv-tramontana:~$ ls -l /tmp/test.txt
-rw-rw-r-- 1 operator operator 5 Aug 18 09:45 /tmp/test.txt
operator@srv-tramontana:~$ du -h /tmp/test.txt
4.0K /tmp/test.txt
operator@srv-tramontana:~$ du -h --apparent-size /tmp/test.txt
5 /tmp/test.txtThe file declares 5 bytes, but it occupies 4 KB because the file system allocates space in blocks and the minimum ext4 block is 4 KB. A directory with a hundred thousand 5-byte files takes up 400 MB for real.
That is why du (which measures what is occupied) and df (which measures what is free on the device) can fail to agree. Another classic cause of a mismatch: a deleted file that is still open by a process. It disappears from the tree, so du does not see it, but the space is not released until the process closes it, so df does count it. It is the reason why deleting a huge log without restarting the service does not free space, and you will come back to it in lesson 05-07.
- Never getting lost: habits of orientation
Three habits that prevent 90 % of the frights:
1. Read the prompt before every destructive command. The Ubuntu prompt already shows the current directory. It costs nothing to look at it, and in the instant before an rm it is what separates deleting the right thing from deleting the other thing.
2. pwd as a reflex. After any chain of cds, after coming back from a break, after connecting by SSH to another machine. It is free.
3. Before acting, look. The pattern you already saw in 02-01 and that will recur throughout the course:
operator@srv-tramontana:~$ ls -la /srv/tramontana/backups/temp # 1. look
operator@srv-tramontana:~$ pwd # 2. confirm where I am
operator@srv-tramontana:~$ cd /srv/tramontana/backups/temp && ... # 3. act, with &&A fourth habit, for later on: when you work on several servers at once, customise the prompt so that it shows the machine name in a different colour. It is the way not to run in production what you meant to run in test. It is done with the PS1 variable (lesson 03-01).
- Guided practice: a tour of the Tramontana tree
We are going to walk through the whole Tramontana Bookings deployment, describing each piece. This tour is the one you would do on receiving a server you do not know.
Starting point and overview:
operator@srv-tramontana:~$ pwd
/home/operator
operator@srv-tramontana:~$ df -h /
Filesystem Size Used Avail Use% Mounted on
/dev/sda2 23G 6.7G 15G 30% /A single file system for the whole tree, at 30 %. Plenty of space, and it also means that anything growing out of control affects the entire system.
The application:
operator@srv-tramontana:~$ tree -L 2 /opt/tramontana
/opt/tramontana
└── app
├── executable
├── templates
└── version.txt
operator@srv-tramontana:~$ ls -ld /opt/tramontana/app
drwxr-xr-x 4 root root 4096 Aug 18 08:30 /opt/tramontana/app
operator@srv-tramontana:~$ cat /opt/tramontana/app/version.txt
Tramontana Bookings 3.2.1
operator@srv-tramontana:~$ file /opt/tramontana/app/executable
/opt/tramontana/app/executable: ELF 64-bit LSB pie executable, x86-64
operator@srv-tramontana:~$ du -sh /opt/tramontana/app
48M /opt/tramontana/appDiagnosis: third-party software in /opt as per the FHS, owned by root (users cannot modify the code: correct), 48 MB, one compiled binary and its templates. Static data: it does not change except in a deployment.
The configuration:
operator@srv-tramontana:~$ ls -l /etc/tramontana/
total 4
-rw-r----- 1 root tramontana 512 Aug 18 08:47 app.conf
operator@srv-tramontana:~$ stat -c 'perms %a, owner %U:%G, modified %y' /etc/tramontana/app.conf
perms 640, owner root:tramontana, modified 2026-08-18 08:47:12.338291044 +0200Two relevant things. The first: permissions 640, that is, nobody who is not root or a member of the tramontana group can read it, which is correct because it contains credentials. The second: the tramontana group appears here, and we have not formally created it yet; that will be done in lesson 05-01. The exact meaning of the 640 is lesson 02-07.
Notice the mtime too: it was modified this morning at 8:47. On a server that should not be changing, a recently modified configuration file is the first thing you investigate when there is an incident.
The logs:
operator@srv-tramontana:~$ sudo ls -lh /var/log/tramontana/
total 25K
-rw-r----- 1 root adm 18K Aug 18 09:14 access.log
-rw-r----- 1 root adm 6.2K Aug 18 09:02 errors.log
operator@srv-tramontana:~$ sudo du -sh /var/log/tramontana
26K /var/log/tramontana
operator@srv-tramontana:~$ sudo file /var/log/tramontana/access.log
/var/log/tramontana/access.log: ASCII textVariable data: they grow with use. 26 KB today is nothing, but an access log in production grows linearly with the traffic, and that is exactly the scenario that justified putting it in /var. That it is ASCII text matters: it means all the text-processing tools of Module 3 will work on it.
The backups and the working data:
operator@srv-tramontana:~$ ls -ld /srv/tramontana/backups
drwxr-x--- 2 root root 4096 Aug 18 08:30 /srv/tramontana/backups
operator@srv-tramontana:~$ ls -lh ~/data/
total 8.0K
-rw-r----- 1 operator operator 418 Aug 17 19:40 houses.txt
-rw-r--r-- 1 operator operator 1.2K Aug 18 07:55 bookings.csv
operator@srv-tramontana:~$ head -n 3 ~/data/bookings.csv
id;date;house;guest;nights;amount
1001;2026-07-03;mas-figueres;Nuria Prat;4;620.00
1002;2026-07-05;can-ventos;Oriol Sala;2;280.00The working data is in your home directory, separate from the application. bookings.csv uses the semicolon as its separator, a detail that will matter when processing it in lesson 03-05.
A summary of the tour, in the report format Marta expects:
| Path | Contents | Nature | Size | Owner |
|---|---|---|---|---|
/opt/tramontana/app |
Binary and templates | Static | 48 M | root:root |
/etc/tramontana/app.conf |
Configuration with credentials | Static, sensitive | 512 B | root:tramontana |
/var/log/tramontana/ |
Access and error logs | Variable, growing | 26 K | root:adm |
/srv/tramontana/backups |
Backups | Variable, grows in jumps | 0 | root:root |
/home/operator/data |
Working data | Variable | 8 K | operator:operator |
With five commands — df, tree, ls -l, stat, du — you have characterised a complete server without touching anything. That is the initial reconnaissance you carry out when you inherit a system.
Common Mistakes and Tips
Confusing .. with .. cd . does nothing, cd .. goes up. One character, a great deal of difference.
Using relative paths in scripts. It works while you run it by hand from the right directory, and it fails the day cron launches it from /. Absolute always in scripts.
Believing that the size of a directory in ls -l is what it contains. It is not: it is 4096 bytes of index. Use du -sh.
Confusing du with df. If you are asking "how much is left on the disk", it is df. If you are asking "what is taking up so much", it is du.
Believing that ctime is the creation date. It is change time. The real creation date, when available, is shown by stat as "Birth".
Trusting the extension. A .txt can be a binary and a file with no extension can be an executable. file is the answer.
Tip: cd - and pushd/popd. cd - toggles between two. If you need a stack of directories, pushd and popd implement one; they are builtins and their documentation is in help pushd.
Tip: ls -ltr in directories with many files. The most recent ends up stuck to the prompt.
Tip: ls -ld is the most forgotten option. Every time you want to know something about the directory and not about its contents, it is -d.
Tip: df -hi when "there is no space left" but df -h says there is. The inodes have run out.
Exercises
Exercise 1: translating paths
Without running anything, starting from the Tramontana tree in section 3 and being in /opt/tramontana/app/templates:
- The relative path to
/opt/tramontana/app/version.txt. - The relative path to
/etc/tramontana/app.conf. - Which directory is
../../../../var/log? - What does
cd ../..do from there, and what is the resultingpwd?
Then check your answers on the server.
Exercise 2: characterising an unknown directory
Marta asks you to review the /var/log directory on srv-tramontana because "the disk seems to be filling up". Without deleting anything, find out and present:
- How much free space is left on the root partition and what percentage is in use.
- How much
/var/logtakes up in total. - Which three subdirectories of
/var/logtake up the most. - Which is the most recently modified file inside
/var/log/tramontanaand at what time. - Whether that file is text or binary.
Exercise 3: the timestamps
In your home directory, with a test file:
- Create
/tmp/times.txtwith some content and note down its three timestamps. - Read it with
cat. Which one changed? - Change its permissions with
chmod 600. Which one changed this time? - Append content to it. Which ones changed?
- Explain why an attacker who wants to hide the fact that they have modified a system file can forge the mtime but finds it far harder to forge the ctime.
Solutions
Solution 1
1. ../version.txt — you go up from templates to app and the file is there.
2. ../../../etc/tramontana/app.conf. Count the levels: from templates to app (one), to tramontana (two), to opt (three)... and from opt you have to go up one more to reach /. That makes four: ../../../../etc/tramontana/app.conf.
Let us check it, because this is exactly the kind of mistake that gets made:
operator@srv-tramontana:/opt/tramontana/app/templates$ ls ../../../etc/tramontana/
ls: cannot access '../../../etc/tramontana/': No such file or directory
operator@srv-tramontana:/opt/tramontana/app/templates$ ls ../../../../etc/tramontana/
app.confThe current absolute path has four components (opt, tramontana, app, templates), so four .. are needed to reach the root. This is exactly why absolute paths are better as soon as the distance goes beyond a couple of levels: /etc/tramontana/app.conf does not allow this mistake.
3. ../../../../var/log is /var/log, by the same calculation.
4. cd ../.. goes up two levels: from templates to app, and from app to tramontana.
operator@srv-tramontana:/opt/tramontana/app/templates$ cd ../..
operator@srv-tramontana:/opt/tramontana$ pwd
/opt/tramontanaSolution 2
# 1. Space on the root partition
operator@srv-tramontana:~$ df -h /
Filesystem Size Used Avail Use% Mounted on
/dev/sda2 23G 6.7G 15G 30% /15 GB free, 30 % used. No problem for now.
1.3 MB out of 23 GB: /var/log is not the cause of the disk filling up. This is the figure that settles Marta's question.
# 3. The three largest subdirectories
operator@srv-tramontana:~$ sudo du -h -d 1 /var/log | sort -h | tail -n 4
8.0K /var/log/private
26K /var/log/tramontana
912K /var/log/journal
1.3M /var/log(The | sort -h | tail is a pipe; it is explained thoroughly in Module 3. You can get the same thing by reading the output of du -h -d 1 /var/log by eye.)
The biggest consumer is journal, the systemd log, with 912 KB. That is normal and it has a configurable size limit (lesson 05-06).
# 4. The most recent one in /var/log/tramontana
operator@srv-tramontana:~$ sudo ls -lt --time-style=long-iso /var/log/tramontana
total 25
-rw-r----- 1 root adm 18432 2026-08-18 09:14 access.log
-rw-r----- 1 root adm 6348 2026-08-18 09:02 errors.logaccess.log, modified at 09:14. -t sorts by date descending, so the first one is the most recent.
# 5. File type
operator@srv-tramontana:~$ sudo file /var/log/tramontana/access.log
/var/log/tramontana/access.log: ASCII textReport for Marta: the root disk is at 30 % with 15 GB free. /var/log takes up 1.3 MB in total, so it is not what is filling the disk; it is worth looking elsewhere. Within the logs, the biggest consumer is the system journal (912 KB) and the application logs add up to 26 KB. The access log was last updated at 09:14 today, which confirms that the application is running and writing normally.
Solution 3
operator@srv-tramontana:~$ echo "first line" > /tmp/times.txt
operator@srv-tramontana:~$ stat -c 'atime=%x%nmtime=%y%nctime=%z' /tmp/times.txt
atime=2026-08-18 10:02:14.118293011 +0200
mtime=2026-08-18 10:02:14.118293011 +0200
ctime=2026-08-18 10:02:14.118293011 +0200When you create it, all three match.
operator@srv-tramontana:~$ cat /tmp/times.txt
first line
operator@srv-tramontana:~$ stat -c 'atime=%x%nmtime=%y%nctime=%z' /tmp/times.txt
atime=2026-08-18 10:03:41.554820119 +0200
mtime=2026-08-18 10:02:14.118293011 +0200
ctime=2026-08-18 10:02:14.118293011 +0200Only atime changed: you have read the contents, you have not modified them. (If you do not see the change, it is because of relatime: if atime was already more recent than mtime and 24 hours have not passed, the kernel does not update it. That is the normal, expected behaviour.)
operator@srv-tramontana:~$ chmod 600 /tmp/times.txt
operator@srv-tramontana:~$ stat -c 'atime=%x%nmtime=%y%nctime=%z' /tmp/times.txt
atime=2026-08-18 10:03:41.554820119 +0200
mtime=2026-08-18 10:02:14.118293011 +0200
ctime=2026-08-18 10:05:02.771820102 +0200Only ctime changed: you have modified the inode (the permissions live there), not the contents, and you have not read it either. This is the demonstration that ctime is not creation time.
operator@srv-tramontana:~$ echo "second line" >> /tmp/times.txt
operator@srv-tramontana:~$ stat -c 'atime=%x%nmtime=%y%nctime=%z' /tmp/times.txt
atime=2026-08-18 10:03:41.554820119 +0200
mtime=2026-08-18 10:06:33.201938477 +0200
ctime=2026-08-18 10:06:33.201938477 +0200mtime and ctime changed. The contents changed (mtime) and, in changing the contents, the size recorded in the inode also changed (ctime). atime did not change because writing is not reading.
Point 5: why ctime is hard to forge. The touch command lets you set atime and mtime to whatever value you like (touch -t, lesson 02-04). An attacker who modifies /etc/tramontana/app.conf can leave the mtime exactly as it was and the file will look untouched in ls -l.
But the ctime cannot be set. There is no system call that allows an arbitrary value to be written into it: the kernel updates it automatically every time the inode changes, including the change caused by touch itself when forging the mtime. Forging the ctime requires direct access to the block device underneath the file system, or changing the system clock, and both of those leave other traces.
Hence the forensic practice: when an intrusion is suspected, mtime and ctime are compared. A file whose ctime is much later than its mtime has been touched in a way that deserves an explanation. It is one of the indicators used by the tools in lesson 06-04.
Conclusion
You no longer navigate the Module 1 tree by feel: you walk it with instruments.
pwdtells you where you are, andpwd -Pwhere you really are when links are involved.cdhas more forms than it seemed:cd -to toggle, plaincdto go home,~userfor somebody else's home directory. And if it fails, you do not move, which is precisely what made the;dangerous.- You can translate between absolute and relative paths by counting levels, and you have the criterion: relative for your fingers, absolute for scripts and cron.
.and..are real entries with their own inode, and that is why./programis the way to run something from the current directory.- You read
lscolumn by column, and you know the combinations used daily:-lah,-ltr,-lhSand the indispensable-ld. tree -Lgives you the structure at a glance without drowning you in depth.statgives you the complete metadata, and you understand atime, mtime and ctime precisely, including why ctime is relevant to security.filetells you what a file really is, because on Linux the extension is not in charge.dumeasures what something takes up,dfmeasures what is left, and you know why they sometimes disagree and what to do when the disk "is full" without being full.- And you have characterised the complete Tramontana Bookings deployment with five commands, without modifying anything.
Looking is over. In the next lesson, File and Directory Operations, you start writing to the disk: touch and mkdir to create, cp and mv with all their traps — including the trailing slash on the destination, which bites everybody — rm and the serious conversation about rm -rf that has to be had once in a lifetime, rsync to copy large directories properly, and tar with gzip, bzip2 and xz to package things up. You will finish by preparing a package of /opt/tramontana/app to hand over to Luis. Make sure your VM snapshot is up to date before you begin: from here on, the commands no longer just look.
Linux Course: From Beginner to System Administrator
Module 1: Introduction to Linux
- What Is Linux?
- History of Linux
- Linux Distributions
- Installing Linux
- First Contact with the System
- The Linux File System Structure
Module 2: Basic Linux Commands
- Introduction to the Command Line
- Getting Help and System Documentation
- Navigating the File System
- File and Directory Operations
- Viewing and Editing Files
- Hard and Symbolic Links
- File Permissions and Ownership
Module 3: Advanced Command-Line Skills
- The Shell Environment: Variables, Aliases and History
- Using Wildcards and Regular Expressions
- Searching Files and Content: find, locate and grep
- Pipes and Redirection
- Text Processing: cut, sort, uniq, sed and awk
- Process Management
- Scheduling Tasks with Cron
- Networking Commands
Module 4: Shell Scripting
- Introduction to Shell Scripting
- Variables and Data Types
- Script Input, Output and Arguments
- Control Structures
- Functions and Libraries
- Debugging and Error Handling
- Production Scripts: Best Practices
Module 5: System Administration
- User and Group Management
- sudo and Special Permissions
- Package Management
- Disk Management
- systemd and Service Management
- System Logs: journald and syslog
- System Monitoring and Performance Tuning
- Backup and Restore
Module 6: Networking and Security
- Network Configuration
- SSH and Remote Access
- Firewalls and Perimeter Security
- Intrusion Detection Systems
- Secrets Management and TLS Certificates
- Securing Linux Systems
Module 7: Advanced Topics
- The Boot Process and System Recovery
- Advanced Diagnostics: strace, perf and eBPF
- Linux Kernel Tuning
- Virtualization with Linux
- Linux Containers and Docker
- Automation with Ansible
- High Availability and Load Balancing
