Linux for the Curious Kid (and Grown-Up) · Book One
Module 1: What are you even looking at?
Before we type a single command, let's figure out what a computer actually is, what Linux is, and where all the typing happens.
3 lessonsstart here
Lesson 1.1
What an operating system does
Starts from zero. This is the very beginning.
By the end you'll understand
what sits between the apps you use and the actual machine
what the kernel is, and why you never see it
why an operating system is a manager, not an app
The big idea
A computer, underneath everything, is just hardware: a chip that does math really fast, memory that holds numbers, a disk that stores your stuff, a screen, a keyboard, a network card. On its own, all of that does... nothing. It's like a workshop full of tools with nobody in it. Something has to pick up the tools, and something has to make sure two people don't grab the same hammer at the same time.
That something is the operating system. It's the layer that sits between your programs and the metal. When your game wants to save your progress, or your browser wants to show a picture, or a song wants to come out of the speaker, the program doesn't touch the disk or the speaker itself. It asks the operating system, and the operating system does it and hands back the result.
Think of it like the manager of a big apartment building. You live there, but you don't run your own water pipes or wire your own electricity. You flip a switch, and the building's manager has quietly handled all the plumbing behind the wall... and made sure your neighbor can't wander into your apartment while they're at it.
Everything a program wants from the machine has to pass through the kernel. Nobody skips the manager.
How it works
The heart of the operating system is the kernel. It's the part that's always running, closest to the hardware, and it never takes a break. It does a few jobs the whole time the computer is on: it shares the chip between every running program so each one gets a turn (that's why everything feels like it's happening at once), it hands out memory and keeps each program in its own space so one can't scribble on another, and it talks to the devices... keyboard, screen, disk... through little translators called drivers.
You never actually see the kernel, and that's the whole point. A program quietly asks for something, the kernel does it, and life goes on. When people say the word "Linux," the kernel is the part that literally is Linux. Everything else is built around it... which is exactly what the next lesson is about.
Watch out
The operating system is not the desktop wallpaper or the row of app icons. Those are just programs sitting on top. The real OS is underneath, out of sight.
You don't launch the kernel. It's already running before anything you tap, and it's the last thing standing when everything closes.
Check yourself
1
When a game saves your progress, why doesn't it just write to the disk itself?
2
Name two things the kernel is doing right this second without anyone asking it to.
3
If the operating system is doing a great job, how much of it should you notice?
Next up → Lesson 1.2: What Linux is, and what a "distribution" is
Lesson 1.2
What Linux is, and what a "distribution" is
Builds on 1.1.
By the end you'll understand
what the word "Linux" actually points at
why there are hundreds of "Linuxes" and how they're related
how to make sense of names like Ubuntu, Debian, Arch and Fedora
The big idea
Here's a surprise: strictly speaking, Linux is just the kernel... that busy manager from the last lesson, and nothing else. And a kernel all by itself isn't something you can sit down and use. It needs a way to type commands, a set of tools, a way to install new software, usually a desktop to click around in. All of that has to be gathered up and packaged together.
That package is called a distribution, or "distro" for short. So Ubuntu, Debian, Arch and Fedora are all "Linux" because they share the exact same kind of engine underneath... but they differ in what's wrapped around it and how it's all put together.
Same engine, three different cars built around it. None is the "real" Linux... they're all the same engine, dressed differently.
How it works
Distros differ in mostly three ways. First, how you get new software. Each one has a "package manager" that installs programs from its own big collection... the Ubuntu family uses one called apt, Arch uses pacman, Fedora uses dnf. Second, how new versus how steady. Some distros give you the very newest versions the day they come out, others freeze a tested set and change slowly, trading shiny-and-new for rock-solid. Third, how much is decided for you. Some hand you a finished desktop with everything ready; others hand you a bare system and let you build it up piece by piece.
One shortcut: families matter more than names. Ubuntu is built on top of Debian, so almost everything you learn on one works on the other. A lot of smaller distros are really just Ubuntu with a different coat of paint. Learn one family and you're most of the way to all its cousins.
Watch out
When people say "Linux" in normal talk, they usually mean "a Linux distribution," not the bare kernel. Both are fine... you just tell which from the sentence.
Online, people argue about which distro is best like it's a personality test. It mostly doesn't matter. The big ideas in this book carry across all of them; the hands-on examples use the Ubuntu/Debian family (Bash, systemd, apt, and later ufw), and a few commands and file paths differ on other distributions.
There are more distros that are alike than different. Don't let the huge list scare you.
Check yourself
1
What's the difference between "Linux the kernel" and "a Linux distribution"?
2
Ubuntu is built on Debian. Why does that mean their commands are mostly the same?
3
Name the three main ways distros differ from each other.
Next up → Lesson 1.3: Getting a shell
Lesson 1.3
Getting a shell
Builds on 1.1 and 1.2. This is where you finally get your hands on it.
By the end you'll understand
what a terminal and a shell are, and how they're different
how to read the prompt
how to run your first commands and read what comes back
The big idea
There are two ways to tell a computer what to do: you can point and click, or you can type a command. The shell is the type-a-command way. It's a program that waits for you to type an instruction, runs it, shows you what happened, and then waits again.
The terminal is just the window the shell lives inside. Terminal = the glass. Shell = the voice behind the glass. People mix the two words up all the time, but that's the real split. And here's the friendly part: it's a conversation. You type a line, press Enter, the computer does the thing and answers, and then it's your turn again. Nothing happens until you press Enter... the shell politely waits for you to finish your sentence.
The prompt is the shell telling you about itself: who you are, what machine you're on, where you are, and that it's ready. The $ means "go."
How it works
When the shell is ready for you, it prints a prompt and just sits there waiting. Read it left to right: your username, then the machine's name, then where you are in the files (the ~ means your home folder... much more on that in Module 2), and finally a symbol that means "your turn." That symbol is usually a $. If you ever see a # there instead, it means you're acting as root... the all-powerful admin, the building manager holding the master key. That's powerful and a little dangerous, so we save it for Module 5.
You type, you press Enter, the shell runs your command and prints whatever it has to say, then shows the prompt again. And here's a habit worth learning early: if a command works but has nothing to report, it often says nothing at all and just gives you a fresh prompt. In this world, silence usually means success.
See it for real
Open your terminal and try these one at a time, pressing Enter after each. Watch how the last two prove the first two lessons, straight from your own machine:
sam@turtle:~$ whoami # who am I?
sam
sam@turtle:~$ hostname # what machine?
turtle
sam@turtle:~$ pwd # where am I right now?
/home/sam
sam@turtle:~$ uname -s # the kernel's name (that's Lesson 1.1!)
Linux
sam@turtle:~$ cat /etc/os-release # which distro? (Lesson 1.2!)
NAME="Ubuntu"
VERSION="24.04 LTS"
sam@turtle:~$ echo hello # the shell repeats you back
hello
sam@turtle:~$▌
See it? uname told you the kernel is Linux, and os-release told you which distribution is wrapped around it. The two ideas from the last two lessons, confirmed by the machine itself.
Watch out
Terminal vs shell: the window is the terminal, the program reading your typing is the shell. "Open a terminal" and "drop to a shell" mean the same practical thing... get to that prompt.
Nothing runs until you press Enter. A half-typed command just sits there.
A command that prints nothing usually worked. Linux almost never says "done!" No news is good news.
Capitals matter. whoami is a command; WhoAmI is not.
Check yourself
1
In sam@turtle:~$, what do sam, turtle, and $ each tell you?
2
You run a command and it prints nothing, just a fresh prompt. Did it fail?
3
What's the difference between the terminal and the shell?
Next module → Module 2: The shape of the system ... one giant tree, and the strange idea that everything is a file.
Module 1 of Linux for the Curious Kid (and Grown-Up). Every diagram is drawn from the real thing... nothing here is pretend. When you're ready, Module 2 gets you moving around the system for real.
Linux for the Curious Kid (and Grown-Up) · Book One
Module 2: The shape of the system
One giant tree, how you walk around it, and the strange, wonderful idea that almost everything in Linux is a file... even a running program.
4 lessonsget your hands dirtybuilds on Module 1
Lesson 2.1
The single tree
Builds on Module 1.
By the end you'll understand
why Linux has no C: or D: drives... just one tree
what the / at the very top means
how any spot in the system gets a name (a "path")
The big idea
If you've used Windows, you know drives get letters... C:, D:, a USB stick becomes E:. Linux doesn't do that. There is one tree, and it starts at a single point called the root, written as just a forward slash: /. Every single file, every folder, every disk, every device... all of it hangs somewhere off that one tree. Plug in a USB stick and it doesn't become a new drive letter. It shows up as a folder somewhere in the same tree (we say it gets "mounted").
Picture an upside-down tree. The root / is the trunk at the very top, and everything branches downward from there. There's exactly one trunk, and every leaf can be reached by following branches down from it.
One tree, one root. Everything... your files, the settings, the programs, even the disks and devices... hangs somewhere off /.
How it works
Off the root hang a handful of branches that are nearly the same on every Linux system. /home holds everyone's personal folders (yours is /home/yourname). /etc holds settings. /usr and /bin hold installed programs and core commands. /dev holds the devices (much more on that in 2.3). /var holds things that keep changing, like logs. A spot in the tree is named by its path... start at / and follow the branches, like /home/sam/notes.txt. Because there's only one tree, there's exactly one way to name any spot.
See it for real
sam@turtle:~$ ls / # the top branches of the tree
bin dev etc home tmp usr var
sam@turtle:~$ ls /home # everyone's personal folders
sam
sam@turtle:~$ cat /etc/hostname # one real settings file
turtle
sam@turtle:~$▌
Watch out
No drive letters, ever. A second disk or a USB stick is "mounted" into the tree as a folder, not given its own letter.
Two different things are both called "root": the root of the tree (/) and root the user (the admin from Module 1). Same word, different things... you'll always tell from context.
Check yourself
1
How many roots does a Linux system's file tree have?
2
Where do your personal files live? Write the path.
3
If there are no drive letters, where does a plugged-in USB stick show up?
Next up → Lesson 2.2: Navigating
Lesson 2.2
Navigating
Builds on 2.1.
By the end you'll understand
that you're always standing somewhere in the tree
three commands to move around: pwd, ls, cd
the difference between an absolute path and a relative one
The big idea
At any moment you're standing in one spot in the tree, called your working directory. Three little commands are your feet and eyes. pwd asks "where am I?" ls asks "what's here?" cd says "go there." That's most of moving around Linux, right there.
Think of walking through a building. pwd reads the sign that says which room you're in, ls looks around the room, and cd walks you through a door into another one.
A path that starts with / is absolute (measured from the root). Anything else is relative to where you're standing.
How it works
pwd prints your current spot, like /home/sam. ls lists what's in it. cd moves you. There are two ways to say where to go. An absolute path starts at the root / and gives the whole route, so cd /etc lands you in /etc no matter where you were. A relative path starts from where you already are, so cd Documents steps into the Documents folder inside your current spot. Three handy shorthands: . means "here", .. means "one level up" (cd .. steps back toward the root), and ~ means your home folder... cd with nothing at all also takes you home from anywhere.
See it for real
sam@turtle:~$ pwd # where am I? (~ is home)
/home/sam
sam@turtle:~$ cd /etc # absolute jump
sam@turtle:/etc$ pwd # the prompt changed too
/etc
sam@turtle:/etc$ cd # nothing = go home
sam@turtle:~$ cd .. # up one level
sam@turtle:/home$▌
Watch out
Absolute vs relative is the thing beginners trip on: starts with / means absolute (from the root); anything else is relative (from here).
cd with nothing goes home. .. is up, . is right here.
Watch the prompt... it usually shows where you are, so you always know your spot.
Check yourself
1
What does a path starting with / mean, versus one that doesn't?
2
You're in /home/sam. What does cd .. do?
3
How do you get home from anywhere, in one short command?
Next up → Lesson 2.3: Everything is a file
Lesson 2.3
Everything is a file
Builds on 2.1 and 2.2.
By the end you'll understand
why Linux shows disks, devices, and system info as "files"
what a special file is, and why /dev and /proc aren't ordinary folders
why learning a few verbs lets you talk to almost the whole system
The big idea
Here's Linux's founding trick. Nearly everything the system exposes... a note you typed, a folder, a hard disk, your keyboard, even information straight from the kernel... is handed to you as a file: a named thing you can open, read bytes from, and write bytes to. So instead of a different tool for every kind of thing, you learn one small set of verbs and they work almost everywhere. One universal socket, not fifty different adapters.
The same verbs... open, read, write... reach a saved document, a raw disk, a throwaway sink, and information the kernel invents the instant you ask.
How it works
Everything hangs off the single tree from 2.1, and among the ordinary files are special ones that aren't stored bytes at all. /dev/sda is a whole disk. /dev/null is a sink that swallows anything you write to it. /proc and /sys are live windows the kernel opens onto itself and the hardware. When you "read" /proc/cpuinfo, nothing was sitting on a disk waiting... the kernel generates the answer the instant you ask and hands it back through the file interface. Same verb, wildly different thing behind it.
See it for real
sam@turtle:~$ cat /proc/uptime # seconds since boot, made live
sam@turtle:~$ cat /proc/cpuinfo # your CPU, generated on demand
processor : 0
model name : ...
sam@turtle:~$▌
Watch out
"File" here does not mean "a document on a disk." Lots of files have no bytes stored anywhere... they're an interface, not a saved thing.
/dev, /proc and /sys look like folders of files, but they're mostly made live by the kernel. Some /sys files change hardware when you write to them, so look before you leap.
It's a slogan, not a law. A few things (like network connections) don't quite fit... and that's fine.
Check yourself
1
Why does reading /proc/uptime give a different answer every time, with nothing saved on a disk?
2
You're told a temperature sensor shows up at /sys/.../temp. How would you read it, with tools you already know?
3
What's fundamentally different between reading /etc/hostname and reading /proc/uptime?
Next up → Lesson 2.4: Processes are files too
Lesson 2.4
Processes are files too
Builds on 2.3. The payoff of "everything is a file."
By the end you'll understand
what a process is, and what a PID is
that every running program shows up in the tree, under /proc
how the shell can even read itself as a file
The big idea
A running program is called a process, and each one gets a number when it starts... its PID (process ID). Now the magic from the last lesson pays off: a running program isn't hidden away inside the machine. Linux shows each one as a folder under /proc, named by its PID. So you can peek inside a live, running program the exact same way you look at a file.
Every running program is mirrored, live, as a little folder of files under /proc... readable while it runs, gone the instant it stops.
How it works
Under /proc/<pid>/ sit live files describing that one process: status (is it running, how much memory), cmdline (the command that started it), fd/ (the files it currently has open). Like /proc/cpuinfo, these are made live, not stored. The command ps lists the processes running near you and their PIDs. And there's a lovely shortcut: $$ is the shell's own PID, so the shell can literally read itself as a file.
See it for real
sam@turtle:~$ ps # programs running in this terminal
PID TTY TIME CMD
2048 pts/0 00:00:00 bash
2143 pts/0 00:00:00 ps
sam@turtle:~$ echo $$ # the shell's own PID
2048
sam@turtle:~$ cat /proc/$$/comm # the shell reads itself
bash
sam@turtle:~$▌
Watch out
PIDs aren't fixed. The same program gets a different number every time it starts.
A /proc/<pid> folder exists only while that program runs. When the program stops, its folder vanishes.
Reading a process is safe and fun. Stopping one (with commands we meet later) is powerful... don't go stopping random PIDs yet.
Check yourself
1
What is a PID, and does a program keep the same one forever?
2
Where in the tree does a running program show up?
3
What happens to /proc/1234 when program 1234 stops?
Next module → Module 3: Working with files ... looking, making, moving, finding, and the wildcards that let one command touch many files at once.
Module 2 of Linux for the Curious Kid (and Grown-Up). Every command here is real and safe to try. You can now find your way around the whole system... next we start changing things in it.
Linux for the Curious Kid (and Grown-Up) · Book One
Module 3: Working with files
Looking inside files, making and moving them, hunting them down, and the one trick... wildcards... that lets a single command touch a hundred files at once.
4 lessonsyou start changing thingsbuilds on Module 2
Lesson 3.1
Looking inside files
Builds on Module 2.
By the end you'll understand
four ways to read a file without opening a heavy app
when to use cat, less, head, and tail
how to get out of less (the classic beginner trap)
The big idea
You can find your way around now. Next: look inside a file straight from the shell. Four small tools, each for a different job. cat dumps the whole file to the screen. less opens a scrolling window you can page through and then quit. head shows just the first lines. tail shows just the last lines.
Picture a long roll of paper. cat unrolls the whole thing on the floor at once. less holds a window over it that you slide up and down. head peeks at the top, tail at the bottom.
Same file, four lenses. Reach for cat on something short, less on something long, and head/tail when you only want an end.
How it works
cat file prints the whole thing... perfect for short files, a firehose for long ones. less file opens a pager: arrow keys or space to move, /word to search, and q to quit. It only views, it never changes anything. head file shows the first 10 lines (head -n 3 for three); tail file shows the last 10 (tail -n 20 for twenty). One extra-handy trick: tail -f keeps a file open and prints new lines as they arrive... it's how people watch a log update live.
See it for real
sam@turtle:~$ cat /etc/hostname # short file, dump it all
turtle
sam@turtle:~$ head -n 2 /etc/os-release # just the top
NAME="Ubuntu"
VERSION="24.04 LTS"
sam@turtle:~$ tail -n 1 /etc/os-release # just the bottom
UBUNTU_CODENAME=noble
sam@turtle:~$ less /etc/services # scroll it, then press q
sam@turtle:~$▌
Watch out
cat on a 10,000-line file floods your screen. Use less for anything long.
Stuck in less? Press q. That's the exit. (Everyone gets trapped once.)
less and friends only look... they never change the file.
Check yourself
1
Which tool for a quick peek at just the first few lines?
2
You opened a file with less. How do you get out?
3
What does cat do to a giant file, and what should you use instead?
Next up → Lesson 3.2: Making and moving
Lesson 3.2
Making and moving
Builds on 3.1.
By the end you'll understand
how to make files and folders (touch, mkdir)
copy, move, and rename (cp, mv)
why rm is the one to respect: no undo, no trash
The big idea
Now you make and rearrange things. touch creates an empty file. mkdir makes a folder. cp copies. mv moves or renames (same command, because both just mean "give this thing a new path"). And rm deletes. Here's the one to burn into memory: rm has no undo and no Recycle Bin. Gone is gone.
Think of it physically. cp is a photocopy... the original stays and you get a duplicate. mv is carrying the thing to another room, or slapping a new label on it... nothing is left behind. rm is the shredder, and there's no bin to fish it back out of.
cp leaves a duplicate, mv relocates or relabels, and rm is permanent. Three verbs, three very different aftermaths.
How it works
touch notes.txt makes an empty file. mkdir project makes a folder, and mkdir -p a/b/c makes a whole chain at once. cp file copy.txt duplicates a file; cp -r folder folder2 copies a folder and everything in it. mv old.txt new.txt renames; mv file.txt project/ moves it into a folder. rm file.txt deletes a file; rm -r folder deletes a folder and all its contents. That -r ("recursive") is exactly the flag that makes rm something to type slowly.
See it for real
sam@turtle:~$ mkdir sandbox # a folder to play in
sam@turtle:~$ touch sandbox/hello.txt # an empty file
sam@turtle:~$ rm sandbox/renamed.txt # gone for good
sam@turtle:~$▌
Watch out
rm has no undo and no trash. Read the line twice before you press Enter, especially with -r or a * (next lesson).
mv onto an existing name overwrites it silently. Check the target isn't something you want.
Copying or deleting a folder needs -r. That flag is the difference between "one file" and "everything inside."
Check yourself
1
After cp versus after mv, what's left in the original spot?
2
Where does a file go when you rm it?
3
Why is rm -r something to type carefully?
Next up → Lesson 3.3: Finding things
Lesson 3.3
Finding things
Builds on 3.2.
By the end you'll understand
how find searches the tree for you
the two things it always needs: where and what
that it can match on more than a name... age, size, type
The big idea
When you don't know where something is, find hunts for it. You point it at a spot in the tree and it walks every branch from there downward, printing the full path of everything that matches. Point it at a branch and say "search everything from here down."
find walks the whole subtree from where you point it, and prints the ones that match. Here, only the .txt files light up.
How it works
The shape is find <where> <what>. find . -name "*.txt" means "from here (.) down, every file ending in .txt." find /home -name "notes.txt" searches all of /home. It can match more than names: -type d finds only folders, -mtime -1 finds things changed in the last day, -size +100M finds things bigger than 100 MB. Quote the pattern ("*.txt") so find handles the wildcard itself... exactly why will make sense after the next lesson.
See it for real
sam@turtle:~$ find . -name "*.txt" # from here down
./sandbox/hello.txt
sam@turtle:~$ find /etc -name "hostname" # search all of /etc
/etc/hostname
sam@turtle:~$ find . -type d # only folders
.
./sandbox
sam@turtle:~$▌
Watch out
find / searches the entire system... slow and noisy. Start as specific as you can (. or your home).
Quote the pattern ("*.txt") so find expands it, not the shell. Next lesson shows why that matters.
find is case-sensitive by default; use -iname to ignore capitals.
Check yourself
1
What two things does find always need?
2
How would you search only from your home folder downward?
3
Besides names, name one other thing find can match on.
Next up → Lesson 3.4: Wildcards, and the shell's best trick
Lesson 3.4
Wildcards, and the shell's best trick
Builds on 3.3. This one changes how you see every command.
By the end you'll understand
what * and ? match
the big secret: the shell expands wildcards before the command runs
how to preview a wildcard safely (so you never rm the wrong thing)
The big idea
A wildcard lets one command touch many files. * means "any run of characters" and ? means "any single character." But here's the secret that changes how you read every command you'll ever type: the shell replaces the wildcard with the real matching filenames before the command runs. The command never sees the *... it only ever sees the finished list.
The shell is a translator standing between you and the command. You say ls *.txt. The translator looks in the folder, swaps *.txt for the real names, and only then hands the command ls a.txt b.txt c.txt. As far as ls knows, you typed the names out yourself.
The * is gone before ls even starts. That single fact explains why echo *.txt is a safe dry-run... and why a wildcard with rm is so powerful.
How it works
* matches any number of characters, including none: *.txt is every .txt, report* is anything starting with "report", * is everything. ? matches exactly one character: file?.txt matches file1.txt and fileA.txt, but not file10.txt. Because the shell expands first, echo *.txt just prints the names a wildcard would match... which makes it the perfect dry-run before you do anything destructive. And it's why rm *.txt is powerful: the shell hands rm every matching file at once.
See it for real
sam@turtle:~$ ls sandbox
a.txt b.txt notes.md
sam@turtle:~$ echo sandbox/*.txt # preview: what would * match?
sandbox/a.txt sandbox/b.txt
sam@turtle:~$ ls sandbox/*.txt # ls only ever sees the two names
sandbox/a.txt sandbox/b.txt
sam@turtle:~$▌
Watch out
Preview before you destroy: run echo (or ls) with the same pattern first to see exactly what a wildcard will hit, then use it with rm.
Mind the space. rm *.txt deletes the .txt files; rm * .txt (with a space) means "delete everything, plus a file called .txt." A space changes everything.
If nothing matches, the shell often leaves the * as a literal *, which can surprise a command. Quote a pattern ("*.txt") when you want the command, not the shell, to handle it.
Check yourself
1
In ls *.txt, who replaces the * with filenames... the shell, or ls?
2
How can you preview exactly what a wildcard will match before deleting anything?
3
Why is a stray space in rm * .txt dangerous?
Next module → Module 4: The shell as a language ... the three streams, and the pipes that let you snap small tools together into big ones.
Module 3 of Linux for the Curious Kid (and Grown-Up). You can now look, make, move, find, and reach many files at once... the real working vocabulary of a shell. Next, we make those tools talk to each other.
Linux for the Curious Kid (and Grown-Up) · Book One
Module 4: The shell as a language
Every command has a grammar. Every program has three channels. And a single character... the pipe... lets you snap small tools into big ones. This is where Linux starts to feel like power.
4 lessonsthe real superpowerbuilds on Module 3
Lesson 4.1
Commands, arguments, options
Builds on Module 3.
By the end you'll understand
the grammar every command line shares
what options (the -flags) and arguments are
how one command changes behavior with different flags
The big idea
You've typed a lot of commands now. Here's the secret: they all share the same grammar. There's the command (what to run), the options (how to run it... the little dash-flags), and the arguments (what to run it on). Learn to see those three parts and every new command becomes easy: just ask "what's the verb, what are the settings, what's it acting on?"
It's a sentence. The command is the verb, the options are adverbs (how to do it), and the arguments are the object (what to do it to). ls -lh /home reads as "list, in long human-friendly form, the folder /home."
Command, options, argument. Once you can spot the three parts, every command you meet fits the same shape.
How it works
The command comes first. Options start with a dash: short ones are a single letter (-l, -a), and you can bundle them (-la is the same as -l -a). Long options use two dashes and a whole word (--all, --human-readable) and read more clearly. Arguments are the things it acts on, usually files or folders. Spaces separate every part, which matters: a filename with a space in it needs quotes ("my notes.txt"), or the shell reads it as two separate arguments.
See it for real
sam@turtle:~$ ls # bare command
sandbox notes.txt
sam@turtle:~$ ls -l # -l: long listing (more detail)
drwxr-xr-x 2 sam sam 4096 Sep 23 09:14 sandbox
sam@turtle:~$ ls -lh /etc # same command, two options + an argument
drwxr-xr-x 7 root root 4.0K Sep 18 09:02 apt
-rw-r--r-- 1 root root 7 Sep 12 14:20 hostname
-rw-r--r-- 1 root root 2.9K Aug 1 11:07 passwd
drwxr-xr-x 2 root root 4.0K Sep 10 08:15 ssh
... and dozens more; /etc is a big folder
sam@turtle:~$▌
Watch out
Options start with a dash; short ones bundle (-la). Long ones (--all) are just easier to read.
--help is itself an option that almost every command understands (Lesson 4.4).
Spaces split the parts, so a filename with a space needs quotes: ls "my notes.txt", not ls my notes.txt.
Check yourself
1
In ls -lh /home, which part is the command, which the option, which the argument?
2
What does bundling -l and -a into -la do?
3
How do you run a command on a filename that has a space in it?
Next up → Lesson 4.2: The three streams
Lesson 4.2
The three streams
Builds on 4.1.
By the end you'll understand
the three channels every program is born with
why errors get their own separate channel
where input comes from by default
The big idea
Every running program comes with three channels. Standard input (stdin) is where it reads from. Standard output (stdout) is where its normal results go. Standard error (stderr) is where its complaints go... kept on a separate channel on purpose. By default, input comes from your keyboard and both outputs land on your screen, so you never notice they're separate... until you start sending them to different places, which is the next lesson.
Picture a machine with one intake chute and two output chutes: one for good product, one for scrap. Keeping the scrap chute separate means a problem never gets mixed into the good output... you can catch it on its own.
One way in, two ways out. Results and errors travel on different channels, which is what lets you handle them separately.
How it works
The channels even have numbers: stdin is 0, stdout is 1, stderr is 2. A program reads from stdin, writes results to stdout, and writes error messages to stderr. Normally both outputs land on your terminal, mixed together, so you don't see the seam. But because they're separate channels, you can send them to different places (next lesson): keep the results, set the errors aside, or the reverse. That separation is why a command can flash an error on your screen even while its real output is quietly flowing somewhere else.
See it for real
sam@turtle:~$ echo hello # normal result → stdout
hello
sam@turtle:~$ ls /nope # a complaint → stderr
ls: cannot access '/nope': No such file or directory
sam@turtle:~$▌
Watch out
Errors ride a separate channel (stderr) even though they look mixed in on screen. That's the whole point... it lets you treat results and errors differently.
stdin defaults to the keyboard. Some commands seem to "hang" because they're waiting for you to type... Ctrl-D ends input, Ctrl-C cancels.
Check yourself
1
Name the three streams and what each is for.
2
Why keep errors on their own channel instead of mixing them into the results?
3
Where does stdin come from by default?
Next up → Lesson 4.3: Pipes and redirection
Lesson 4.3
Pipes and redirection
Builds on 4.2. This is the superpower.
By the end you'll understand
how the pipe | snaps small tools into big ones
how to send output into a file with > and >>
why > is the one to aim carefully
The big idea
Because output and input are just channels, you can re-route them. The pipe, written |, takes one program's stdout and feeds it straight into the next program's stdin. So instead of one giant do-everything tool, Linux gives you lots of small sharp tools and lets you snap them together in a row. That's the whole Unix idea, and it's genuinely where the power lives.
Think of a conveyor belt. Each little machine does one job and drops its result on the belt, which carries it to the next machine. ls lists, grep keeps the matching lines, wc -l counts them... line them up and you've built a "count the txt files" machine out of three simple parts.
A pipe | carries one tool's output into the next. Three simple tools in a row become one that counts your .txt files.
How it works
cmd1 | cmd2 sends cmd1's stdout into cmd2's stdin, and you can chain as many as you like: ls | grep txt | wc -l means "list everything, keep the lines with txt, count them." To send output into a file instead of the screen, use redirection: ls > files.txt writes the list into a file (replacing whatever was there), and ls >> files.txt adds to the end instead. You can even redirect just the errors: command 2> errors.txt (remember, stderr is channel 2).
See it for real
sam@turtle:~$ ls /etc | wc -l # count things in /etc
241
sam@turtle:~$ ls /etc | grep conf | wc -l # count the "conf" ones
>overwrites the whole file, silently. Aim it at the wrong file and its old contents are gone. Use >> when you mean "add to the end."
A plain pipe carries stdout only. Errors (stderr) still hit your screen unless you redirect them too.
Order matters in a pipeline: each stage only ever sees what the stage before it passed along.
Check yourself
1
What does | do between two commands?
2
What's the difference between > and >>?
3
Why is > something to aim carefully?
Next up → Lesson 4.4: Reading the manual
Lesson 4.4
Reading the manual
Builds on 4.1 and 3.1. The skill that makes you self-sufficient.
By the end you'll understand
that every command carries its own instructions
man versus --help
how to read a SYNOPSIS line (the part that trips everyone up)
The big idea
Nobody memorizes all of this... not even the pros. The manual is built right in. man <command> opens the full manual page for any command; <command> --help gives a quick summary. The real skill isn't memorizing commands, it's learning to read their manuals... and the one line worth learning to read is the SYNOPSIS.
Every command came with its own little instruction booklet already in the box. You just have to flip it open and know how to read the first page.
The SYNOPSIS is the grammar from Lesson 4.1, written in shorthand: [ ] is optional, ... means "you can repeat this." ls [OPTION]... [FILE]... = ls, then any options, then any files.
How it works
man ls opens the manual for ls. It opens inside less, so you scroll with the arrows and press q to quit... exactly the pager you met in Lesson 3.1. A man page always has the same sections: NAME (a one-line summary), SYNOPSIS (how to call it), DESCRIPTION, OPTIONS, and often EXAMPLES. For a quick reminder rather than the whole booklet, most commands take --help. And if you don't even know the command's name, apropos <word> searches for commands about a topic.
See it for real
sam@turtle:~$ ls --help | head # quick summary (piped to head so it fits!)
Usage: ls [OPTION]... [FILE]...
List information about the FILEs ...
sam@turtle:~$ man ls # the full manual (press q to quit)
sam@turtle:~$ apropos calendar # find commands about a topic
cal (1) - display a calendar
sam@turtle:~$▌
Watch out
man opens in less, so q quits (that trap again from 3.1).
In a SYNOPSIS, [ ] means optional and ... means "one or more." Reading that line is half the skill.
--help is faster for a quick reminder; man is the full story. If man x says "no manual entry," try x --help.
Check yourself
1
What does man do, and how do you get back out of it?
2
In a SYNOPSIS line, what do [ ] and ... mean?
3
What's a faster way to get a quick reminder than opening the full manual?
Next module → Module 5: You, the user ... ownership, the read/write/execute permissions, and the careful power of root and sudo.
Module 4 of Linux for the Curious Kid (and Grown-Up). You can now read a command's grammar, follow its three streams, pipe small tools into big ones, and look up anything you forget. That last one matters most... it means you never have to memorize, only understand.
Linux for the Curious Kid (and Grown-Up) · Book One
Module 5: You, the user
Linux always knows who you are... and it uses that to keep your stuff yours and the system safe. Ownership, the read/write/execute switches, and the careful power of root and sudo.
3 lessonscloses the foundationsbuilds on Module 4
Lesson 5.1
Users and ownership
Builds on Module 4.
By the end you'll understand
why Linux always tracks who you are
that every file has an owner and a group
why your home is yours and the system is protected
The big idea
Linux was built for many people sharing one machine, so it always keeps track of who. You are a user (back in Lesson 1.3, whoami answered with your name). Every file carries an owner (a user) and a group, and before the system lets anyone touch a file, it checks who they are. That's the whole reason your files stay yours and the system's files stay safe from an accidental bump.
Remember the apartment building from Lesson 1.1? Each tenant has their own apartment (your home folder, /home/sam) and a key to it. You can't wander into a neighbor's apartment, and you certainly can't get into the building's utility room... that belongs to the manager, root, who we'll meet at the end of this module.
Every file carries an owner and a group. The kernel checks who you are before it lets you change anything... so your files are yours, and root's files are safe from you.
How it works
Each user has a name, and the system knows your identity the moment you log in. whoami prints it; id shows your user plus the groups you belong to. In the long listing from Lesson 4.1, those two names in the middle (sam sam) are the file's owner and its group. Groups let several people share access to the same thing... a "family" group, say. Your home folder is owned by you; most of the rest of the tree is owned by root and merely readable by you.
Those two names in ls -l are owner first, then group.
You own your home; root owns most everything else.
Being a "user" isn't about the screen you see... it's an identity the kernel checks on every single access.
Check yourself
1
What two ownership labels does every file carry?
2
Who owns your home folder, and who owns /etc?
3
Which command tells you who you are and what groups you're in?
Next up → Lesson 5.2: Permissions... read, write, execute
Lesson 5.2
Permissions: read, write, execute
Builds on 5.1. The one string every Linux user learns to read.
By the end you'll understand
the three things you can do to a file: read, write, execute
the three audiences: owner, group, everyone else
how to read that -rw-r--r-- string at the front of ls -l
The big idea
Ownership (last lesson) answers "whose." Permissions answer "who's allowed to do what." There are three actions... read (r, look at it), write (w, change it), and execute (x, run it). And there are three audiences... the owner, the group, and everyone else. So every file carries nine little yes/no switches: read-write-execute for each of the three audiences. That whole cryptic -rw-r--r-- at the front of ls -l? It's just those switches, written out.
Ten characters, four groups: the type, then read/write/execute for owner, group, and others. A dash means "no." Here the owner can read and write; everyone else can only read.
How it works
Read -rw-r--r-- left to right. The first character is the type: - for a file, d for a directory. Then three triads: owner rw- (read and write, no execute), group r-- (read only), others r-- (read only). A program or script needs the x switch or it won't run... chmod +x hello.sh flips it on. Those numbers you'll see (like 644 or 755) are just the triads written as digits, adding up r=4, w=2, x=1: so rw- is 6, r-- is 4, giving 644. On a directory, x has a special meaning: "you're allowed to enter it."
See it for real
sam@turtle:~$ ls -l note.txt
-rw-r--r-- 1 sam sam 84 Sep 23 note.txt
sam@turtle:~$ chmod +x hello.sh # make it runnable
sam@turtle:~$ ls -l hello.sh # the x switches appear
-rwxr-xr-x 1 sam sam 40 Sep 23 hello.sh
sam@turtle:~$ ls -ld sandbox # a folder: leading d
drwxr-xr-x 2 sam sam 4096 Sep 23 sandbox
sam@turtle:~$▌
Watch out
The very first character is the type (- file, d directory), not a permission.
A script won't run without x: chmod +x is the fix. On a folder, x means "may enter."
Don't chmod 777 things "to make it work"... that hands write access to everyone. We'll cover security later, but the good habit starts now.
Check yourself
1
In -rw-r--r--, what can the owner do, and what can everyone else do?
2
How do you make a script runnable?
3
What does the very first character of the string tell you?
Next up → Lesson 5.3: root and sudo
Lesson 5.3
root and sudo
Builds on 5.2, and closes the foundations.
By the end you'll understand
why you're deliberately not all-powerful
what root is, and what sudo does
why the prompt shows # instead of $ sometimes
The big idea
Here's a feature disguised as a limitation: as a normal user, you can't change most of the system. Almost everything outside your home is owned by root, the all-powerful admin identity... the building manager with the master key, from Lesson 1.1. That's on purpose. It means you can't accidentally wreck the machine. When you genuinely need to do an admin job... install software, edit a system file... sudo lets you borrow root's power for that one command, then hands it back.
root is the master key, and you don't carry it around... too easy to do damage. sudo is checking that key out from the front desk for a single job, signing for it with your own password, and returning it the moment the command finishes. And the # prompt from Lesson 1.3? That's the sign you're holding the key right now.
Same command, two outcomes. Without sudo the system's files are off-limits; with it, you borrow root's power for exactly one command, then you're back to being you.
How it works
Your everyday commands run as you. A system-changing one gets refused with Permission denied, because you don't own those files. Put sudo in front to run that single command as root: sudo apt install ..., sudo nano /etc/hosts. It asks for your password (to confirm it's really you), runs the one command with full power, and then you're an ordinary user again. Full root shells exist, but people avoid living in them... running sudo one command at a time keeps every powerful action deliberate. And the prompt tells you which hat you're wearing: $ is you, # is root.
See it for real
sam@turtle:~$ cat /etc/shadow # the password file, not yours
cat: /etc/shadow: Permission denied
sam@turtle:~$ sudo cat /etc/shadow # asks YOUR password, then works
[sudo] password for sam:
sam@turtle:~$ whoami
sam
sam@turtle:~$ sudo whoami # that one command ran as root
root
sam@turtle:~$▌
Watch out
sudo is real power aimed at the whole system. Read the command before you run it, especially anything with rm or a wildcard (Module 3).
It asks for your password, not a separate root password.
"Permission denied" usually means "this needs sudo"... or it's genuinely not yours to touch. Don't live as root; sudo just the one thing that needs it.
Check yourself
1
Why aren't you root all the time... what's the safety benefit?
2
What does sudo do, and whose password does it ask for?
3
In the prompt, what does # mean instead of $?
That's the foundations. → Course 201: The System picks up here... how programs run, and who's allowed to do what.
Module 5 of Linux for the Curious Kid (and Grown-Up), and the close of Course 101. You now understand what the system is, its shape, how to work with files, how to speak the shell, and how it keeps you and itself safe. That's a real foundation... everything after this is building on ground you already stand on.
You've met processes as folders in /proc. Now you learn to see them as a living tree, to run things while keeping your shell, to watch the machine breathe, and to stop a program without breaking anything.
4 lessonsfirst module of 201builds on Course 101
Lesson 6.1
What a process really is
Builds on 2.4 (processes are files too) and 5.1 (users).
By the end you'll understand
what a process is made of, beyond "a running program"
why every process has a parent, all the way up to PID 1
how to see the whole family tree of what's running
The big idea
A program sitting on disk is just a file. The moment you run it, the kernel makes a process: the program's code, its own private patch of memory, a PID, and a note about who started it. That "who started it" is the interesting part. No process appears out of nowhere... every one is spawned by another process, its parent. Your shell spawned ls. Something spawned your shell. Follow the chain up and it always ends at the same place: PID 1, the very first process the kernel starts at boot (on most systems that's systemd), the ancestor of everything.
So the whole machine is one family tree. Not a pile of programs... a tree, with one root.
Almost everything running is a descendant of PID 1. Your commands are children of your shell, which is a descendant of systemd.
How it works
Each process carries its own PID and its parent's PID (the PPID). ps shows the processes in your terminal; ps -e shows every process on the machine; ps -ef adds the parent for each one. pstree draws the whole thing as an actual tree, which is the fastest way to see the idea. When a parent exits, its children are handed up to be adopted... usually by PID 1... so nobody is ever orphaned. The kernel also gives each process its own memory that no other process can touch, which is why one crashing program doesn't take the rest down with it (that's the memory-guarding job from Lesson 1.1, now visible).
See it for real
sam@turtle:~$ ps -o pid,ppid,comm # me, my parent, my name
PID PPID COMMAND
2048 2041 bash
2210 2048 ps
sam@turtle:~$ pstree -p | head -n 5 # the tree, from PID 1 down
PID 1 is special. It's the first thing the kernel runs and the last thing standing. You never kill it... the system ends if it ends.
PIDs get reused. A number that meant one program yesterday can mean another today; always check the name beside it.
That process folder from Lesson 2.4 (/proc/<pid>) is exactly this... status in there lists the PPID.
Check yourself
1
What's the difference between a program on disk and a process?
2
Follow any process's parents upward. Where does the chain always end?
3
Which command draws the whole tree so you can see it?
Next up → Lesson 6.2: Foreground, background, and jobs
Lesson 6.2
Foreground, background, and jobs
Builds on 6.1 and 4.2 (the three streams).
By the end you'll understand
why a running command "takes" your prompt
how to run something in the background and keep working
the three states a job can be in, and the keys that move it between them
The big idea
When you run a command, the shell hands it your terminal... your keyboard and your screen... and waits. That's a foreground job: it has your attention, and the prompt doesn't come back until it's done. But the shell can also run a job in the background: it keeps running, the prompt comes straight back, and you carry on. And a job can be stopped... paused mid-flight, going nowhere until you say. Three states, and a handful of keys and commands to move a job between them.
Think of the shell as having one pair of hands. A foreground job is the thing it's holding. Putting a job in the background is setting it on the bench to keep running on its own. Stopping is pressing pause.
Three states, a few moves. A job you start normally is in the foreground; Ctrl-Z pauses it, bg sets it running behind you, fg pulls it back. Add & to start it in the background from the go.
How it works
Run command & and it starts in the background; the shell prints a job number and a PID and gives you the prompt straight back. jobs lists your background and stopped jobs. If something's hogging the foreground, Ctrl-Z stops it (pauses, doesn't kill); then bg lets it keep running in the background, or fg brings it back to the front. A job that writes to the screen will still splatter text over your prompt from the background... send its output somewhere with > (Lesson 4.3) if that bugs you. And a plain background job dies when you close the terminal; nohup command & lets it survive the disconnect... the crude ancestor of the tmux session you'll meet in 301.
See it for real
sam@turtle:~$ sleep 300 & # start it in the background
[1] 2301
sam@turtle:~$ jobs # what's running behind me?
[1]+ Running sleep 300 &
sam@turtle:~$ fg # bring it to the front (prompt is gone now)
sleep 300
^Z # Ctrl-Z: paused
[1]+ Stopped sleep 300
sam@turtle:~$ bg # let it run in the background again
[1]+ sleep 300 &
sam@turtle:~$▌
Watch out
Ctrl-Z pauses; Ctrl-C kills. Different keys, very different results.
A stopped job is frozen until you fg or bg it. Forget one and it sits there forever.
Background jobs still own your screen. Redirect their output, or expect text landing mid-prompt.
Check yourself
1
What does & at the end of a command do?
2
You pressed Ctrl-Z by accident. Is the program dead? How do you get it back?
3
Name the three states a job can be in.
Next up → Lesson 6.3: Watching the machine
Lesson 6.3
Watching the machine
Builds on 6.1.
By the end you'll understand
how to see what the machine is doing right now
how to read top: load, CPU, memory, the busiest processes
what "normal" looks like, so you can tell when something isn't
The big idea
top is a live window onto the machine, refreshing every couple of seconds: how busy the processor is, how much memory is in use, and which processes are eating the most. It's the first place to look when a box feels slow. But a screen of numbers is useless until you know what they mean... and the single most important habit is learning what normal looks like on your own machine, so an odd number stands out the moment it appears.
Four things to read on a top screen: the load average, how idle the CPU is, memory free versus used, and who's at the top of the process table.
How it works
The load average is three numbers: how busy the machine has been over the last 1, 5, and 15 minutes. The scale is per core: on a 4-core box, 4.0 means every core fully busy, 0.4 means mostly relaxed. The CPU line splits time into user programs (us), the kernel (sy), and idle (id)... high idle is a happy machine. The memory line is total, free, and used. Below all that, the process table, busiest first by default. Press M inside top to sort by memory instead, P for CPU, and q to quit. htop is the same idea with colour and mouse support, if it's installed.
See it for real
sam@turtle:~$ uptime # just the load, no live screen
sam@turtle:~$ nproc # how many cores is that load spread over?
8
sam@turtle:~$ free -h # memory, human sizes
total used free shared buff/cache available
Mem: 31Gi 5.9Gi 20Gi 410Mi 5.1Gi 25Gi
Swap: 2.0Gi 0B 2.0Gi
sam@turtle:~$ top # live view; M memory, P cpu, q to quit
sam@turtle:~$▌
Watch out
Load is per core. A load of 2.0 is alarming on one core and idle on eight. Check nproc before you judge a number.
"Free" memory being low isn't automatically bad... Linux uses spare memory as cache and gives it back when a program needs it. Look at "available" if free shows it.
Never call a number abnormal until you know what normal is on that machine. Watch it on a quiet day first.
Check yourself
1
The load average reads 3.0. Is that busy? What do you need to know first?
2
In the CPU line, what does a high id mean?
3
How do you sort top by memory, and how do you get out?
Next up → Lesson 6.4: Stopping things safely
Lesson 6.4
Stopping things safely
Builds on 6.1, 6.2 and 5.3 (sudo).
By the end you'll understand
that "killing" a process is really sending it a message
the difference between asking a program to stop and forcing it
why kill -9 is the last resort, not the first
The big idea
Despite the name, kill doesn't kill... it sends a signal, a short message the kernel delivers to a process. The everyday one is SIGTERM: "please stop." A well-behaved program catches it, finishes what it's doing, saves its work, closes its files, and exits cleanly. Only if that fails do you reach for SIGKILL (kill -9): the kernel ends the process on the spot, no message delivered, no chance to tidy up. It always works, and that's exactly why it's dangerous... half-written files and stuck locks are what you get when a program never got to say goodbye.
Think of it as the difference between knocking on a door and asking someone to leave, versus cutting the power to the building. The first is how you handle things. The second is for when the first didn't work.
Same target, two signals. kill asks and lets the program tidy up; kill -9 has the kernel end it with no warning. Try the first; keep the second for when it's ignored.
How it works
kill <pid> sends SIGTERM. Give the program a few seconds to honour it. If it's truly hung and won't respond, kill -9 <pid> sends SIGKILL, which cannot be caught or ignored. pkill <name> and killall <name> target by name instead of number, which is handy and also easy to over-aim. There are other signals too: kill -HUP tells many services to re-read their config without restarting, and Ctrl-C in a terminal is just SIGINT delivered by the shell. You can only signal your own processes; stopping someone else's, or a system service, needs sudo (Lesson 5.3).
See it for real
sam@turtle:~$ sleep 600 & # something to practise on
[1] 3120
sam@turtle:~$ pgrep sleep # find it by name
3120
sam@turtle:~$ kill 3120 # polite: SIGTERM
[1]+ Terminated sleep 600
sam@turtle:~$ pgrep sleep # nothing back = it's gone
sam@turtle:~$▌
Watch out
Polite first. kill, wait a beat, and only then kill -9. The forced kill skips the program's chance to save your work.
killall and pkill match by name... pkill python stops every python. Check with pgrep before you fire.
Never signal PID 1, and think twice before sudo killing anything you didn't start yourself.
Check yourself
1
What does kill actually do, if not "kill"?
2
What's the difference between SIGTERM and SIGKILL, and which should you try first?
3
Why is pkill python riskier than kill 3120?
Next module → Module 7: Permissions and ownership, in depth ... numeric modes, groups that actually do something, and the special bits.
Module 6 of Linux for the Curious Kid (and Grown-Up), the first module of Course 201. You can now see the machine as a tree of processes, run things without losing your shell, read what the box is doing, and stop a program the right way. The system has stopped being a black box.
You can read an rwx string. Now you learn to write one as a number, make groups actually do something, hand files to other people, and meet the three odd bits that make /tmp and passwd work.
4 lessonsbuilds on Module 5Course 201
Lesson 7.1
Modes by number
Builds on 5.2 (read, write, execute).
By the end you'll understand
why 644 and 755 keep turning up everywhere
how to turn any rwx triad into a digit, and back
the handful of modes you'll actually use
The big idea
In Lesson 5.2 you learned that each audience gets three switches: read, write, execute. Here's the trick that makes them fast to type: give each switch a weight... read is 4, write is 2, execute is 1... and add up the ones that are on. rw- is 4+2 = 6. r-x is 4+1 = 5. rwx is 4+2+1 = 7. Do that for owner, group, and others, and a whole permission string collapses into three digits. rw-r--r-- is just 644. That's the entire secret behind those numbers.
Three weights, three digits. Once r=4 w=2 x=1 is in your head you can read or write any mode in a second.
How it works
chmod 644 file sets the whole string at once, which is why numbers beat the +x-style letters once you're fluent. The ones you'll reach for: 644 for ordinary files (you edit, everyone reads), 755 for scripts and folders (runnable or enterable by all), 600 for private files like keys (only you, and only read/write), 700 for a private folder. New files don't start at 777 and get trimmed by hand... a setting called the umask quietly subtracts permissions from every new file, which is why fresh files usually land at 644 and fresh folders at 755 without you doing anything.
See it for real
sam@turtle:~$ chmod 600 secret.txt # only me, read and write
sam@turtle:~$ ls -l secret.txt
-rw------- 1 sam sam 120 Sep 23 secret.txt
sam@turtle:~$ chmod 755 run.sh # runnable by everyone
sam@turtle:~$ ls -l run.sh
-rwxr-xr-x 1 sam sam 88 Sep 23 run.sh
sam@turtle:~$ umask # what gets subtracted from new files
0022
sam@turtle:~$▌
Watch out
The digits are always in the order owner, group, others. 640 and 460 are very different files.
777 means everyone can do everything, including rewrite it. Almost never what you want... if something "needs 777 to work," something else is wrong.
On a folder, that x digit is what lets you enter it. A folder at 644 can be listed but not entered, which confuses everyone once.
Check yourself
1
Turn rwxr-x--- into a number. Then turn 640 back into a string.
2
Which mode would you give a private key file, and why?
3
Why does a fresh file usually appear as 644 without you setting it?
Next up → Lesson 7.2: Groups that do something
Lesson 7.2
Groups that do something
Builds on 5.1 (users and ownership) and 7.1.
By the end you'll understand
what a group is actually for
how to make one, put people in it, and give it a folder
the one gotcha that makes new group members "not work" until they log out
The big idea
Back in Lesson 5.1 every file had a group beside its owner, and the middle triad of the permission string was "what the group may do." Until now that's been a dead letter, because on a one-person machine your group is just... you. Groups earn their keep the moment two people share something. Make a group called family, put everyone in it, hand a folder to that group with rwx for the group... and now every member can create and read files there, while strangers can only look. That's the whole design: a group is a name for a set of people, so you can give permission to the set instead of one person at a time.
Give the folder to the group, give the group rwx, and every member can create and read files there. Anyone outside gets whatever the last digit allows... here, read only.
How it works
Making a group and filling it needs root: sudo groupadd family, then sudo usermod -aG family alex for each person (the -a means append... forget it and you replace all their groups with this one). groups or id shows who's in what. Then point a folder at the group with chgrp family /srv/shared and open it up with chmod 775 /srv/shared (or 770 to shut outsiders out entirely). The classic gotcha: group membership is read at login. Add someone to a group and their current shell doesn't know yet... they log out and back in (or run newgrp family) before it takes.
See it for real
sam@turtle:~$ sudo groupadd family # the set of people
sam@turtle:~$ sudo usermod -aG family sam # add yourself, too
sam@turtle:~$ sudo usermod -aG family alex # -a: append, don't replace
sam@turtle:~$ sudo usermod -aG family jo
sam@turtle:~$ sudo mkdir /srv/shared
sam@turtle:~$ sudo chgrp family /srv/shared # hand it to the group
sam@turtle:~$ sudo chmod 775 /srv/shared # group can work here
sam@turtle:~$ ls -ld /srv/shared
drwxrwxr-x 2 root family 4096 Sep 23 /srv/shared
sam@turtle:~$▌
Watch out
usermod -G without -areplaces someone's groups. That's how people accidentally lose their sudo group. Always -aG.
New membership doesn't apply to shells already open. Log out and in.
A file made inside a shared folder still gets the creator's group by default, not the folder's. Lesson 7.4 fixes that with one special bit.
Check yourself
1
What problem do groups solve that owner-and-others alone can't?
2
You added someone to a group and it "doesn't work." What's the first thing to check?
3
Why is the -a in usermod -aG so important?
Next up → Lesson 7.3: Giving things away with chown
Lesson 7.3
Giving things away with chown
Builds on 5.1, 5.3 (sudo) and 7.2.
By the end you'll understand
how to change a file's owner, its group, or both
why only root can hand a file to someone else
the one flag that makes chown dangerous
The big idea
chmod changes what people may do; chown changes whose the thing is. chown alex report.txt makes alex the owner. chown alex:family report.txt sets owner and group in one go. Here's the rule that surprises people: you can't give away a file you own. Only root can change who owns something. That's deliberate... if anyone could hand files to anyone, you could dump a giant file onto someone else's quota or make your mess look like theirs. So ownership changes go through sudo, on purpose.
One command moves ownership and group together. It runs under sudo because handing files to other people is a root-only power.
How it works
sudo chown user file changes the owner. sudo chown user:group file changes both; sudo chown :group file changes only the group (the same as chgrp). Add -R to walk a whole folder tree... which is the flag to type slowly. sudo chown -R sam /home/sam is a normal repair after you've accidentally run something as root in your own home. sudo chown -R sam / is how you turn a working system into a paperweight in one line, because half the system only works when root owns it.
See it for real
sam@turtle:~$ chown alex report.txt # as me: refused
chown: changing ownership of 'report.txt': Operation not permitted
sam@turtle:~$ sudo chown alex:family report.txt # as root: fine
sam@turtle:~$ ls -l report.txt
-rw-r--r-- 1 alex family 2048 Sep 23 report.txt
sam@turtle:~$▌
Watch out
-R is recursive. Check the path twice. A stray space or a / in the wrong spot and you've re-owned the wrong tree.
"Operation not permitted" on chown isn't a bug. You can't give away what you own; that's the point.
Changing the owner does not change permissions. A file at 600 is still locked to whoever now owns it.
Check yourself
1
What's the difference between chmod and chown?
2
Why can't you hand your own file to another user without sudo?
3
What does -R do, and why is it the flag to respect?
Next up → Lesson 7.4: The three special bits
Lesson 7.4
The three special bits
Builds on 7.1, 7.2 and 7.3. The strange letters in ls -l, explained.
By the end you'll understand
why everyone can write to /tmp but nobody can delete your files there
how a shared folder makes new files belong to the group automatically
why passwd can edit a root-only file when you run it
The big idea
Three extra switches sit above the nine you know, and each one solves a real problem. The sticky bit on a folder means "anyone can add files here, but only a file's owner can delete it"... that's /tmp, shared by everyone and safe anyway. The setgid bit on a folder means "every new file in here takes the folder's group, not the creator's"... the missing piece from 7.2, so files in a shared folder don't quietly fall back to each person's own group. And the setuid bit on a program means "run this as the file's owner, not as whoever launched it"... which is how passwd, owned by root, can change your password inside a root-only file while you stay a normal user.
Three bits, three jobs. Each shows up as an s or t in the permission string, and each is a fourth digit in front of the mode.
How it works
They're written as a fourth digit in front: 1 for sticky, 2 for setgid, 4 for setuid, so /tmp is 1777 and a shared folder becomes 2775. In ls -l they replace an x: a t at the very end is sticky, an s in the group triad is setgid, an s in the owner triad is setuid (a capital S or T means the bit is set but the underlying x isn't, which is usually a mistake). Sticky and setgid on folders are everyday tools. Setuid on a program is powerful and rare... it's how passwd and sudo themselves work, and a setuid program with a bug is one of the classic ways a machine gets broken into, so you don't sprinkle it around.
See it for real
sam@turtle:~$ ls -ld /tmp # the t at the end: sticky
drwxrwxrwt 14 root root 4096 Sep 23 /tmp
sam@turtle:~$ sudo chmod g+s /srv/shared # new files inherit the group
sam@turtle:~$ ls -ld /srv/shared
drwxrwsr-x 2 root family 4096 Sep 23 /srv/shared
sam@turtle:~$ ls -l /usr/bin/passwd # the s in the owner triad: setuid
-rwsr-xr-x 1 root root 68208 Feb 6 /usr/bin/passwd
sam@turtle:~$▌
Watch out
Sticky and setgid on folders are safe, everyday tools. Setuid on programs is the one to be careful with... it hands out root.
A capital S or T in the string means the special bit is on but execute is off. Nearly always unintended.
The shared-folder recipe is: chgrp family, chmod 2775. Without the 2, files people create there quietly belong to their own group and the sharing breaks.
Even with setgid, a normal umask (usually 022) makes new files group-readable but not group-writable... so members can see each other's files but not edit them. For a folder where people edit each other's work, have everyone set umask 002 as well.
Check yourself
1
Everyone can write in /tmp. Why can't they delete your files there?
2
What does setgid on a folder change about files created inside it?
3
How can passwd edit a root-only file when a normal user runs it?
Next module → Module 8: The filesystem for real ... the single tree turns out to be several disks stitched together, a file can wear more than one name, and when a disk fills up you can track down exactly what ate it.
Module 7 of Linux for the Curious Kid (and Grown-Up), Course 201. Permissions stopped being a string to decode and became a tool you set on purpose: modes by number, groups that share, ownership you can hand over, and the three special bits behind the system's everyday tricks.
You met the single tree back in Module 2. Now the truth under it: the tree is stitched together from several disks, a file can wear more than one name, and when the disk fills up you can find exactly what ate it.
4 lessonsbuilds on Module 2Course 201
Lesson 8.1
One tree, many disks
Builds on 2.1 (the single tree).
By the end you'll understand
what a mount point is, and why the one tree is really several disks
how to see what's mounted and how full each part is
how a USB stick joins the tree, and why you unmount before pulling it out
The big idea
In Module 2 you learned there's one tree, starting at /. Here's the part that was hidden: that tree isn't one disk. Linux takes each disk (or each slice of a disk, or a USB stick you plug in) and grafts it onto the tree at a chosen folder. That folder is called a mount point. So / might live on one disk, your /home on another, and a memory stick appears at something like /mnt/usb. To you it's still one smooth tree you walk with cd... underneath, you're stepping from one disk to another without ever noticing the seam.
Think of a big building where a few of the "rooms" are actually separate buildings joined by a hallway. You walk straight through and it feels like one place. Mounting is joining a new building onto the hallway; unmounting is detaching it again.
One tree to walk, three disks underneath. Each disk is grafted on at a mount point... /home and /mnt/usb are just folders where another disk begins.
How it works
Your main disks are mounted for you at boot, so the tree is already whole when you log in. To see the seams, two commands. df lists every mounted filesystem with how full it is and where it's mounted. lsblk draws the disks and their partitions as a little tree of hardware. When you plug in a USB stick, a desktop usually mounts it automatically under /media/you/...; on a bare server you mount it by hand: sudo mount /dev/sdb1 /mnt/usb. When you're done, sudo umount /mnt/usb before you pull it out, so any waiting writes actually finish.
See it for real
sam@turtle:~$ df -h # every mounted disk, and how full
Filesystem Size Used Avail Use% Mounted on
/dev/sda2 118G 64G 48G 58% /
/dev/sda3 400G 210G 170G 56% /home
/dev/sda1 511M 6.1M 505M 2% /boot/efi
/dev/sdb1 29G 1.8G 27G 7% /mnt/usb
sam@turtle:~$ lsblk # the disks as hardware
NAME SIZE MOUNTPOINT
sda 512G
|-sda1 512M /boot/efi
|-sda2 118G /
`-sda3 400G /home
sdb 29G
`-sdb1 29G /mnt/usb
sam@turtle:~$▌
Watch out
Pull a USB stick out without umount and you can lose the last few writes... Linux holds some in memory and flushes them a moment later. Unmount first; it waits for them.
A mount point is just a folder. Whatever was in that folder is hidden while something is mounted on top of it, and comes back when you unmount.
/mnt is the traditional "mount it here yourself" spot; /media is where desktops auto-mount removable drives. Same idea, different habit.
Check yourself
1
What is a mount point, in one sentence?
2
You want to know which disk your home folder actually lives on, and how full it is. What do you run?
3
Why is umount worth the extra second before you yank a memory stick?
Next up: Lesson 8.2: What a filesystem actually is
Lesson 8.2
What a filesystem actually is
Builds on 8.1.
By the end you'll understand
what the word filesystem really means (the scheme that turns a disk into files)
the common types you'll meet: ext4, FAT/exFAT, NTFS
why the type matters... what a disk can and can't remember
The big idea
A raw disk, underneath, is just an enormous row of numbered slots for bytes. It has no idea what a "file" or a "folder" is. A filesystem is the scheme laid over those slots that turns them into named files and folders, each with a size, an owner, permissions, and a date. Linux's usual scheme is called ext4. A stick from a camera is often FAT (you'll see it as vfat) because every device on earth understands it. A drive from a Windows machine is usually NTFS.
The type isn't just trivia... it decides what the disk can remember. ext4 knows about Linux owners and rwx permissions. FAT doesn't, at all. So a file you carefully set to 600 loses that the moment it lands on a FAT stick... not a bug, the stick simply has nowhere to write it down.
Same slots, organized. The filesystem is the scheme that makes files out of bytes... and the type decides what facts about each file the disk can keep.
How it works
Add -T to df and it shows the type of each mounted filesystem. lsblk -f shows the type and the disk's label too. You only pick a type when you format a fresh disk (with a command like mkfs.ext4), which is rare for a beginner... but now the word won't surprise you. The everyday consequence is simple: keep Linux files on an ext4 disk if you care about their owners and permissions, and treat FAT/exFAT sticks as good for plain files you're carrying between machines.
See it for real
sam@turtle:~$ df -T # same as df, with a type column
Filesystem Type Size Used Avail Use% Mounted on
/dev/sda2 ext4 118G 64G 48G 58% /
/dev/sda3 ext4 400G 210G 170G 56% /home
/dev/sda1 vfat 511M 6.1M 505M 2% /boot/efi
/dev/sdb1 vfat 29G 1.8G 27G 7% /mnt/usb
sam@turtle:~$ lsblk -f /dev/sdb1 # type + label of the stick
NAME FSTYPE LABEL MOUNTPOINT
sdb1 vfat CAMERA /mnt/usb
sam@turtle:~$▌
Watch out
FAT and exFAT can't store Linux owners or permissions, so everything on such a stick tends to look like 777. That's the filesystem, not a mistake you made.
Linux reads NTFS fine; writing to it works but is best kept for simple cases. For carrying files between Linux and anything else, FAT/exFAT is the calm choice.
mkfs ("make filesystem") formats a disk, which erases everything on it. It's the one command in this module that destroys data... aim it very carefully.
Check yourself
1
What does a filesystem give you that a raw disk of numbered slots can't?
2
Why do cameras and cheap USB sticks tend to use FAT?
3
You copy a 600 private file onto a FAT stick and its permissions vanish. Bug, or expected?
Next up: Lesson 8.3: A file with two names
Lesson 8.3
A file with two names: links
Builds on 2.3 (everything is a file) and 8.2.
By the end you'll understand
that a filename is a label pointing at the real data, not the data itself
the two kinds of link: a hard link (a second real name) and a symlink (a signpost)
what breaks when you delete the original, and when to use which
The big idea
Here's a quiet truth: the filename isn't the file. The name is a label hanging on the file's real data. Normally a file has exactly one label. But you can add more, in two different ways.
A hard link is a second real label pointing at the exact same data: two names, one file, completely equal. Delete one name and the other still works... the data survives until the last name pointing at it is gone. A symbolic link (symlink) is a different animal: it's a tiny signpost file that just holds the path to another file. Follow it and you land on the target. But delete or move the target and the signpost now points at nothing... a "dangling" link. So: a hard link is a twin name for one thing; a symlink is a shortcut that names a path.
A hard link is a twin name for the same data; a symlink is a small file that just holds a path. That difference is the whole lesson.
How it works
ln original newname makes a hard link... a second true name for the same data. It only works within one filesystem and only for files, not folders. ln -s target linkname makes a symlink, and it's the one you'll reach for: it can cross disks, it can point at a folder, and ls -l shows it plainly as linkname -> target. Symlinks are everywhere on a Linux box once you notice them... for example /usr/bin/python is usually a symlink to python3. Hard links are rarer in daily life; symlinks do most of the real work.
See it for real
sam@turtle:~$ ln notes.txt notes-backup.txt # a hard link: a twin name
sam@turtle:~$ ls -l notes*.txt # the 2 is the number of names
-rw-r--r-- 2 sam sam 41 Sep 23 09:10 notes-backup.txt
-rw-r--r-- 2 sam sam 41 Sep 23 09:10 notes.txt
sam@turtle:~$ ln -s notes.txt latest # a symlink: a signpost
sam@turtle:~$ ls -l latest
lrwxrwxrwx 1 sam sam 9 Sep 23 09:15 latest -> notes.txt
sam@turtle:~$▌
Watch out
A hard link can't cross to another disk and can't point at a folder... those are the moments to use ln -s instead.
A symlink can dangle: move or delete its target and it silently points at nothing. ls -l still shows the ->, but opening it fails.
Editing through a hard link edits the one shared file (both names see it). Editing through a symlink edits whatever it points at right now.
Check yourself
1
In your own words, the difference between a hard link and a symlink?
2
You delete a file that a symlink pointed at. What does the symlink do now?
3
Why can't a hard link point at a file on a different disk?
Next up: Lesson 8.4: Where did the space go?
Lesson 8.4
Where did the space go?
Builds on 8.1.
By the end you'll understand
how to read free space for a whole disk with df
how to measure what a folder is using with du
the everyday hunt: a disk fills up, and you track down what ate it
The big idea
Two questions, two tools. "How full is the disk?" is df... it asks each filesystem for the whole picture: total size, used, free, and the percent. "What in here is using all the space?" is du... it walks a folder and adds up the files inside it. Think of df as the fuel gauge on the dash and du as opening the trunk to see what's so heavy. When a disk fills up (and Linux starts failing to save things), the move is: df to confirm which disk is full, then du to hunt down the hog.
df is the fuel gauge for each disk; du opens the folders and weighs them. Full disk? df to find which, du to find what.
How it works
df -h gives every filesystem's used and free space in human sizes. du -sh folder gives one folder's grand total (-s for summary, -h for human). To find a hog, list each subfolder's size and sort it: du -h -d1 . | sort -h... the -d1 means "just one level down," and sort -h puts the biggest last. The classic hunt looks like this: df says / is 98% full, so sudo du -h -d1 / | sort -h, the bottom line is /var, you drill in with sudo du -h -d1 /var | sort -h, and there it is... a folder of old logs.
See it for real
sam@turtle:~$ df -h / # how full is the main disk?
Filesystem Size Used Avail Use% Mounted on
/dev/sda2 118G 64G 48G 58% /
sam@turtle:~$ du -sh ~ # my whole home folder's total
96G /home/sam
sam@turtle:~$ du -h -d1 ~ | sort -h # subfolders, biggest last
340M /home/sam/code
1.2G /home/sam/documents
6.4G /home/sam/photos
88G /home/sam/videos
sam@turtle:~$▌
Watch out
df and du can disagree. A file a program still has open but that's been deleted keeps using space until the program closes... df counts it, du can't see it. Restart the culprit and the space returns.
Running du across the whole system needs sudo to read into folders you don't own, or it'll skip them and undercount.
A disk can be "full" on inodes (it ran out of file slots) rather than bytes... millions of tiny files. df -i shows that. Rare, but it explains the baffling "no space left" on a disk that looks half empty.
Check yourself
1
Which do you reach for: "how full is the disk" versus "what's eating the space in this folder"?
2
Write the command that lists each subfolder of your home directory by size, biggest last.
3
Give one reason df and du might report different amounts of used space.
Next module → Module 9: Installing software the Linux way ... how programs really arrive on a Linux box: one command to a trusted shelf, the four moves you'll use every day, why it's safer than a download, and the family of managers behind every distro.
Module 8 of Linux for the Curious Kid (and Grown-Up), Course 201. The single tree turned out to be many disks stitched together; a file can wear more than one name; and when the disk fills up, you now know how to find exactly what's eating it.
On other systems you hunt the web for an installer and hope it's the real thing. On Linux you ask one trusted place for a program by name... it arrives, gets set up, brings whatever else it needs, and can be removed just as cleanly. That trusted place is a package manager.
4 lessonsbuilds on Module 5Course 201
Lesson 9.1
The app store you type to
Builds on 5.3 (root and sudo).
By the end you'll understand
what a package manager is, and why it beats hunting for installers
the words package, repository, and dependency
how one command fetches a program, sets it up, and remembers it
The big idea
On a lot of computers, getting a program means finding a website, downloading an installer, and trusting that what you got is really the thing you wanted and nothing else. Linux does it the other way around. There's one command, apt, that acts like an app store for the whole system. You ask it for a program by name, and it goes to a big, curated collection of software called a repository, fetches the package, unpacks it, sets it up, and writes down that it's installed... so later it can remove it just as cleanly. No hunting, no guessing.
It also handles dependencies for you. If the program you asked for needs three other pieces to run, apt notices and brings them along in the same breath. You never assemble a program by hand from parts.
Instead of hunting the web and hoping, you name the program and apt brings it from a trusted, checked shelf... and writes down that it's there.
How it works
apt is the friendly front you'll type. Underneath it sits an older tool, dpkg, that does the actual unpacking of a Debian package (a .deb file); apt is the part that finds the package, works out the dependencies, and hands the pieces to dpkg. You rarely call dpkg yourself. One line does the whole job: sudo apt install cowsay reads the catalog, sees what cowsay needs, fetches it all, unpacks it, and sets it up. It's sudo because installing software changes the whole machine, not just your files.
See it for real
sam@turtle:~$ sudo apt install cowsay # ask for it by name
sam@turtle:~$ cowsay "hello from Linux" # and now it's yours to run
__________________
< hello from Linux >
------------------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
sam@turtle:~$▌
Watch out
Seeing extra packages pulled in alongside the one you asked for is normal... those are the dependencies it needs to run, not bloat someone snuck in.
Installing needs sudo, because it writes into system folders every user shares. Running the program afterward usually doesn't.
The package name isn't always the obvious word. If apt install can't find it, apt search (next lesson) helps you find the real name.
Check yourself
1
In one sentence, what does a package manager do that hunting for an installer doesn't?
2
What's a dependency, and who deals with it when you install something?
3
Why does apt install need sudo when running the program afterward usually doesn't?
Next up: Lesson 9.2: The four moves
Lesson 9.2
The four moves
Builds on 9.1.
By the end you'll understand
the four commands that cover almost everything: update, upgrade, install, remove
the one trap every beginner hits: update is not upgrade
how to search for a program and clean up after one
The big idea
Almost everything you'll ever do with packages is four verbs. update refreshes the catalog... it asks the repositories "what's available now, and what versions?" and changes nothing on your machine. upgrade then actually installs the newer versions of what you already have. install adds a new program. remove takes one away. That's the whole day-to-day.
Here's the trap, and nearly everyone falls in it once: update does not upgrade your software. The name feels like it should, but all it does is refresh the list of what's out there. upgrade is the one that changes your machine. So they go together, in that order: refresh the list, then act on it.
update refreshes the list of what's out there; upgrade is what actually swaps your installed software for newer versions. Run them in that order.
How it works
The everyday habit is one line: sudo apt update && sudo apt upgrade. The && means "run the second only if the first succeeded", so you refresh the catalog and then upgrade against the fresh list. To add a program it's sudo apt install NAME; to take one away, sudo apt remove NAME, or sudo apt purge NAME if you also want its leftover config files gone. When a program you removed leaves behind dependencies nothing else uses, sudo apt autoremove sweeps them up. And before you install, apt search WORD finds packages by keyword and apt show NAME tells you what one is.
See it for real
sam@turtle:~$ sudo apt update # refresh the catalog only
12 packages can be upgraded. Run 'apt list --upgradable' to see them.
sam@turtle:~$ sudo apt upgrade # now change the software
Upgrading:
curl openssl tar ... (12 packages)
12 upgraded, 0 newly installed, 0 to remove.
sam@turtle:~$ apt search steam-locomotive # find a package by keyword
sl/noble 5.02-1 amd64
Correct you if you type 'sl' by mistake (a train runs across)
sam@turtle:~$▌
Watch out
update refreshes the list; upgrade changes your software. If you only ran update and nothing seemed to happen... that's correct, that's all it does.
remove leaves a program's config files behind (handy if you'll reinstall); purge takes those too. Neither touches your documents.
In a compound line like sudo apt update && sudo apt upgrade, the && is a gate: the upgrade runs only if the update succeeded first. A single & would mean something entirely different (background it), so mind the doubling.
Check yourself
1
You ran sudo apt update and your programs look exactly the same. Bug, or expected?
2
What's the difference between apt remove and apt purge?
3
In sudo apt update && sudo apt upgrade, what does the && guarantee?
Next up: Lesson 9.3: Where it all comes from
Lesson 9.3
Where it all comes from: repositories and trust
Builds on 9.1.
By the end you'll understand
why installing from a repository is safer than a random download
what a signing key does, and where the trusted shelves are listed
that adding an outside repository is a trust decision you're making
The big idea
Why is it safe to install this way when downloading a random program from the web isn't? Because the software comes from repositories your distribution curates, and every package is signed with a cryptographic key. When apt update refreshes the catalog, it checks those signatures... if a package has been tampered with, or comes unsigned from a shelf it doesn't trust, apt refuses it. You're not trusting a stranger's website; you're trusting a known set of shelves whose seal gets checked every time.
The list of shelves your machine trusts lives in a file, /etc/apt/sources.list (and a folder of add-ons, /etc/apt/sources.list.d/). You can add more shelves for software that isn't in the official ones... but each shelf you add is a piece of trust you're extending by hand.
The safety isn't magic. Packages are signed, and apt checks the seal every time it refreshes... trusted shelves pass, unsigned strangers get stopped.
How it works
You can read the trusted shelves with cat /etc/apt/sources.list... each line names a repository and what part of it to use. If you download a .deb by hand, the tidy way to install it is sudo apt install ./thing.deb (with the ./, so apt knows it's a file, and it still sorts out dependencies); the older sudo dpkg -i thing.deb installs it but won't fetch anything it depends on. Adding an outside shelf... often called a PPA on Ubuntu... is done with a command like sudo add-apt-repository ppa:some/thing, and from then on its software shows up in apt like any other. Just remember you vouched for it.
See it for real
sam@turtle:~$ cat /etc/apt/sources.list # the shelves this machine trusts
deb http://archive.ubuntu.com/ubuntu noble main universe
deb http://archive.ubuntu.com/ubuntu noble-updates main universe
deb http://security.ubuntu.com/ubuntu noble-security main
sam@turtle:~$ sudo apt install ./editor.deb # a hand-downloaded package, done right
Note: selecting 'editor' from local deb file
Setting up editor (1.9.0) ...
sam@turtle:~$▌
Watch out
Every extra repository or PPA you add is trust you're extending by hand. Add them only from sources you'd trust with your machine... the official shelves are vetted, the wider web is not.
sudo dpkg -i thing.deb installs a lone file but won't pull in what it depends on, so it can leave you half-installed. sudo apt install ./thing.deb is the friendlier way.
A signature error during apt update is the system doing its job, not an annoyance to force past. It means a shelf's seal didn't check out... stop and find out why.
Check yourself
1
What makes installing from a repository safer than downloading a program from a random website?
2
Where does your machine keep the list of repositories it trusts?
3
Why is adding a PPA a decision worth pausing on?
Next up: Lesson 9.4: apt isn't the only one
Lesson 9.4
apt isn't the only one
Builds on 9.1 and 1.2 (distributions).
By the end you'll understand
that every distribution has a package manager... the idea is universal, the command varies
the main families: apt/dpkg, dnf/rpm, pacman
what the newer cross-distro formats (snap, flatpak) trade for "runs anywhere"
The big idea
Everything so far used apt, because that's what Ubuntu and Debian use. But the idea... ask a trusted place for a program by name, and let it handle the rest... is universal. What changes from one Linux family to another is the command you type and the package format underneath. Back in Module 1 you met distributions; this is where that matters. Fedora and Red Hat use dnf with .rpm packages. Arch uses pacman. Ubuntu uses apt over dpkg and .deb, which you already know. Learn the shape once and "install X on this machine" is never scary, whatever the distro.
There's also a newer way. snap and flatpak are cross-distro formats: a package brings its dependencies bundled inside and runs in a little sandbox, so the same one program works on any distribution. The trade is that these packages are bigger and start a touch slower... the price of carrying their own world with them.
Same idea, different words. The classic families each have their own command and format; the newer bundled formats run the same package on any of them.
How it works
The quickest way to feel it is one task, done three ways. Installing the little system monitor htop is sudo apt install htop on Ubuntu, sudo dnf install htop on Fedora, and sudo pacman -S htop on Arch. Different verbs, identical intent. The cross-distro formats look like snap install htop or flatpak install htop, and they'll run the same on any of those systems. You don't need to memorize all of them... you need to know that the manager is the first thing you find out about a new distro, and the idea carries over intact.
sam@turtle:~$ apt policy htop # which shelf a package would come from
htop:
Candidate: 3.3.0-4build1
Version table: 3.3.0-4build1 (noble/main)
sam@turtle:~$▌
Watch out
Install a program with one manager, remove it with the same one. Mixing (installing with apt, hunting for it with snap) just confuses you about what's really there.
snap and flatpak apps are larger and can start a little slower, because each carries its own copy of the pieces it needs. That's the cost of "runs anywhere", not a fault.
The commands differ, the concepts don't. If you can reason about apt, you can read dnf or pacman docs in minutes... don't let an unfamiliar verb make it feel like a new world.
Check yourself
1
You sit down at a Fedora machine instead of Ubuntu. What's the first thing you'd want to know?
2
What do snap and flatpak give you, and what do they cost?
3
Why is it a good habit to remove a program with the same manager you installed it with?
Next module → Module 10: Slicing and shaping text ... the text workshop: grep to find lines, cut and sort | uniq -c to pull columns and count them, sed to rewrite on the fly, and awk to run a little program on every row.
Module 9 of Linux for the Curious Kid (and Grown-Up), Course 201. Software stopped being a risky download and became a request to a trusted shelf: one command to install, one to remove, a catalog you refresh before you act, seals that get checked every time, and a whole family of managers whose idea is the same even when the words differ.
Almost everything on a Linux box is plain text... logs, settings, lists, the output of every command. That's a gift: a handful of small tools, snapped together with pipes, can search it, pull out columns, swap words, and add up numbers. This is your text workshop.
4 lessonsbuilds on Module 4Course 201
Lesson 10.1
Finding lines with grep
Builds on 4.2 (pipes) and 3.4 (wildcards).
By the end you'll understand
grep as a sieve: it keeps the lines that match and drops the rest
the handful of flags you'll actually use: -i, -v, -n, -c
a first taste of patterns (regular expressions): ^, $, ., *
The big idea
You met grep in passing back with pipes. Here it earns a lesson of its own, because it's the tool you'll reach for most. Think of it as a sieve for lines: you give it a pattern, it reads text a line at a time, and it keeps only the lines that match, letting the rest fall away. Point it at a file, or feed it the output of another command through a pipe, and suddenly a thousand-line log becomes the five lines you cared about.
The pattern can be a plain word, but grep also speaks regular expressions... a tiny language for describing shapes of text. Four pieces get you a long way: ^ means "start of the line", $ means "end of the line", . means "any one character", and * means "the thing before me, repeated". So ^WIN finds lines that begin with WIN, and failed$ finds lines that end with failed.
grep is a sieve for lines: give it a pattern, and only the lines that match come through. Everything else quietly drops.
How it works
The shape is grep PATTERN file, or something | grep PATTERN to sieve another command's output. The flags you'll lean on: -i ignores case (win matches WIN), -vinverts it (keep the lines that don't match), -n prefixes each hit with its line number, and -c just counts the matches instead of printing them. Wrap a pattern in single quotes when it has special characters, so the shell hands it to grep untouched.
See it for real
sam@turtle:~$ grep WIN game.log # keep the lines that match
WIN level 1 cleared
WIN level 3 cleared
WIN level 4 cleared
sam@turtle:~$ grep -c WIN game.log # just count them
3
sam@turtle:~$ grep -v WIN game.log # the ones that DON'T match
LOSE level 2 failed
LOSE level 5 failed
sam@turtle:~$ grep -n cleared game.log # with line numbers
1:WIN level 1 cleared
3:WIN level 3 cleared
4:WIN level 4 cleared
sam@turtle:~$▌
Watch out
grep -v keeps the non-matches. It's easy to reach for the wrong one... "show me everything except the noise" is -v.
In a pattern, . means "any character" and * means "repeat the thing before". To match a literal dot, quote the pattern and put a backslash before it: '3\.03'.
Quote patterns with special characters in single quotes, or the shell may try to expand them before grep ever sees them.
Check yourself
1
In one line, what does grep do to a stream of text?
2
Which flag shows the lines that don't match, and which just counts matches?
3
What does the pattern ^WIN match that the plain word WIN might not want to?
Next up: Lesson 10.2: Columns and counting
Lesson 10.2
Columns and counting
Builds on 10.1 and 4.2 (pipes).
By the end you'll understand
how to pull one column out of delimited text with cut
the single most useful pipeline in Linux: sort | uniq -c
a quick way to transform characters with tr
The big idea
A lot of text is really a table: rows of records, each split into fields by a comma or a space or a colon. cut pulls out the fields you want. You tell it the delimiter with -d and which field with -f, and it hands back just that column. Then comes the pipeline everyone learns and never forgets: sort | uniq -c. uniq collapses adjacent duplicate lines, and -c counts them... but it only sees duplicates that sit next to each other, so you sort first to line them up. Together they answer "how many of each?" for anything.
Pull out a column with cut, line the duplicates up with sort, and let uniq -c tally them. "How many of each?" for any list.
How it works
cut -d, -f2 file means "split each line on commas, give me field 2". You can ask for several: -f1,3, or a range -f2-4. The counting pipeline is sort | uniq -c, and the reason sort has to come first is that uniq only notices duplicates that are next to each other. A common companion is tr, which swaps characters wholesale... tr 'a-z' 'A-Z' uppercases everything it's fed, and tr -d deletes characters. And when a file has a header row you don't want, tail -n +2 starts from line 2.
See it for real
sam@turtle:~$ cut -d, -f2 pets.csv # just the "kind" column
kind
reptile
mammal
insect
mammal
arachnid
sam@turtle:~$ tail -n +2 pets.csv | cut -d, -f2 | sort | uniq -c # how many of each?
uniq only collapses adjacent duplicates. Forget the sort first and it will happily leave repeats scattered through the list, uncounted.
cut -d takes a single character as the delimiter. It's perfect for commas or colons, but real spaces-and-tabs columns are where awk (next-but-one lesson) does better.
Header rows sneak into your counts. tail -n +2 drops the first line so the word "kind" doesn't get tallied as if it were an animal.
Check yourself
1
Why must sort come before uniq -c?
2
Write the command that pulls the third comma-separated field out of data.csv.
3
What does tail -n +2 do, and why is it handy for a CSV?
Next up: Lesson 10.3: Find and replace with sed
Lesson 10.3
Find and replace with sed
Builds on 10.1 (patterns).
By the end you'll understand
sed as a stream editor: it edits text as it flows past, no editor opened
the one command you'll use constantly: s/old/new/ (and /g for every match)
how to print just some lines, or delete lines, on the way through
The big idea
sed is a stream editor. Instead of opening a file, scrolling, and typing, you tell sed a rule and it applies that rule to every line as the text flows past. The rule you'll use ninety percent of the time is substitute: s/old/new/ replaces the first old on each line with new. Add a g on the end... s/old/new/g... and it replaces every match on the line, not just the first. That's the whole core of it.
Because it works on a stream, sed is happiest in a pipe or pointed at a file, printing the edited result to the screen. It doesn't change the file itself unless you explicitly ask it to... so you can experiment freely and just watch the output.
sed edits the stream as it passes. The s/old/new/ rule rewrites matching lines and leaves the others untouched... nothing is opened, nothing changed on disk.
How it works
The substitute rule is sed 's/OLD/NEW/', and the slashes just separate the three parts: find OLD, put NEW. Without g it changes the first match on each line; with g it changes them all. You can also select lines: sed -n '2,4p' prints only lines 2 through 4 (the -n says "don't print everything, only what I mark with p"), and sed '1d' deletes the first line... a tidy way to drop a header. It reads from a file or a pipe, and by default just prints the result, leaving the original alone.
See it for real
sam@turtle:~$ sed 's/mammal/MAMMAL/' pets.csv # swap the first match per line
name,kind,legs
turtle,reptile,4
cat,MAMMAL,4
ant,insect,6
dog,MAMMAL,4
spider,arachnid,8
sam@turtle:~$ echo hello | sed 's/l/L/g' # /g = every match, not just the first
heLLo
sam@turtle:~$ sed '1d' pets.csv # delete line 1 (the header)
turtle,reptile,4
cat,mammal,4
ant,insect,6
dog,mammal,4
spider,arachnid,8
sam@turtle:~$▌
Watch out
By default sed prints the edited stream and leaves the file untouched. That's a feature... experiment freely. (The -i flag edits the file in place, which is worth being careful with.)
Without g, s/// changes only the first match on each line. If a line has the word twice and only one changed, you wanted /g.
The separator is usually /, but if your text is full of slashes (like paths) you can use another character: sed 's#/home#/data#' reads more clearly than escaping every slash.
Check yourself
1
What does the g at the end of s/old/new/g change?
2
Does sed 's/a/b/' notes.txt change the file on disk? Why or why not?
3
Write a sed command that deletes the first line of a file.
Next up: Lesson 10.4: A tiny program per line, with awk
Lesson 10.4
A tiny program per line, with awk
Builds on 10.2 (columns).
By the end you'll understand
awk as a tool that sees every line as fields and runs a little program on each
$1, $2, $NF: the first field, the second, the last
the two everyday jobs: printing chosen fields, and adding a column up
The big idea
Where cut grabs a column and sed rewrites text, awk does the thing they can't: it treats each line as a row of fields and runs a tiny program on every one. It splits the line for you and hands you the pieces as $1, $2, $3, and so on, with $NF meaning "the last field" and $0 the whole line. You write a rule in the shape pattern { action }: for each line, if the pattern is true, do the action. Leave the pattern off and it runs on every line; leave the action off and it just prints matching lines.
Two jobs cover most of what a beginner needs. First, printing chosen fields: awk '{print $1}' prints the first field of every line. Second, adding a column up: you keep a running total across the lines and print it at the very end, in a special END block that runs once after the last line.
awk hands you each line already split into fields. Run a little program per line, keep a running total, and print it in END.
How it works
Tell awk the delimiter with -F (for a CSV, -F,), then give it a program in single quotes. awk -F, '{print $2}' pets.csv prints the second field of every line, just like cut. Patterns make it choosier: awk -F, '$2=="mammal" {print $1}' prints the name only on the rows whose kind is mammal, and NR>1 means "skip line 1" (NR is the current line number). The classic total: awk -F, 'NR>1 {sum+=$3} END {print sum}' adds field 3 down the whole file and prints it once at the end. Two commas of syntax, and you can answer questions no single-purpose tool can.
See it for real
sam@turtle:~$ awk -F, 'NR>1 {print $1, $2}' pets.csv # two fields, skipping the header
turtle reptile
cat mammal
ant insect
dog mammal
spider arachnid
sam@turtle:~$ awk -F, '$2=="mammal" {print $1}' pets.csv # only the mammals' names
cat
dog
sam@turtle:~$ awk -F, 'NR>1 {sum+=$3} END {print sum}' pets.csv # add up the legs
26
sam@turtle:~$▌
Watch out
Default awk splits on any whitespace, which is great for command output. For a CSV you must say -F, or every row looks like one big field.
String comparisons need quotes and doubled equals: $2=="mammal". A single = would try to assign, not compare.
awk is a whole language, and this is just the doorway. Printing fields and summing a column already covers a huge share of real use... reach for the rest when you meet it.
Check yourself
1
What does $NF mean, and how is it different from $1?
2
Why do you need -F, when working on a CSV?
3
In plain words, what does awk 'NR>1 {sum+=$3} END {print sum}' do?
Next module → Module 11: How machines talk to each other ... addresses, names, and numbered doors: ip and ping, DNS, ports and curl, and ssh into another machine.
Module 10 of Linux for the Curious Kid (and Grown-Up), Course 201. Text stopped being a wall of words and became something you can work: grep to find lines, cut and sort | uniq -c to pull columns and count them, sed to rewrite on the fly, and awk to run a little program on every row. Small tools, one pipe at a time.
The moment a computer joins a network, it gets an address, learns to look up names, and can knock on other machines' doors. Networking sounds like a dark art... it's really just addresses, names, and numbered doors called ports. Here are the few commands that let you see all three.
4 lessonsbuilds on Module 6Course 201
Lesson 11.1
Every machine has an address
Builds on 1.1 (what a computer is).
By the end you'll understand
what an IP address is, and that your machine can have more than one
localhost and 127.0.0.1: the machine talking to itself
how to see your own addresses with ip addr and hostname -I
The big idea
For two machines to talk, each needs an address, the same way a letter needs one. On a network that address is an IP address, a set of numbers like 192.168.1.42. Your home router hands one out to every device that joins... your laptop, your phone, the printer... so they can find each other. A machine reaches the network through an interface (your wifi or an ethernet cable), and it can have several: one for the real network, and a special built-in one called loopback that always points back at itself.
That loopback interface has the address 127.0.0.1, and the name localhost. When a program says "connect to localhost", it means "connect to this very machine"... the message never leaves the box. It's how a web server and a browser on the same computer talk during testing.
The router gives every device an address so they can find each other. And every machine also has localhost (127.0.0.1)... a loop straight back to itself.
How it works
To see your own addresses, ip addr lists every interface and the address on it. You'll always see lo (loopback, 127.0.0.1) and usually one real interface like eth0 (a cable) or wlan0 (wifi) carrying the address your router gave you. For just the useful number, hostname -I prints your machine's address on the network. Addresses that start with 192.168. or 10. are private... they only mean something inside your own network, which is why every home tends to reuse 192.168.1.x.
See it for real
sam@turtle:~$ ip addr # every interface and its address
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536
inet 127.0.0.1/8 scope host lo
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500
inet 192.168.1.42/24 scope global eth0
sam@turtle:~$ hostname -I # just my address on the network
192.168.1.42
sam@turtle:~$▌
Watch out
127.0.0.1 and localhost always mean "this machine". A service bound only to localhost can't be reached from another computer... that's often on purpose.
A private address (192.168.x.x, 10.x.x.x, or 172.16.x.x–172.31.x.x) is meaningful only inside your own network. Two different homes can both have a 192.168.1.42 and never collide.
Your address can change. Routers lease them out and may hand your laptop a different one next week, unless it's been reserved.
These are IPv4 addresses, the four-number kind this lesson uses. A real machine usually has an IPv6 address too (longer, with colons, like fe80::1), and ip addr shows both... the ideas here are the same either way.
Check yourself
1
What is an IP address for, in one sentence?
2
What does localhost (127.0.0.1) refer to?
3
Which command prints just your machine's address on the network?
Next up: Lesson 11.2: Names and reachability
Lesson 11.2
Names and reachability
Builds on 11.1.
By the end you'll understand
DNS: how a name like example.com becomes an address
ping: the quickest test of "is that machine answering?"
how to tell a name problem apart from a reachability problem
The big idea
Machines find each other by number, but people can't remember numbers, so we use names. example.com is a name; behind it sits an address like 93.184.216.34. The system that turns names into addresses is DNS, the phone book of the internet. Every time you open a site, your machine quietly asks DNS "what's the number for this name?" and then connects to the number.
Once you have an address, the simplest question is "is anything there, and is it answering?" That's ping. It sends a tiny "are you there?" packet and waits for the echo back, reporting how long the round trip took. Fast replies mean a healthy path; no replies mean the machine is off, unreachable, or ignoring you. Splitting those two questions... does the name resolve, and does the address answer... is how you find where a problem lives.
DNS turns the name into a number; ping then asks the number "are you there?" and times the echo. Two separate questions... name, then reachability.
How it works
host NAME (or dig NAME +short) does just the lookup and shows you the address a name points to. ping NAME resolves the name and then sends those little echo requests; add -c 3 to send just three and stop, instead of pinging forever. If host gives you an address but ping gets no reply, the name is fine and the machine is the problem. If host itself fails, the name never resolved... a DNS problem, before reachability even enters the picture.
See it for real
sam@turtle:~$ host example.com # just the name lookup
example.com has address 93.184.216.34
sam@turtle:~$ ping -c 3 example.com # is it answering?
PING example.com (93.184.216.34) 56(84) bytes of data.
64 bytes from 93.184.216.34: icmp_seq=1 ttl=56 time=11.2 ms
64 bytes from 93.184.216.34: icmp_seq=2 ttl=56 time=11.5 ms
64 bytes from 93.184.216.34: icmp_seq=3 ttl=56 time=11.8 ms
--- example.com ping statistics ---
3 packets transmitted, 3 received, 0% packet loss
sam@turtle:~$▌
Watch out
No reply to ping doesn't always mean "down". Plenty of machines are told to ignore pings on purpose, so a silent server may be perfectly healthy.
Plain ping runs until you stop it with Ctrl+C. Use -c 3 when you just want a quick check.
"The internet is down" is usually one of two very different faults: names not resolving (DNS) or addresses not answering (reachability). host then ping tells you which.
Check yourself
1
What job does DNS do?
2
host returns an address but ping gets no reply. Where's the problem, roughly?
3
Why add -c 3 to a ping?
Next up: Lesson 11.3: Ports and talking to servers
Lesson 11.3
Ports and talking to servers
Builds on 11.2 and 6.1 (processes).
By the end you'll understand
ports: numbered doors on a machine, one per service
the client / server idea, and the common ports 22, 80, 443
how to fetch a page from the command line with curl
The big idea
An address gets you to the right machine, but a machine runs many services at once... a web server, a mail server, a remote-login service. So each service waits behind a numbered door called a port. Web pages come through port 80 (or 443 for the secure version), remote login through port 22. When you connect, you're really connecting to an address and a port: "machine 93.184.216.34, door 443".
The one that knocks is the client; the one waiting behind the door is the server. Your browser is a client; the web server is a server. And a server is just a program (a process, from Module 6) that has claimed a port and is listening on it. curl lets you be the client from the command line: hand it a URL and it knocks on the right door, sends the request, and prints whatever comes back.
A machine has many numbered doors (ports), one per service. The client knocks on the right door, the listening server answers. curl is you, knocking.
How it works
curl URL fetches whatever's at that address and prints it... for a web page, that's the raw HTML. Add -I and it asks for just the headers: the server's short status reply, like HTTP/1.1 200 OK, where 200 means "here you go" and 404 means "no such page". You rarely type the port... http:// assumes 80 and https:// assumes 443... but you can spell it out with a colon, as in example.com:80. Its cousin wget does the same fetch but saves the result to a file instead of printing it, which is handy for downloads.
See it for real
sam@turtle:~$ curl -I http://example.com # just the headers
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 128
sam@turtle:~$ curl http://example.com # the page itself (raw HTML)
<html>
<head><title>Example Domain</title></head>
<body>
<h1>Example Domain</h1>
<p>This domain is for use in illustrative examples.</p>
</body>
</html>
sam@turtle:~$▌
Watch out
A web page arriving as a wall of <tags> is normal... that's HTML, the raw text your browser would draw. curl shows you what the browser hides.
The status code is the quick tell: 200 is success, 301/302 a redirect, 404 not found, 500 the server broke. curl -I shows it without downloading the whole page.
Ports below 1024 (like 22, 80, 443) are the "well-known" ones and are traditionally privileged, so a normal process needs extra privilege to claim one. That's why binding a web server to port 80 usually means running it as root (or granting it that one privilege on purpose).
Check yourself
1
What's a port, and why does one machine need many of them?
2
In a client/server exchange, which one is your browser?
3
What does curl -I get you that plain curl doesn't focus on?
Next up: Lesson 11.4: Who's listening, and reaching other machines
Lesson 11.4
Who's listening, and reaching other machines
Builds on 11.3 and 6.4 (services are processes).
By the end you'll understand
how to see which doors are open on your own machine with ss
ssh: opening a shell on another machine, the everyday remote tool
what a firewall does, in one idea
The big idea
You've been the client, knocking on other machines' doors. Now turn it around: which doors are open on your machine? A service that's listening has claimed a port and is waiting for knocks, and ss shows you exactly which ones. That's worth knowing... an open port is a way in, so you want to be able to answer "what's listening here, and did I mean for it to be?"
The most useful door of all is 22, where ssh waits. ssh ("secure shell") opens a shell on another machine over an encrypted connection, so you can work on a computer across the room or across the world as if you were sitting at it. It's how nearly all remote Linux work happens. And a firewall is simply a guard at the doors: a set of rules about which ports may be knocked on from where, so you can leave a service running but only let the right people reach it.
ss shows the open doors on your own machine; ssh walks you through port 22 of another one and hands you a shell there. A firewall is the guard deciding who may knock.
How it works
ss -tln is the everyday incantation: -t for TCP, -l for listening, -n for plain numbers instead of names. It prints a row per open door, with the local address and port. To reach another machine, ssh user@address... for example ssh sam@192.168.1.50... and after it checks the machine's identity and your login, your prompt is now a shell on that machine; type exit to come home. On Ubuntu the firewall is usually ufw, and a rule reads as plainly as ufw allow 22 ("let people reach the ssh door").
See it for real
sam@turtle:~$ ss -tln # which doors are open (listening) here
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
LISTEN 0 128 127.0.0.1:631 0.0.0.0:*
sam@turtle:~$ ssh sam@192.168.1.50 # open a shell on another machine
Welcome to Ubuntu 24.04 LTS
(a real ssh drops you into a shell on 192.168.1.50; this practice terminal stays on turtle)
sam@turtle:~$▌
Watch out
An open port is a door someone could try. Seeing something unexpected in ss -tln is worth a pause... know what's listening and why.
0.0.0.0:22 means "port 22, on every interface"... reachable from the network. 127.0.0.1:631 means "only from this machine". The address before the colon tells you how exposed a service is.
ssh the first time asks you to confirm the machine's identity (its fingerprint). That prompt is a security feature, not a nuisance... it's how you know you reached the right box.
Check yourself
1
What does ss -tln tell you about your own machine?
2
In one sentence, what does ssh let you do?
3
What's the difference between a service listening on 0.0.0.0:22 and one on 127.0.0.1:631?
Next module → Module 12: Editing files without leaving the terminal ... nano the friendly editor, vim and its two modes (and how to actually quit it), and the editors that open themselves.
Module 11 of Linux for the Curious Kid (and Grown-Up), Course 201. The network stopped being a mystery: every machine has an address, names turn into addresses through DNS, ping asks if anything's answering, services wait behind numbered ports, curl knocks as a client, ss shows your own open doors, and ssh walks you into a shell on a machine somewhere else.
Module 12: Editing files without leaving the terminal
Sooner or later you'll need to change a file right there in the shell, with no window to click. There are two editors worth knowing: nano, which you can use in five minutes, and vim, which is strange at first and then never leaves your hands. And yes... we'll settle "how do I even quit vim" once and for all.
4 lessonsbuilds on Module 3Course 201
Lesson 12.1
nano, the friendly editor
Builds on 3.2 (making and editing files).
By the end you'll understand
how to open, type in, save, and exit nano
the shortcut bar at the bottom, and that ^ means the Ctrl key
why nano is the safe first editor when one opens in front of you
The big idea
Editing a file in a graphical program is obvious: you see the text, you click, you type, you save. In the terminal there's no window... the editor takes over the whole screen instead. nano is the editor that makes this painless. You open a file, you just type (no modes, no tricks), and the two rows along the bottom remind you of every shortcut. It's the editor you can be productive in immediately, which is exactly why it's the one to reach for when you're not sure.
Those shortcuts use the Ctrl key, and nano writes it as ^. So ^O on the bar means "hold Ctrl and press O", and it means Write Out... save. ^X is Exit. Save with ^O (it asks you to confirm the filename... just press Enter), then leave with ^X. That's the whole editor.
nano puts the cheat-sheet on screen. ^ is Ctrl, so ^O saves ("Write Out") and ^X exits. No modes, no memorizing... you can use it right now.
How it works
Open a file with nano notes.txt (it's created if it doesn't exist). Type normally, use the arrow keys to move, Backspace to delete. When you're done, press Ctrl+O to save... nano shows the filename at the bottom and you press Enter to confirm... then Ctrl+X to leave. If you try to exit with unsaved changes, nano won't just lose them: it asks "Save modified buffer?" and you answer Y or N.
See it for real
You can actually do this right now. Open the practice terminal (the button at the bottom-right of this page) and type nano notes.txt. The editor takes over the panel, shortcut bar and all. Add a line, press Ctrl+O then Enter to save, and Ctrl+X to come back to the shell. Then cat notes.txt to see your change stuck.
Watch out
^ always means Ctrl in nano's bar, never the actual caret character. ^X is Ctrl+X.
After ^O, nano asks you to confirm the filename. You almost always just press Enter... it's offering to save under a different name if you want.
Exiting with ^X while you have unsaved changes triggers the "Save modified buffer?" question. Answering N throws your edits away, so read it before you hit a key.
Check yourself
1
In nano's bar, what does ^O mean, and what does it do?
2
You've made changes and press ^X. What does nano ask you?
3
Why is nano a good editor to reach for when one suddenly opens in front of you?
Next up: Lesson 12.2: vim and the two modes
Lesson 12.2
vim and the two modes
Builds on 12.1.
By the end you'll understand
the one idea that makes vim confusing... and then obvious: modes
how to type text (Insert mode) and how to give commands (Normal mode)
how to save and quit... including the legendary :q!
The big idea
People get stuck in vim for one reason: they expect to just type, and instead the letters seem to do weird things. That's because vim has modes. When it opens, you're in Normal mode, where the keys are commands, not text... x deletes a character, dd deletes a line. To actually type, you switch to Insert mode by pressing i. Now the keys are text, just like any editor. When you're done typing, press Esc to go back to Normal mode. That's the whole secret: i to type, Esc to command.
Saving and quitting happen from Normal mode. Press : and a little command line appears at the bottom; there you type w to write (save), q to quit, or wq to do both, then Enter. And the famous escape hatch, the one everyone eventually searches for: :q! means "quit and throw away my changes" ... the way out when you've made a mess and just want to leave. Press Esc first (to be sure you're in Normal mode), then :q! and Enter.
vim's modes, in one picture. Start in Normal, press i to type, Esc back, and : for the command line where you save and quit. :q! is the always-works exit.
How it works
Open a file with vim notes.txt. You land in Normal mode... don't panic, don't type yet. Press i and you'll see -- INSERT -- at the bottom; now type like normal. Press Esc to return to Normal. To save and quit: Esc, then :wq, then Enter. To quit without saving: Esc, then :q!, then Enter. If you try a plain :q with unsaved changes, vim stops you with E37: No write since last change... that's your cue to either :w first or :q! to abandon.
See it for real
Try the whole loop safely in the practice terminal: type vim notes.txt, press i, type a line, press Esc, then type :wq and Enter. You just edited and saved a file in vim... and you know the way out. Do it a few times and the fear is gone for good.
Watch out
If keys are "doing weird things", you're in Normal mode. Press i to type, or Esc then i if you're not sure where you are.
:q refuses to quit when you have unsaved changes (that E37 message). It's protecting your work, not being difficult... :wq to keep changes, :q! to drop them.
When in doubt, press Esc a couple of times first. It always lands you back in Normal mode, from where every command works.
Check yourself
1
You open vim and start typing, but the letters act like commands. What mode are you in, and how do you fix it?
2
What's the difference between :wq and :q!?
3
A plain :q gives you E37: No write since last change. What are your two options?
Next up: Lesson 12.3: Getting things done in vim
Lesson 12.3
Getting things done in vim
Builds on 12.2.
By the end you'll understand
moving around without arrow keys: h j k l, gg, G
the everyday edits: x, dd, u
finding and replacing: /word and :%s/old/new/g
The big idea
Surviving vim is knowing the modes. Using vim is a small vocabulary of Normal-mode commands that, once in your fingers, are genuinely fast, because your hands never leave the keyboard. You don't need many. Move with hjkl (left, down, up, right... arrows work too while you learn), jump to the top of the file with gg and the bottom with G. Edit with x (delete the character under the cursor), dd (delete the whole line), and the one everyone loves, u (undo). Those handful of keys cover most of a day.
Two more earn their keep. /word then Enter searches forward for "word" and drops your cursor on it. And the power move: :%s/old/new/g replaces every "old" with "new" in the whole file... the same substitute idea you met in sed, now inside the editor. The % means "all lines", the g means "every match on each line".
The whole working vocabulary on one card. A dozen keys, and vim goes from baffling to fast... because your hands stay home on the keyboard.
How it works
All of these are Normal-mode commands, so press Esc first if you've been typing. To delete three lines, dd three times. Made a mistake? u undoes it, one step per press. To fix a typo everywhere, :%s/teh/the/g and Enter corrects every "teh" in the file at once. Searching with / is how you get somewhere fast in a long file... type /error, Enter, and you're on the first "error". None of this is memory work at first; keep the card above beside you and it sinks in within a week.
See it for real
In the practice terminal, open a file with vim pets.csv, press Esc to be sure you're in Normal mode, and try it: dd to delete a line, u to bring it back, then :%s/mammal/MAMMAL/g and Enter to replace every "mammal". Save and quit with :wq, then cat pets.csv to see the result.
Watch out
These commands only work in Normal mode. If dd is typing the letters "dd" into your file, you're in Insert mode... press Esc first.
u undoes one change per press, and it's your best friend. Deleted the wrong line? u. Bungled a substitute? u.
:%s/old/new/g changes the whole file at once, which is powerful and easy to overdo. If it did too much, u reverts the entire substitution in one step.
Check yourself
1
Which keys move the cursor in Normal mode, and which way does each go?
2
You deleted the wrong line with dd. How do you get it back?
3
What does :%s/cat/dog/g do?
Next up: Lesson 12.4: Choosing, and the editor that opens itself
Lesson 12.4
Choosing, and the editor that opens itself
Builds on 12.1 and 12.2.
By the end you'll understand
when to pick nano and when to pick vim
the EDITOR setting that decides which one opens for you
the situations where an editor opens itself, and how to not get trapped
The big idea
Which editor? For a quick change... a config line, a note... nano is faster to get in and out of, especially while you're still learning. For real editing you do a lot of, vim pays back the learning curve in speed. There's no wrong answer; many people use nano for two-minute jobs and vim for everything else. What matters is that you can save and quit both, because sometimes the computer picks the editor for you.
That's the part nobody warns you about. Certain commands open an editor themselves: writing a commit message with git commit, scheduling a job with crontab -e, editing the admin file with sudo visudo. If the system's default editor is vim and you weren't ready for it, that's exactly the moment people panic. Now you won't: it's just vim (or nano), and you already know :wq or Ctrl+X. You can even choose which one opens by setting the EDITOR variable... export EDITOR=nano makes all of these use nano.
Some commands open an editor without asking. Whichever it is, you're no longer trapped: ^X for nano, :wq or :q! for vim. And EDITOR lets you pick which one shows up.
How it works
export EDITOR=nano (or =vim) sets your preferred editor for the current session; put that line in a shell startup file like ~/.bashrc (which you'll meet in Course 301) to make it stick. When git commit or crontab -e opens, it opens that editor. Beyond these two, you'll hear about emacs, vim's famous rival... just as deep, a different philosophy... and about graphical editors like VS Code that many people use for big projects. You don't have to choose a side. Knowing your way around nano and vim means no terminal, anywhere, can ever trap you in a file.
See it for real
In the practice terminal, try both and feel the difference: nano notes.txt (type, Ctrl+O, Ctrl+X) then vim notes.txt (i, type, Esc, :wq). Same file, two very different feels... now both are yours.
Watch out
The scary moment is almost always an editor opening when you didn't expect it (usually from git commit). Take a breath: it's nano or vim, and you know both exits.
export EDITOR=nano only lasts for the current shell session unless you save it in a startup file (next module). That's why the surprise editor is sometimes vim even after you "changed it".
emacs and VS Code are worth exploring later, but they're not required. nano and vim are on essentially every Linux machine you'll ever touch.
Check yourself
1
Give a reason to pick nano and a reason to pick vim.
2
Name one command that opens an editor by itself.
3
What does export EDITOR=nano do?
Next module → Module 13: Teaching the shell to do it for you ... the finale: save your commands in a script, add variables, decisions, and loops, and write a real little program.
Module 12 of Linux for the Curious Kid (and Grown-Up), Course 201. Editing in the terminal stopped being a trap: nano for a quick change with its cheat-sheet on screen, vim with its two modes (i to type, Esc to command, :wq to leave), the working vocabulary to actually get things done, and the calm to handle any editor that opens itself.
Everything you've typed one command at a time, you can save in a file and run all at once. Add a few variables, a decision or two, and a loop, and that file becomes a real little program. This is where the whole course comes together... you're going to write software.
4 lessonsbuilds on Module 4Course 201 finale
Lesson 13.1
A script is just saved commands
Builds on 12.1 (editing files) and 5.2 (permissions).
By the end you'll understand
that a script is a file of commands the shell runs top to bottom
the #!/bin/bash first line (the "shebang") and comments with #
two ways to run one: bash script.sh, or chmod +x then ./script.sh
The big idea
Here's the quiet magic at the end of the course: a script is nothing more than the commands you already know, saved in a file so the shell can run them all in order. Write three echo lines in a file, and running that file prints all three. No new language to learn... the shell is the language, and you already speak it.
Two small conventions make a file a proper script. The first line is usually #!/bin/bash, the shebang, which tells the system "run this with bash". And any line starting with # is a comment... a note for humans that the shell ignores. To run a script you either hand it to bash directly (bash script.sh), or you mark the file executable with chmod +x (permissions, from Module 5) and run it as ./script.sh.
A script is your commands in a file. Bash reads it top to bottom. Run it with bash file, or make it executable and call it with ./file.
How it works
Write the file with an editor you now know... nano hello.sh. Put #!/bin/bash on the first line, then your commands. Save and exit. Now bash hello.sh runs it. If you'd rather run it as its own little program, chmod +x hello.sh turns on the execute permission and then ./hello.sh works (the ./ means "the one right here in this folder"). Comments starting with # are ignored by bash, so use them freely to explain what a script does.
See it for real
sam@turtle:~$ cat hello.sh # a script is just a file of commands
#!/bin/bash
echo "Hello from my first script!"
sam@turtle:~$ bash hello.sh # run it with bash
Hello from my first script!
sam@turtle:~$ chmod +x hello.sh # or make it executable...
sam@turtle:~$ ./hello.sh # ...and run it directly
Hello from my first script!
sam@turtle:~$▌
Try it yourself
In the practice terminal, write your own: nano first.sh, type #!/bin/bash on line one and echo "I made a script!" on line two, save with Ctrl+O and exit with Ctrl+X. Then bash first.sh. You just wrote and ran a program.
Watch out
./script.sh only works after chmod +x. Without the execute bit you'll get "Permission denied"... that's Module 5 talking. bash script.sh always works regardless.
The ./ matters. Just typing script.sh usually fails, because the shell doesn't look in the current folder for programs by default... ./ says "yes, this one, right here".
The shebang must be the very first line, starting at the very first character. A blank line or a space before #!/bin/bash stops it working.
Check yourself
1
What is a shell script, in one sentence?
2
What does the first line #!/bin/bash do?
3
Name the two ways to run a script.
Next up: Lesson 13.2: Variables and input
Lesson 13.2
Variables and input
Builds on 13.1.
By the end you'll understand
how to store a value in a variable and use it with $
capturing a command's output with $(...), and script arguments with $1
asking the person a question with read
The big idea
A variable is a named box that holds a value. You put something in with name=value (no spaces around the =), and you get it back out by putting a $ in front: $name. So name="Sam" then echo "Hi, $name!" prints Hi, Sam!. One catch you already met in Module 12's cousin: double quotes let $name expand, but single quotes are literal, so 'Hi, $name' prints the dollar sign and all.
Two more ways to fill a variable make scripts genuinely useful. $(...) runs a command and hands you its output... today=$(date) puts the date into today. And a script can take arguments: when you run ./greet.sh Sam, the script sees Sam as $1 (the first argument), $2 would be the second, and so on. For a value typed while the script runs, read name waits for the person to type a line and stores it in name.
Fill a variable three ways: set it directly, capture a command's output with $(...), or receive it as an argument ($1). Get it back with a $.
How it works
Assignment is strict about spaces: name="Sam" works, name = "Sam" does not (the shell would think name is a command). Wrap values with spaces in quotes. Use "$name" in double quotes almost always, so the value comes through cleanly. Inside a script, $1, $2, $3 are the arguments in order, and $# is how many there were. And read answer pauses for the person to type, then continues with their text in answer... the start of an interactive script.
See it for real
sam@turtle:~$ name="Sam"
sam@turtle:~$ echo "Hi, $name!" # double quotes expand it
Hi, Sam!
sam@turtle:~$ echo 'Hi, $name!' # single quotes stay literal
Hi, $name!
sam@turtle:~$ cat greet.sh
#!/bin/bash
echo "Hello, $1! Welcome to Linux."
sam@turtle:~$ bash greet.sh Sam # Sam arrives as $1
Hello, Sam! Welcome to Linux.
sam@turtle:~$▌
Try it yourself
In the practice terminal, meet read: type read color and press Enter, then type a colour and Enter, then echo "You picked $color". The shell remembered what you typed. That's how a script asks a question.
Watch out
No spaces around = in an assignment. x=5 is a variable; x = 5 tries to run a command called x.
Single quotes never expand $. Reach for double quotes when you want the value, single quotes when you want the literal dollar sign.
Quote your variables ("$name") especially when a value might contain spaces, or the shell may split it into pieces you didn't intend.
Check yourself
1
Write the line that stores blue in a variable called color.
2
What's the difference between "$name" and '$name'?
3
Inside ./greet.sh Sam, what is $1?
Next up: Lesson 13.3: Making decisions
Lesson 13.3
Making decisions
Builds on 13.2.
By the end you'll understand
the if ... then ... else ... fi shape, and the [ ... ] test
comparing numbers (-gt, -lt, -eq) and text (=)
the exit code $?, and chaining with && and ||
The big idea
A program gets interesting when it can choose. In bash that's if: "if this is true, do that; otherwise do the other thing." The shape reads almost like English: if a condition, then some commands, maybe else some others, and fi to close it (that's if backwards... bash's little joke). The condition is usually a test written in square brackets: [ $x -gt 5 ] asks "is x greater than 5?". Mind the spaces inside the brackets... they're required.
Underneath, every command reports whether it succeeded with an exit code: 0 means success, anything else means failure, and the last one is stored in $?. That's what if really checks. It's also what lets you chain commands: A && B runs B only if A succeeded, and A || B runs B only if A failed. So mkdir data && cd data means "make the folder, and only if that worked, go into it".
An if checks a condition and takes one path or the other. The condition is really an exit code (0 = success), which is also what && and || chain on.
How it works
The full shape is if [ CONDITION ]; then COMMANDS; else COMMANDS; fi, and you can spread it across lines or keep it on one with semicolons. Number tests use -gt (greater), -lt (less), -eq (equal), -ge, -le, -ne; text uses = and != (as in [ "$name" = "Sam" ]); and file tests like [ -f notes.txt ] ask "does this file exist?". Check $? right after a command to see if it worked, and reach for &&/|| when a whole if would be more than the moment needs.
See it for real
sam@turtle:~$ x=7
sam@turtle:~$ if [ $x -gt 5 ]; then echo "big"; else echo "small"; fi
big
sam@turtle:~$ grep -q WIN game.log && echo "we have wins" # run 2nd only if 1st succeeds
we have wins
sam@turtle:~$ echo $? # exit code of the last command: 0 = success
0
sam@turtle:~$▌
Watch out
The spaces inside [ ] are not optional. [ $x -gt 5 ] works; [$x -gt 5] does not... bash needs the brackets to stand alone as words.
Numbers use -eq/-gt/-lt; text uses =/!=. Mixing them ([ "$a" -eq "$b" ] on words) gives odd errors.
0 means success here, which feels backwards if you think of 0 as "false". In exit-code land, zero is "all good" and non-zero is "something went wrong".
Check yourself
1
What closes an if block?
2
Which exit code means success, and where do you find the last one?
3
What does A && B do?
Next up: Lesson 13.4: Doing it many times
Lesson 13.4
Doing it many times
Builds on 13.3 and 10.2 (columns).
By the end you'll understand
the for loop: do something once for each item in a list
the while loop: keep going as long as a condition holds
how variables, decisions, and loops combine into a real, useful script
The big idea
The last piece is repetition. A for loop walks through a list and runs its body once for each item: for i in a b c; do echo $i; done prints a, then b, then c. The list can be words you type, numbers from $(seq 1 5), or files from a wildcard like *.txt. A while loop is the other flavour: it keeps running as long as a condition stays true, which is how you count, or wait for something. Both open with do and close with done.
And that's the whole toolkit. Commands, variables, decisions, loops. Put them together and you can write a script that does a real job... reads some data, decides what matters, and reports it. The little pets report below loops over each kind of animal, counts how many there are with tools from Module 10, and prints a tidy summary. Thirteen modules ago you were looking at a blinking cursor; now you're writing programs.
for runs its body once per item in a list; while runs its body over and over until the condition turns false. Both are do ... done.
How it works
A for loop is for VAR in LIST; do COMMANDS; done, and each time around, VAR holds the next item. A while loop is while [ CONDITION ]; do COMMANDS; done, and you make sure something inside changes, or it never stops (n=$((n+1)) does simple arithmetic to move a counter along). The capstone script below pulls it all together: a for over the animal kinds, a $(...) capture that counts each with grep -c, and an echo to report... a genuine little program built entirely from pieces you now know.
See it for real
sam@turtle:~$ for i in 1 2 3; do echo "line $i"; done
line 1
line 2
line 3
sam@turtle:~$ cat report.sh # the finale: everything together
#!/bin/bash
echo "Pets report:"
for kind in reptile mammal insect arachnid; do
count=$(grep -c ",$kind," pets.csv)
echo "$kind: $count"
done
sam@turtle:~$ bash report.sh
Pets report:
reptile: 1
mammal: 2
insect: 1
arachnid: 1
sam@turtle:~$▌
Try it yourself
In the practice terminal, write the capstone in your own words: nano mine.sh, and inside, loop over a few things and echo each with a variable. Save, then bash mine.sh. Change the list, run it again. That loop of write, run, tweak is programming... and you're doing it, right here in the page.
Watch out
A while loop needs something inside it to change, or the condition stays true forever. If a loop seems stuck, check that your counter is actually moving.
for f in *.txt loops over matching files; if nothing matches, some shells loop once with the literal *.txt, so a quick [ -f "$f" ] check inside is a safe habit.
Indentation inside do ... done is for humans, not bash... it doesn't change what runs, but your future self will thank you for it.
Check yourself
1
What does a for loop do with the items in its list?
2
What must change inside a while loop, and why?
3
In report.sh, what is count=$(grep -c ",$kind," pets.csv) doing?
Next course → Module 14: Making the shell your own ... Course 301 begins, where you stop just using the system and start operating it: your environment, aliases, a ~/.bashrc that sets it all up, and your own commands on the PATH.
Module 13 of Linux for the Curious Kid (and Grown-Up), and the finale of Course 201. A script turned out to be your own commands, saved and run together; variables hold values, if makes choices, and for and while repeat. Two courses in, you've gone from a blinking cursor to writing programs... and Course 301 is where you make the whole system truly your own.
Welcome to the last stretch. The shell you've been using came with sensible defaults... but it's yours to shape. A few settings, a handful of shortcuts, and one small file turn a generic terminal into your workshop, set up exactly how you like it, every time you log in.
4 lessonsbuilds on Module 13Course 301
Lesson 14.1
The environment
Builds on 13.2 (variables).
By the end you'll understand
the environment: variables the shell and its programs share, like $HOME, $USER, $PATH
the difference between a plain variable and an exported one
how to see the environment with env and add to it with export
The big idea
Every shell carries a set of variables that describe your world, called the environment. You've already used some without knowing: $HOME is your home folder, $USER is your name, $HOSTNAME is the machine's. These aren't magic... they're just variables that were set for you when you logged in, and programs read them to know where "home" is or who you are.
Here's the distinction that matters. A variable you set with name=value lives only in this shell. If you export it, it becomes part of the environment, and every program you launch from this shell inherits it too. That's why settings like your preferred editor are exported... so the programs you run can see them. env shows you everything currently in the environment.
The environment is a bag of shared variables. A plain variable stays in this shell; export puts it in the environment, where every program you launch can read it.
How it works
Read any environment variable the usual way, with a $: echo $HOME. See the whole environment with env (it's long... pipe it through grep to find one, as in env | grep PATH). To add something, export NAME=value: for example export EDITOR=nano tells every program that opens an editor to use nano. A plain NAME=value without export still works, but only in the current shell... fine for a quick variable in a script, not for a setting programs need to see.
See it for real
sam@turtle:~$ echo $HOME # a variable set for you at login
/home/sam
sam@turtle:~$ echo "$USER on $HOSTNAME"
sam on turtle
sam@turtle:~$ export GREETING="hello" # add it to the environment
sam@turtle:~$ env | grep GREETING # and there it is
GREETING=hello
sam@turtle:~$▌
Watch out
A variable set without export vanishes the moment you launch another program... it never made it into the environment. export is what shares it.
$PATH, $HOME and friends are just variables, but breaking them causes chaos. Add to $PATH (keep the old value); don't blindly overwrite it.
Anything you set at the prompt lasts only for this session. To make it permanent you put it in a startup file... which is the next couple of lessons.
Check yourself
1
What is the "environment"?
2
What does export do that a plain name=value doesn't?
3
How would you check the value of $EDITOR?
Next up: Lesson 14.2: Aliases
Lesson 14.2
Aliases: your own shortcuts
Builds on 14.1.
By the end you'll understand
what an alias is: a short name that stands for a longer command
how to make one with alias, list them, and remove one with unalias
why aliases are the first thing most people personalize
The big idea
If you type ls -l forty times a day, why type all four characters? An alias is a nickname you invent for a command: alias ll='ls -l' means "whenever I type ll, run ls -l." From then on ll just works. Aliases are the gentlest, most satisfying bit of customization... small, immediate, and entirely yours. Most people collect a handful they can't live without.
The shape is alias name='the full command', with the command in single quotes so it's taken literally. Type alias by itself to see the ones you have, and unalias name to drop one. Like everything you set at the prompt, an alias lasts only for this session... but in the next lesson you'll learn where to put them so they're there every time.
An alias is a nickname for a command. ll stands in for ls -l... type the short one, get the long one. Small, immediate, and yours.
How it works
alias ll='ls -l' creates it; now typing ll (with any extra arguments, like ll notes.txt) runs ls -l with them. alias on its own lists everything you've defined, and unalias ll removes one. You can alias anything: a long command you always mistype, a chain you run often, or a friendlier name. The single quotes keep the command literal so nothing expands too early.
See it for real
sam@turtle:~$ alias ll='ls -l' # invent a shortcut
sam@turtle:~$ ll notes.txt # ll now means ls -l
-rw-r--r-- 1 sam sam 41 Sep 23 09:10 notes.txt
sam@turtle:~$ alias # what have I got?
alias la='ls -a'
alias ll='ls -l'
sam@turtle:~$▌
Try it yourself
In the practice terminal, make your own: alias hi='echo hello there', then type hi. Invent one for a command you find yourself typing a lot. When you're done playing, unalias hi takes it away.
Watch out
An alias only lasts this session unless you save it in a startup file (next lesson). Close the terminal and a prompt-made alias is gone.
Aliasing over a real command (like alias ls='ls -l') can confuse you later when you expect the plain one. Give shortcuts their own names when you can.
Single quotes around the command matter... they stop the shell expanding parts of it before the alias is even used.
Check yourself
1
Write an alias gs that runs git status.
2
How do you see all the aliases you currently have?
3
Why don't prompt-made aliases survive closing the terminal?
Next up: Lesson 14.3: .bashrc, the startup file
Lesson 14.3
.bashrc, the startup file
Builds on 14.1, 14.2, and 12.1 (editing).
By the end you'll understand
what ~/.bashrc is: the file bash runs every time a shell starts
how to make your settings permanent by putting them there
how to apply changes right away with source, without reopening the terminal
The big idea
Everything you've set at the prompt so far... exports, aliases... disappears when you close the terminal. The fix is a file called ~/.bashrc in your home folder (the leading dot makes it hidden, which is why you haven't seen it). Bash reads this file every time a new shell starts and runs the lines in it. So anything you'd type to set up your shell, you write there once, and it's done for you forever after. Your export EDITOR=nano, your favourite aliases... they live in ~/.bashrc.
There's one catch people trip on: editing ~/.bashrc doesn't change your current shell, because bash only read it at startup. Either open a new terminal, or... better... run source ~/.bashrc, which re-reads the file into the shell you're in right now. "Source" means "run this file's lines here, as if I'd typed them", so your changes take effect immediately.
~/.bashrc runs at every shell start, so settings written there are permanent. Edit it, then source it to apply the changes without reopening the terminal.
How it works
Open it with the editor you know: nano ~/.bashrc. You'll usually find some lines already there. Add your own... an export, some alias lines... save and exit. Then source ~/.bashrc (or the shorthand . ~/.bashrc) re-reads it so your current shell picks up the changes. Every new terminal from now on runs it automatically. This one file is where "my shell, set up my way" actually lives.
See it for real
sam@turtle:~$ cat ~/.bashrc # the setup that runs at every login
# ~/.bashrc: runs for every new shell
export EDITOR=nano
export PATH="$HOME/bin:$PATH"
alias ll='ls -l'
alias la='ls -a'
sam@turtle:~$ source ~/.bashrc # re-read it after an edit
sam@turtle:~$▌
Try it yourself
In the practice terminal: nano ~/.bashrc, add a line like alias week='echo it is a good week', save (Ctrl+O, Enter) and exit (Ctrl+X). Type week... nothing yet, because this shell hasn't re-read the file. Now source ~/.bashrc, and week works. That's the whole loop.
Watch out
Editing ~/.bashrc doesn't touch your current shell until you source it or open a new terminal. That surprises everyone once.
The dot in .bashrc makes it hidden, so plain ls won't show it. Use ls -a to see it (and other dotfiles).
A typo in ~/.bashrc can spill an error into every new shell you open. If that happens, open the file and fix the offending line... it's just text.
Check yourself
1
What is ~/.bashrc, and when does bash read it?
2
You added an alias to ~/.bashrc but it doesn't work yet. What do you do?
3
Why doesn't plain ls show .bashrc?
Next up: Lesson 14.4: PATH and your own commands
Lesson 14.4
PATH and your own commands
Builds on 14.1, 14.3, and 13.1 (scripts).
By the end you'll understand
what $PATH is, and how the shell uses it to find commands
why ls works from anywhere but ./myscript.sh needs the ./
how to add a folder to $PATH and run your own scripts by name
The big idea
When you type ls, how does the shell know where ls actually lives? It looks in $PATH: a list of folders, separated by colons, that it searches in order until it finds a program with that name. ls is in /usr/bin, which is on the PATH, so it just works from anywhere. A script sitting in your current folder isn't on the PATH, which is exactly why you have to spell it out as ./myscript.sh... the ./ says "not on the PATH, right here."
So here's the mastery move: make a folder for your own commands, put it on the PATH, and drop your scripts in it. The custom is a bin folder in your home: ~/bin. Add it to the PATH (once, in ~/.bashrc, with export PATH="$HOME/bin:$PATH"), make a script executable there, and you can run it by name from anywhere, exactly like a built-in command. Your own tools, first-class citizens.
The shell finds commands by walking $PATH folder by folder. Put ~/bin on the PATH and your own executable scripts run by name, just like ls.
How it works
See your PATH with echo $PATH... a colon-separated list. The ~/.bashrc you looked at already adds your personal folder with export PATH="$HOME/bin:$PATH" (note it keeps the old $PATH on the end, so nothing is lost). Now the recipe: write a script, move it into ~/bin, make it executable with chmod +x, and type its name. The shell finds it on the PATH and runs it, no ./ needed. You've turned a script into a command... the same way every tool on the system got there.
See it for real
sam@turtle:~$ echo $PATH # the folders the shell searches
/home/sam/bin:/usr/local/bin:/usr/bin:/bin
sam@turtle:~$ ls ~/bin # my own commands live here
hi
sam@turtle:~$ hi # run it by name, no ./ needed
hi from my own command!
sam@turtle:~$▌
Try it yourself
In the practice terminal, make your own command from scratch: nano ~/bin/wave, write #!/bin/bash then echo "o/", save and exit. Then chmod +x ~/bin/wave, and type wave from anywhere. You just added a command to your system... the same move that put every other tool there.
Watch out
A script in ~/bin still needs chmod +x to be runnable. Without it, the shell finds the file but refuses to run it.
When you edit PATH, always keep the old value: export PATH="$HOME/bin:$PATH". Dropping the :$PATH would hide every normal command and break your shell.
Order matters. The shell stops at the first match, so a command in ~/bin can shadow a system one of the same name... handy on purpose, confusing by accident.
Check yourself
1
What is $PATH, and what does the shell do with it?
2
Why does ls work without ./ but myscript.sh in your folder needs it?
3
What three steps turn a script into a command you can run by name?
Next module → Module 15: Services, the programs that run themselves ... the background: what a service is, how systemd runs them, starting and enabling with systemctl, and reading their logs.
Module 14 of Linux for the Curious Kid (and Grown-Up), and the opening of Course 301. The shell stopped being something you just use and became something you shape: an environment you can read and add to, aliases for the commands you love, a ~/.bashrc that sets it all up for you at every login, and a $PATH that lets your own scripts stand shoulder to shoulder with the built-in tools.
Module 15: Services, the programs that run themselves
Some programs you start and watch. Others run quietly in the background for months... the thing that answers when you connect over SSH, the one that runs your scheduled jobs, the web server serving pages at 3am. Those are services, and on modern Linux one manager runs them all: systemd.
4 lessonsbuilds on Module 6Course 301
Lesson 15.1
What a service is
Builds on 6.1 (processes) and 6.4 (services are processes).
By the end you'll understand
the difference between a program you run and a service that runs itself
that systemd is the manager (process 1) that starts and supervises them
how to look at one service with systemctl status
The big idea
Most commands you've run start, do their thing, and finish, right there in front of you. A service is different: it's a program meant to run in the background, continuously, without anyone watching. The SSH server that lets you log in remotely, the scheduler that runs jobs at set times, a web server... these all just sit there, running, ready. The old Unix word for one is a daemon.
Something has to start all these at boot, keep them running, and restart them if they crash. On modern Linux that's systemd, and it's special: it's process 1, the very first thing the kernel starts, the ancestor of everything else (you saw PID 1 back in Module 6). You talk to it with one command, systemctl, and systemctl status NAME shows you everything about a service at a glance: whether it's loaded, whether it's running, its main process, and its most recent log lines.
systemd (process 1) starts and watches over the background services. They run for months without anyone looking... unlike a command you type, which starts and finishes.
How it works
You inspect a service with systemctl status NAME. The first line's dot tells you at a glance: a filled ● means running, a hollow ○ means stopped. Then Loaded says systemd knows about it (and whether it's set to start at boot), Active says whether it's running right now and since when, Main PID is its process, and the last few log lines show what it's been up to. Reading a service is almost always the first thing you do... before you touch anything, you look.
See it for real
sam@turtle:~$ systemctl status ssh # look before you touch
Active: active (running) since Tue 2026-09-23 08:00:01 UTC
Main PID: 720 (sshd)
Sep 23 08:00:01 turtle sshd[720]: Server listening on 0.0.0.0 port 22.
sam@turtle:~$▌
Watch out
A service is just a long-running process, so everything from Module 6 still applies... it has a PID, it uses memory, you could even see it in ps. systemd is the part that manages it.
Service names sometimes end in .service (ssh.service). You can usually leave that off... systemctl status ssh and systemctl status ssh.service mean the same thing.
Not every Linux uses systemd, but the vast majority do now. If a system is different, the idea of a managed background service still holds.
Check yourself
1
What makes a service different from a command you type?
2
What is systemd, and what's special about its process number?
3
What does the dot at the start of systemctl status tell you?
Next up: Lesson 15.2: Controlling services
Lesson 15.2
Controlling services
Builds on 15.1 and 5.3 (sudo).
By the end you'll understand
the four everyday verbs: start, stop, restart, status
why changing a service needs sudo
how to confirm the result with is-active
The big idea
Once you can read a service, controlling it is four verbs. systemctl start NAME gets it running, stop halts it, and restart stops then starts it again (the classic fix for "it's gone weird... turn it off and on"). status, which you already know, is how you check. Because services affect the whole machine, not just your files, changing one needs sudo... but just looking (status, is-active) does not.
A small nicety: systemctl is quiet on success. Run sudo systemctl start nginx and, if it worked, you get... nothing. No news is good news. To confirm, ask: systemctl is-active nginx answers active or inactive in one word, perfect for a quick check or a script.
Four verbs run a service: start, stop, restart, and status. Changing one needs sudo; looking doesn't. And success is silent, so confirm with is-active.
How it works
To bring a stopped service up: sudo systemctl start nginx. To take it down: sudo systemctl stop nginx. When a service is misbehaving, sudo systemctl restart nginx is the reliable reset. After any change, systemctl status nginx shows the full picture, or systemctl is-active nginx gives the one-word answer. Forget the sudo and systemd will refuse with an "access denied", because you're asking to change something that belongs to the whole system.
See it for real
sam@turtle:~$ systemctl status nginx # stopped, at the moment
sam@turtle:~$ sudo systemctl start nginx # quiet = it worked
sam@turtle:~$ systemctl is-active nginx # confirm
active
sam@turtle:~$▌
Try it yourself
In the practice terminal: systemctl status nginx (stopped), then sudo systemctl start nginx, then systemctl status nginx again... watch the dot fill in and Active flip to running. sudo systemctl stop nginx puts it back. Try start without sudo and read the refusal.
Watch out
Silence after a start/stop means success. systemd only speaks up when something goes wrong... don't wait for a "done" that isn't coming.
restart is a full stop-then-start, so there's a brief moment the service is down. Usually fine; worth knowing for something people are actively using.
Changing a service without sudo gets refused. Reading it never needs sudo, which is why you can always status first.
Check yourself
1
Which verb both stops and starts a service in one go?
2
You ran sudo systemctl start nginx and got no output. Did it work?
3
Why does start need sudo but status doesn't?
Next up: Lesson 15.3: Start now, or start at boot?
Lesson 15.3
Start now, or start at boot?
Builds on 15.2.
By the end you'll understand
the crucial pair: start (run it now) versus enable (run it at every boot)
that they're independent... a service can be one, both, or neither
is-active and is-enabled, the one-word answers to each
The big idea
Here's the distinction that trips up nearly everyone, and it's worth pausing on. start runs a service right now, this once. enable is a completely separate thing: it tells systemd "run this automatically every time the machine boots." They don't imply each other. You can start a service without enabling it (it's running now, but won't come back after a reboot). You can enable one without starting it (it'll come up next boot, but isn't running yet). Most real setups want both: start it now, and enable it so it survives a reboot.
This is the same shape as the update-versus-upgrade trap from Module 9: two words that sound related but do different jobs. Keep them straight and services stop being mysterious. Two one-word questions keep you honest: systemctl is-active NAME ("running right now?") and systemctl is-enabled NAME ("set to start at boot?").
Running-now and starts-at-boot are two independent switches. start flips one, enable the other. Most services you care about want both on.
How it works
sudo systemctl enable nginx sets it to start at boot (it prints a line about creating a symlink... that's systemd wiring it into the boot sequence). sudo systemctl disable nginx undoes that. Neither one touches whether it's running right now... for that you still start or stop. When you want a service properly set up, do both: sudo systemctl enable --now nginx is even a shortcut that enables and starts in one go. Check your work with the two questions: is-active and is-enabled.
See it for real
sam@turtle:~$ systemctl is-enabled nginx # will it start at boot?
disabled
sam@turtle:~$ sudo systemctl enable nginx # make it start at every boot
Created symlink /etc/systemd/system/multi-user.target.wants/nginx.service -> /lib/systemd/system/nginx.service.
sam@turtle:~$ systemctl is-enabled nginx
enabled
sam@turtle:~$▌
Watch out
enable does not start the service now, and start does not make it survive a reboot. The classic "it worked until I rebooted" is a service that was started but never enabled.
enable --now does both at once when that's what you want, which is most of the time.
Disabling a service doesn't stop it, and stopping doesn't disable it. Two switches, always.
Check yourself
1
In your own words, the difference between start and enable?
2
A service "worked until I rebooted, then it was gone." What was missing?
3
Which command tells you whether a service will start at boot?
Next up: Lesson 15.4: What's it doing? Reading a service's logs
Lesson 15.4
What's it doing? Reading a service's logs
Builds on 15.1.
By the end you'll understand
that a service writes a running diary, and systemd collects it in the journal
how systemctl status already shows you the last few lines
how to read one service's log with journalctl -u
The big idea
A background service can't pop up a window to tell you what happened, so instead it writes a running diary... log lines... as it works. systemd gathers every service's diary into one place called the journal, and you read it with journalctl. This is how you answer the question you'll ask constantly once you run services: "why did that just do that?" The answer is almost always in the logs.
You've actually already seen a preview: the bottom of systemctl status shows a service's most recent lines. For more than a peek, journalctl -u NAME shows that one service's whole diary. Each line carries a timestamp, the machine's name, and which program wrote it... so you can see exactly what happened and when. (Logs are a big enough topic to get their own module soon; this is your first, most useful slice.)
Services write log lines; systemd pools them into the journal. journalctl -u ssh pulls out just that service's diary, timestamped and labelled.
How it works
Start with systemctl status NAME... the last few log lines at the bottom are often all you need. When you want the fuller story, journalctl -u NAME shows that service's log. It can be long, so the same habits help: pipe it through grep to find a word, or use journalctl -u NAME -n 20 for just the last 20 lines. And journalctl -u NAME -ffollows the log live, printing new lines as they arrive... the way you watch a service while you poke at it. There's a whole module on logs coming; for now, "status first, then journalctl -u" will answer most questions.
See it for real
sam@turtle:~$ journalctl -u ssh # just the ssh service's diary
Sep 23 08:00:01 turtle sshd[720]: Server listening on 0.0.0.0 port 22.
Sep 23 08:14:22 turtle sshd[720]: Accepted password for sam from 192.168.1.50
sam@turtle:~$ journalctl -u cron -n 1 # just the last line
Start with systemctl status. Its last few log lines solve a surprising number of "why won't this start?" problems before you even open journalctl.
journalctl without -u shows everything, from every service... useful but overwhelming. Narrow to one service with -u NAME, or a count with -n.
-f follows the log and keeps running until you stop it with Ctrl+C, just like ping. Great for watching a service in real time.
Check yourself
1
Where does systemd keep every service's log lines?
2
What does journalctl -u nginx show?
3
Before opening the full journal, what's the quick first place to look?
Next module → Module 16: Scheduling work ... make the machine do things on its own: cron and the crontab, the five-field schedule, one-offs with at, and systemd timers.
Module 15 of Linux for the Curious Kid (and Grown-Up), Course 301. The background stopped being a mystery: services are long-running programs, systemd (process 1) starts and supervises them, systemctl lets you read and control them, start and enable are two separate switches, and when you want to know what a service is up to, its diary is waiting in the journal.
A good operator is lazy in the best way: anything that happens on a schedule... a nightly backup, a weekly cleanup, a report every morning... the machine should do by itself. Linux has a patient little clock-watcher called cron that runs your jobs while you sleep.
4 lessonsbuilds on Module 15Course 301
Lesson 16.1
Why schedule, and meet cron
Builds on 15.1 (services) and 13.1 (scripts).
By the end you'll understand
why running things on a schedule is a superpower for operators
what cron is: a service that wakes up every minute and runs due jobs
that your personal schedule lives in a crontab
The big idea
You already know how to write a script that does a job. The next step up is having it run on its own, at set times, forever, without you. That's the difference between doing a task and operating a system. Backups at 2am, a log cleanup every Sunday, a "good morning" summary at 7... none of these should need a human to remember them.
The tool for recurring jobs is cron, and it's exactly the kind of background service you met last module. It wakes up once a minute, every minute, looks at everyone's schedules, and runs whatever is due right then. Your schedule is a little table called a crontab ("cron table"): each line says when to run something and what to run. Cron reads it and does the rest.
cron is a clock-watcher: every minute it checks your crontab and runs whatever is due. You write the schedule once; it runs your jobs forever.
How it works
You don't start or stop cron yourself... it's a service that's simply always running (you could systemctl status cron and see it, from last module). What you manage is your crontab, your own list of scheduled jobs. Every user has one. You look at it, you edit it, and cron takes it from there. The next lessons are all about reading and writing those schedule lines... which is really the whole skill.
See it for real
sam@turtle:~$ systemctl status cron # the scheduler is just a service
● cron.service - Regular background program processing daemon
Active: active (running) since Tue 2026-09-23 08:00:02 UTC
cron only runs while the machine is on. If the computer is asleep at 2am, that 2am job simply doesn't happen (there's a cousin, anacron, for laptops that miss their slot... a detail for later).
Each user has their own crontab. Your jobs run as you; the system has its own separate schedules too.
A scheduled job runs with a bare, minimal environment... not the cosy one your ~/.bashrc sets up. That trips people up, and we'll come back to it.
Check yourself
1
In one sentence, what does cron do?
2
What's a crontab?
3
Give an everyday task worth scheduling instead of doing by hand.
Next up: Lesson 16.2: Your crontab
Lesson 16.2
Your crontab
Builds on 16.1 and 12.1 (editing).
By the end you'll understand
crontab -l to list your jobs and crontab -e to edit them
the shape of a crontab line: five time fields, then the command
that editing your crontab opens the editor you already know
The big idea
Two commands manage your whole schedule. crontab -llists what you've got. crontab -eedits it... and here's a lovely payoff from Course 301's first module: it opens your crontab in whatever editor $EDITOR points to. You set export EDITOR=nano, so crontab -e opens nano. Add or change lines, save, exit... and cron picks up the new schedule automatically. No restart, no fuss.
Each line has the same shape: five time fields, then the command to run. The five fields, in order, are minute, hour, day-of-month, month, and day-of-week. So 0 7 * * * echo "good morning" reads as "at minute 0 of hour 7, every day, run the echo." A * means "every" for that field. Get comfortable with that shape and cron is basically solved... the next lesson zooms into the fields.
crontab -e opens your schedule in your editor; save and exit, and cron reads it right away. Every line is five time fields, then the command.
How it works
crontab -l prints your current schedule (lines starting with # are comments, ignored by cron... great for labelling what a job does). crontab -e opens it for editing; the first time, you may get an empty file with a helpful header. Write your lines, save, exit. To wipe your whole crontab, crontab -r (careful... that's all of it, gone). You almost never edit the crontab file directly on disk; you always go through crontab -e, which validates it and tells cron to reload.
See it for real
sam@turtle:~$ crontab -l # five time fields, then a command
sam@turtle:~$ crontab -e # opens in your $EDITOR (nano) to change it
sam@turtle:~$▌
Try it yourself
In the practice terminal: crontab -e opens your schedule in nano (the editor from Module 12, the setting from Module 14... it all connects). Add a line like 30 8 * * 1 echo "Monday standup", save with Ctrl+O and exit with Ctrl+X, then crontab -l to see it stuck.
Watch out
Always edit with crontab -e, not by opening the raw file. -e checks your work and reloads cron; hand-editing the file can silently skip both.
crontab -r removes your entire crontab with no confirmation. It sits one key away from -e on the keyboard, so look twice.
Comment your jobs with # lines. Six months from now, "what is this 3am job?" is a question you'll be glad you answered in advance.
Check yourself
1
Which command lists your jobs, and which edits them?
2
What are the five fields at the start of a crontab line, in order?
3
What does a # at the start of a line do?
Next up: Lesson 16.3: Reading the five fields
Lesson 16.3
Reading the five fields
Builds on 16.2.
By the end you'll understand
exactly what each of the five time fields means and its range
the four symbols that do all the work: *, */n, a-b, a,b
how to read and write the schedules you'll actually need
The big idea
The whole art of cron is those five fields, so let's pin them down. In order they are: minute (0–59), hour (0–23, so 24-hour time), day of month (1–31), month (1–12), and day of week (0–6, where 0 is Sunday). A * in a field means "every". So 0 2 * * * is "minute 0, hour 2, every day of the month, every month, every day of the week" ... that is, 2:00 every morning.
Four symbols cover almost everything. * is "every". */n is "every n" ... */15 in the minute field means every 15 minutes. A range with a dash, 1-5 in the weekday field, means Monday through Friday. A list with commas, 0,30 in the minute field, means "at 0 and 30". Combine them and you can say almost any schedule in five little fields.
Five fields, four symbols. 0 2 * * * is "minute 0, hour 2, every day" ... 2am nightly. Learn the symbols and you can write nearly any schedule.
How it works
Read left to right, field by field, and translate. A few you'll reach for constantly: 0 2 * * * is every day at 2am; */15 * * * * is every 15 minutes; 0 9 * * 1-5 is 9am on weekdays; 0 0 1 * * is midnight on the first of every month. There are also friendly shortcuts that replace all five fields: @daily (midnight every day), @hourly, @weekly, @reboot (once, when the machine starts up). When in doubt, write the schedule in words first, then translate it field by field.
See it for real
# a few schedules, and what they mean:
0 2 * * * → every day at 02:00
*/15 * * * * → every 15 minutes
0 9 * * 1-5 → 09:00, Monday to Friday
0 0 1 * * → midnight on the 1st of each month
@reboot → once, each time the machine starts
sam@turtle:~$▌
Watch out
Hours are 24-hour. "2pm" is 14, not 2 (2 is 2am). This is the single most common cron mistake.
Day-of-week counts from 0 = Sunday (and 7 also works as Sunday). Many names are accepted too, like MON, but numbers are safest.
If you set both day-of-month and day-of-week, cron runs when either matches, not both... a surprising corner. Leave one as * unless you truly mean the OR.
Check yourself
1
Write the schedule for "every day at 6:30am".
2
What does */10 * * * * mean?
3
In the hour field, how do you write 2pm?
Next up: Lesson 16.4: Just once, and the modern way
Lesson 16.4
Just once, and the modern way
Builds on 16.1 and 15.1 (services).
By the end you'll understand
at: schedule a command to run once, at a future time
systemd timers: the modern alternative to cron, tied to services
when to reach for cron, for at, and for a timer
The big idea
cron is for things that repeat. Sometimes you want the opposite: run a command once, later, then forget it. That's at. You give it a time and a command, and it fires that one time... "compile this at midnight when nobody's using the machine," and you can log off. It even has a little queue you can inspect (atq) and clear (atrm).
There's also a newer way to do recurring jobs: systemd timers. Since systemd already manages your services (Module 15), it can also start them on a schedule, with a companion timer unit. Timers are more powerful than cron... they log to the journal, can catch up on missed runs, and depend on other units... which is why modern system packages increasingly ship timers instead of cron jobs. You can see the ones on a machine with systemctl list-timers. For your own quick jobs, cron is still perfectly good and simpler; timers are what you'll meet in the plumbing of a modern system.
Three ways to schedule: at for a one-off, cron for simple recurring jobs, and systemd timers for the modern, service-tied approach.
How it works
For a one-off, pipe a command into at with a time: echo "backup.sh" | at 22:00 queues it for 10pm tonight. atq shows the queue, atrm N cancels job N. For the modern recurring approach, you don't usually write timers by hand as a beginner, but you'll see them: systemctl list-timers shows what's scheduled and when it next fires. The rule of thumb: reach for at when it's once, cron when it's your own simple repeat, and know that timers are what modern packages use under the hood.
See it for real
sam@turtle:~$ echo "echo backup done" | at 22:00 # run once, tonight
warning: commands will be executed using /bin/sh
job 1 at 22:00 (this practice terminal queues it; it will not really fire)
sam@turtle:~$ atq # what's in the one-off queue
1 22:00 a sam
sam@turtle:~$ systemctl list-timers # the modern, timer-based schedules
NEXT LEFT UNIT ACTIVATES
Wed 2026-09-24 00:00 UTC 2h left apt-daily.timer apt-daily.service
Wed 2026-09-24 06:00 UTC 8h left logrotate.timer logrotate.service
sam@turtle:~$▌
Watch out
at runs a command once; cron runs it every time it matches. Reaching for the wrong one gives you either a job that never repeats or one that never stops.
Scheduled jobs (cron and at both) run with a minimal environment and a plain /bin/sh. Use full paths to your scripts and don't assume your aliases or $PATH tweaks are there.
You rarely hand-write systemd timers as a beginner, but recognizing them in list-timers means you won't be confused when a modern system has no cron job for something that clearly runs on a schedule.
Check yourself
1
Which tool runs a command exactly once at a future time?
2
What command shows the systemd timers on a machine?
3
Why must scheduled jobs use full paths and not rely on your ~/.bashrc?
Next module → Module 17: Users and groups, from the other side ... running a system other people use: the user database, adding and removing users, group membership, and who holds the keys to sudo.
Module 16 of Linux for the Curious Kid (and Grown-Up), Course 301. Time itself became something you can operate: cron runs your recurring jobs from a crontab of five-field schedules, crontab -e edits it in the editor you set up, at handles the one-offs, and systemd timers are the modern machinery doing the same underneath. The machine now works while you sleep.
Back in Course 101 you learned to be a user. Now you learn to manage them: who exists on the machine, how to add and remove people, how groups grant shared access, and who gets the keys to sudo. This is the heart of running a system other people use.
4 lessonsbuilds on Module 5Course 301
Lesson 17.1
The user database
Builds on 5.1 (users) and 7.2 (groups).
By the end you'll understand
that every user is a line in /etc/passwd, with a number (UID) as their real identity
the split between system users and human users
why passwords live separately in /etc/shadow, locked away
The big idea
To the system, a "user" isn't really a name... it's a number, the UID, and a line in a plain text file called /etc/passwd. Each line holds seven colon-separated fields: the name, an x placeholder for the password, the UID, the primary group's GID, a description, the home folder, and the login shell. root is always UID 0... that's what actually makes root special, the zero, not the name.
Most of the entries aren't people at all. UIDs 1 to 999 are system users... accounts that services run as, so a web server isn't running as you or as root. Real humans start at 1000 (that's you: sam is 1000). And the passwords? They're deliberately not in /etc/passwd (which everyone can read). They live in /etc/shadow, readable only by root, so even though anyone can list the users, nobody but root can get at the password hashes. The x in the passwd line is just a pointer saying "the real secret is over in shadow."
A user is seven fields in /etc/passwd, keyed by a UID number (root = 0, system users 1–999, people 1000+). Passwords sit apart in root-only /etc/shadow.
How it works
Read the users with cat /etc/passwd... it's just text, and everyone can look. Groups are the same idea in /etc/group. But try cat /etc/shadow as yourself and you'll get "Permission denied"... that file is locked to root on purpose, because it holds the (hashed) passwords. With sudo you can read it. A cleaner way to query either database is getent: getent passwd sam pulls just that user's line, and getent group sudo shows who's in a group.
See it for real
sam@turtle:~$ cat /etc/passwd # everyone can read who exists
root:x:0:0:root:/root:/bin/bash
sam:x:1000:1000:Sam:/home/sam:/bin/bash
alex:x:1001:1001:Alex:/home/alex:/bin/bash
sam@turtle:~$ cat /etc/shadow # but not the passwords
cat: /etc/shadow: Permission denied
sam@turtle:~$ getent passwd sam # query one user cleanly
sam:x:1000:1000:Sam:/home/sam:/bin/bash
sam@turtle:~$▌
Watch out
The name is for humans; the UID is the real identity. Two names with the same UID are the same user to the system, and root is really just "UID 0".
/etc/passwd being world-readable is fine and normal... it holds no secrets. The secret is the password hash, and that's why /etc/shadow is locked to root.
Most lines in /etc/passwd are system accounts, not people. Don't be alarmed by a long list... services each get their own low-numbered user.
Check yourself
1
What is a user's real identity, if not their name?
2
Why is /etc/shadow readable only by root?
3
Roughly what UID does the first human user get?
Next up: Lesson 17.2: Creating and managing users
Lesson 17.2
Creating and managing users
Builds on 17.1.
By the end you'll understand
how to add a user with useradd (and its friendly wrapper adduser)
giving them a password with passwd
changing a user with usermod and removing one with userdel
The big idea
Adding a user is one command, and because it changes the whole system it needs sudo. sudo useradd -m -s /bin/bash jo creates jo: the -m makes their home folder, and -s sets their login shell. That one command writes the /etc/passwd line, creates a matching group, adds a /etc/shadow entry, and builds /home/jo. New users have no password yet (they can't log in until they get one), so you follow up with sudo passwd jo.
Two more verbs round it out. usermod changes an existing user... their shell, their groups, their name. userdel removes one, and userdel -r also deletes their home folder and mail. On many systems there's also adduser, a friendlier, interactive wrapper around useradd that asks you the questions and sets sensible defaults... handy, but useradd is the one that's everywhere.
The user lifecycle: useradd -m creates them (passwd line, home, group), passwd gives a password, usermod changes them, userdel -r removes them.
How it works
The everyday create is sudo useradd -m -s /bin/bash NAME, then sudo passwd NAME to set a password. Check your work with getent passwd NAME or id NAME (which shows their UID and groups). To change a user later, usermod: sudo usermod -s /bin/bash NAME changes their shell, and its group options are the next lesson. To remove someone, sudo userdel NAME, or sudo userdel -r NAME to take their home folder with them. All of it needs sudo, because you're editing accounts the whole machine shares.
sam@turtle:~$ sudo passwd jo # give jo a password so they can log in
passwd: password updated successfully
sam@turtle:~$ id jo
uid=1002(jo) gid=1002(jo) groups=1002(jo)
sam@turtle:~$▌
Try it yourself
In the practice terminal: sudo useradd -m -s /bin/bash robin, then getent passwd robin and id robin to see the new account. Give it a password with sudo passwd robin. When you're done, sudo userdel -r robin removes it cleanly.
Watch out
Forget -m and the user gets no home folder... they'll log in with nowhere to put anything. For a real person you almost always want -m.
A brand-new user has no password and can't log in until you run passwd for them. That's a safety default, not a bug.
useradd and adduser aren't the same command. useradd is the low-level one on every system; adduser is a friendlier wrapper that isn't always present.
Check yourself
1
What do the -m and -s options to useradd do?
2
Why can't a freshly created user log in yet?
3
What does the -r in userdel -r add?
Next up: Lesson 17.3: Groups and membership
Lesson 17.3
Groups and membership
Builds on 17.2 and 7.2 (groups share access).
By the end you'll understand
primary versus secondary groups, and how to see a user's groups
adding someone to a group with usermod -aG
the one-letter mistake that wipes a user's other groups
The big idea
Back in Course 101 you saw that groups let people share access. As an admin, you're the one who puts people in them. Every user has one primary group (usually a group of their own, matching their name) and any number of secondary groups that grant extra access... being in the sudo group, or a family group that owns a shared folder. groups NAME and id NAME show which groups someone is in.
You add someone to a group with usermod, and here is the single most important detail in this whole module: use -aG, not just -G. The -G flag sets a user's secondary groups... it replaces the whole list. The -a means "append". So usermod -aG sudo jo adds jo to sudo and keeps their other groups; usermod -G sudo jo would make sudo their only secondary group and silently drop the rest. Countless people have accidentally removed themselves from groups by forgetting the -a. Always -aG.
The one-letter difference that bites everyone: -aGadds a group and keeps the rest; -G alone replaces the whole secondary list. Always use -aG.
How it works
Make a group with sudo groupadd NAME. Add a user to one (or several) with sudo usermod -aG group1,group2 NAME. Check with groups NAME or getent group NAME (which lists the members). A group change takes effect on the user's next login... a logged-in session keeps its old groups until it starts fresh, which surprises people who add themselves to a group and wonder why it "didn't work" yet. And the golden rule bears repeating: to add a group without disturbing the others, it's -aG every time.
See it for real
sam@turtle:~$ groups jo # jo is only in their own group so far
jo
sam@turtle:~$ sudo usermod -aG sudo jo # -aG: add sudo, keep the rest
sam@turtle:~$ groups jo # now a member of sudo too
jo sudo
sam@turtle:~$ getent group sudo # who's in the sudo group
sudo:x:27:sam,jo
sam@turtle:~$▌
Watch out
usermod -G (no -a) replaces a user's secondary groups. Forgetting the -a is how people accidentally drop themselves from sudo and lock themselves out. Use -aG.
Group changes apply at the user's next login, not instantly. If groups doesn't show it yet, a fresh session (or a reboot) will.
A user's primary group is set differently (with -g, lowercase) and there's only one. The -aG business is all about the secondary groups.
Check yourself
1
What's the difference between a primary and a secondary group?
2
Why is usermod -aG almost always what you want, not -G alone?
3
You added yourself to a group but groups doesn't show it. Why, and what fixes it?
Next up: Lesson 17.4: The keys to sudo
Lesson 17.4
The keys to sudo
Builds on 17.3 and 5.3 (root and sudo).
By the end you'll understand
how being in the sudo group is what grants the power to use sudo
where the sudo rules live, and why you edit them only with visudo
the principle of least privilege: give the least access that does the job
The big idea
You've typed sudo since Course 101. Now the reveal of why it works for you: you're in a special group (called sudo on Ubuntu, wheel on some systems), and there's a rule that says "members of that group may run commands as root." Grant someone sudo power and you're really just doing what you learned last lesson: sudo usermod -aG sudo NAME. Take it away by removing them from the group. The group is the keyring.
The rules themselves live in /etc/sudoers, and there's one iron law: never edit it with a normal editor... always use visudo. Why? visudo checks your changes for mistakes before saving. A single typo in /etc/sudoers can break sudo for everyone, including you, leaving no way to fix it (you'd need sudo to repair the file that broke sudo). visudo is the seatbelt. And the guiding principle behind all of this is least privilege: give each person and service the least access that lets them do their job, and no more. Not everyone needs sudo; most things shouldn't run as root.
Being in the sudo group is the key; the rule %sudo ALL=(ALL:ALL) ALL in /etc/sudoers is the lock. Edit it only with visudo, and grant the least access that does the job.
How it works
To make someone a sudoer, add them to the group: sudo usermod -aG sudo NAME. To see the rules, sudo cat /etc/sudoers... you'll find %sudo ALL=(ALL:ALL) ALL, where the % means "the group named sudo" and the line reads "may run any command, as anyone." To change the rules, sudo visudo opens the file in your editor but validates it on save, refusing to write a broken file. For extra rules, drop small files in /etc/sudoers.d/ rather than editing the main file. Through all of it, lean toward least privilege... the fewer people and processes with root, the fewer ways things go wrong.
See it for real
sam@turtle:~$ groups # why can I sudo? I'm in the sudo group
sam sudo
sam@turtle:~$ sudo cat /etc/sudoers # the rules (root-only to read)
# /etc/sudoers: edit with visudo
Defaults env_reset
root ALL=(ALL:ALL) ALL
%sudo ALL=(ALL:ALL) ALL
sam@turtle:~$ sudo visudo # the ONLY safe way to edit it
sam@turtle:~$▌
Try it yourself
In the practice terminal: groups to see that you're in sudo, then sudo cat /etc/sudoers to read the rule that grants it. Try sudo visudo to open the file in the editor (it's how you'd add a rule for real). Make a user with sudo useradd -m robin and grant them power: sudo usermod -aG sudo robin, then groups robin.
Watch out
Never open /etc/sudoers in a plain editor. A typo there can break sudo for everyone, and you'd need sudo to fix it. Always sudo visudo, which validates before saving.
Removing someone from the sudo group revokes their power... but don't remove your only sudo user, or nobody can administer the machine.
Least privilege isn't just a slogan. Most services should run as their own limited user, not root, so a compromise of one thing doesn't hand over the whole system.
Check yourself
1
How do you grant a user the ability to use sudo?
2
Why must you edit /etc/sudoers with visudo and not a normal editor?
3
State the principle of least privilege in your own words.
Next module → Module 18: Reading what the system tells you ... when something breaks, the machine already wrote down why: the journal read with journalctl, the classic logs in /var/log, and a calm loop that turns "it's broken" into a fix.
Module 17 of Linux for the Curious Kid (and Grown-Up), Course 301. Running a system for other people came into focus: users are numbered lines in /etc/passwd with their secrets locked in /etc/shadow, useradd and friends manage them, groups (added with the all-important -aG) share access, and sudo power is just membership in a group, its rules edited safely with visudo, always leaning toward the least privilege that does the job.
When something breaks, most people guess. But the machine has been keeping a diary the whole time, writing down what it did and what went wrong. This module teaches you to read that diary... the modern journal and the classic text logs... and turns "it's broken and I don't know why" into a calm, repeatable method.
4 lessonsbuilds on Module 15Course 301
Lesson 18.1
The system keeps a diary
Builds on 15.4 (a service's logs) and 8.1 (the filesystem tree).
By the end you'll understand
that Linux writes down what happens... logs are that written record
the two places logs live: the systemd journal and plain text files in /var/log
how to read a single log line: when, which machine, which program, and what happened
The big idea
Here's the secret that separates people who fix things from people who reboot and hope: the machine almost always already told you what went wrong. As services start, stop, succeed, and fail, they write short notes about it. That stream of notes is the log, and it's the first place to look when anything misbehaves, not the last.
Those notes land in two homes. The modern one is the systemd journal: a single, searchable store that catches almost everything, which you read with one tool, journalctl (next lesson). The older one is a folder of plain text files under /var/log... syslog for general system chatter, auth.log for logins, kern.log for the kernel. Because those are just text, every tool you learned in Course 201 (cat, tail, grep) works on them directly. Most modern systems keep both, so it pays to know your way around each.
Every event leaves a note. Notes go to the journal (read with journalctl) and to text files in /var/log (read with cat/tail/grep). Each line tells you when, on which machine, from which program, and what happened.
How it works
To peek at the recent journal, journalctl -n 3 shows the last three entries (the whole thing is huge, so you almost always ask for a slice). To see the text logs, list the folder with ls -l /var/log. Notice the owners: files like auth.log are owned by root and the adm group, because a record of who logged in is sensitive. General chatter in syslog is readable by anyone. Reading a line is a habit worth drilling: left to right, it's the timestamp, the hostname, the program and its process id in brackets, then a colon and the message. The message is the part that matters, but the rest tells you exactly when and where to look next.
See it for real
sam@turtle:~$ journalctl -n 3 # the last three journal entries
Sep 23 08:15:04 turtle sshd[901]: Failed password for invalid user admin from 203.0.113.9
Sep 23 08:15:59 turtle nginx[1180]: ERROR: could not bind to port 80: Permission denied
Sep 23 08:16:00 turtle systemd[1]: nginx.service: Failed with result exit-code.
sam@turtle:~$ ls -l /var/log # the classic text logs, and who owns them
-rw-r----- 1 root adm 304 Sep 23 08:14 auth.log
-rw-r--r-- 1 root adm 303 Sep 23 08:07 kern.log
-rw-r--r-- 1 root adm 339 Sep 23 08:16 syslog
sam@turtle:~$▌
Watch out
Logs are the first place to look, not a last resort. The answer is usually already written down... rebooting just erases the scene of the crime.
Don't be spooked by volume. A busy machine writes thousands of lines; the next two lessons are entirely about filtering so you only see the ones you need.
Old lines are old news. Always check the timestamp... an error from last Tuesday isn't why the thing broke five minutes ago.
Check yourself
1
What are the two places logs live on a modern Linux system?
2
Reading left to right, what are the parts of a single log line?
3
Why is /var/log/auth.log owned by root and the adm group?
Next up: Lesson 18.2: journalctl, the modern front door
Lesson 18.2
journalctl, the modern front door
Builds on 18.1 and 15.2 (services have units).
By the end you'll understand
that journalctl reads one big searchable journal, and the flags are how you ask it questions
The whole journal, printed raw, is a firehose... useless. The skill isn't reading it all, it's asking it good questions, and journalctl's flags are the questions. Learn five and you can find almost anything. The one you'll reach for most, when a particular service is misbehaving, is -u: journalctl -u nginx shows only the lines from the nginx unit, nothing else. That single flag turns a haystack back into a short, readable story.
The rest narrow the stream in other ways. -n 20 shows the last twenty lines; -b limits to this boot (since the last power-on), so yesterday's noise disappears; -p err keeps only problems (errors and worse), which is often the fastest way to spot what's wrong; and --since "08:15" jumps to a moment in time. You can stack them: journalctl -u nginx -p err asks "show me only the errors, only from nginx." And when you want to watch trouble happen live... start a service in one window and follow its journal in another... journalctl -f keeps printing new lines as they arrive, until you press Ctrl+C. The old-hand shortcut for "what just broke" is journalctl -xe: the end of the journal, with extra hints.
Think of journalctl as a funnel and its flags as the questions: -u one service, -p err only problems, -n the recent ones, -b this boot, --since a time. Stack them to narrow further; add -f to watch new lines arrive live.
How it works
Start with the service you suspect: journalctl -u SERVICE. If it's long, cut it down with -n 20 or -b (this boot only). To go straight to trouble, add -p err... it drops everything that isn't at least an error. To zoom to a moment, --since "08:15" (and --until for the far end). To watch live, journalctl -f (or journalctl -u SERVICE -f to follow just one). And -x adds explanatory hints to failures, which is why journalctl -xe... end of the journal, with hints... is the classic "so what just happened?" command.
See it for real
sam@turtle:~$ journalctl -u nginx # only the nginx service's lines
Sep 23 08:15:59 turtle nginx[1180]: ERROR: could not bind to port 80: Permission denied
Sep 23 08:16:00 turtle systemd[1]: nginx.service: Failed with result exit-code.
sam@turtle:~$ journalctl -p err # only problems, from everything
Sep 23 08:15:59 turtle nginx[1180]: ERROR: could not bind to port 80: Permission denied
Sep 23 08:16:00 turtle systemd[1]: nginx.service: Failed with result exit-code.
sam@turtle:~$ journalctl --since 08:15 # only what happened at or after 08:15
Sep 23 08:15:04 turtle sshd[901]: Failed password for invalid user admin from 203.0.113.9
Sep 23 08:15:59 turtle nginx[1180]: ERROR: could not bind to port 80: Permission denied
Sep 23 08:16:00 turtle systemd[1]: nginx.service: Failed with result exit-code.
sam@turtle:~$▌
Try it yourself
In the practice terminal: run journalctl -p err to see just the problems, then journalctl -u nginx to read that one service's story. Try journalctl -b -n 5 for the last five lines of this boot, and journalctl -f to see the "following..." live mode. Notice how each flag makes the pile smaller.
Watch out
-u wants the unit name (nginx, ssh), the same name systemctl uses... not a program's path or a pid.
Bare journalctl with no flags dumps the entire journal from oldest to newest. Always narrow it: by unit, by -b, by -p, or by time. An unfiltered dump is how people convince themselves logs are "too hard."
-f follows forever until you stop it with Ctrl+C. That's the point... it's for watching a problem happen, not for a quick look.
Check yourself
1
Which flag shows only one service's log lines, and what do you pass it?
2
What does -p err do, and why is it often the fastest first look?
3
What does journalctl -f do, and how do you stop it?
Next up: Lesson 18.3: The classic text logs in /var/log
Lesson 18.3
The classic text logs in /var/log
Builds on 18.2, 10.1 (grep), and 3.x (tail).
By the end you'll understand
the main text logs: syslog, auth.log, kern.log... and dmesg for the kernel
watching a log fill in real time with tail -f, and searching with grep
that some logs need sudo... and that a tool can't read what you can't read
The big idea
Long before the journal, logs were just text files in /var/log, and plenty still are. Because they're plain text, you don't need any special tool... your Course 201 kit already works. Two moves cover most days. The first is tail -f, which shows the end of a file and then keeps showing new lines as they're written... it's like watching the log fill in live, perfect for "do the thing and see what it says." The second is grep, to pull only the lines that match: grep Failed /var/log/auth.log finds every failed login without you reading the rest.
But there's a catch you already know the shape of, from Module 17. Some logs hold sensitive things... auth.log records every login and every sudo... so they're owned by root and the adm group, and a normal user can't read them without sudo. And here's the part people trip on: a tool can only read what you can read. If cat is denied, then grep, tail, and less on that same file are denied too... permission isn't about the command, it's about the file. So grep Failed /var/log/auth.log fails for you, but sudo grep Failed /var/log/auth.log works. One more friend lives slightly apart: dmesg prints the kernel's own ring buffer... boot messages and hardware events, like a USB stick being plugged in.
The text logs live in /var/log. Watch one fill live with tail -f, search it with grep, read the kernel's own buffer with dmesg. Sensitive logs like auth.log are root:adm... and permission follows the file, so if cat is denied, so are grep and tail.
How it works
To watch the general log live, tail -f /var/log/syslog (press Ctrl+C to stop). To search, pipe or point grep at the file: grep nginx /var/log/syslog. The readable logs (syslog, kern.log) open with no ceremony; the sensitive one does not... cat /var/log/auth.log is denied, so you reach for sudo cat /var/log/auth.log (and the same sudo in front of grep or tail when you point them there). For hardware and boot questions... "did it even see my USB drive?"... dmesg is the fast answer, and dmesg | grep -i usb pulls just the USB lines.
See it for real
sam@turtle:~$ tail -n 3 /var/log/syslog # the general log is readable
Sep 23 08:11:03 turtle systemd[1]: Started Daily apt download.
Sep 23 08:12:40 turtle NetworkManager[540]: <warn> wlan0: signal strength weak
Sep 23 08:16:00 turtle systemd[1]: nginx.service: Failed with result exit-code.
sam@turtle:~$ cat /var/log/auth.log # but logins are sensitive
cat: /var/log/auth.log: Permission denied
sam@turtle:~$ sudo cat /var/log/auth.log # sudo opens it
Sep 23 08:14:22 turtle sshd[720]: Accepted password for sam from 192.168.1.50 port 51920
Sep 23 08:15:04 turtle sshd[901]: Failed password for invalid user admin from 203.0.113.9 port 40144
sam@turtle:~$ dmesg | grep -i usb # did the kernel see my USB stick?
[ 435.120000] usb 1-1: new high-speed USB device number 3 using xhci_hcd
sam@turtle:~$▌
Try it yourself
In the practice terminal: run tail -f /var/log/syslog to see the follow mode, then grep nginx /var/log/syslog to pull just the nginx lines. Try cat /var/log/auth.log and watch it get denied... then sudo cat /var/log/auth.log. Prove the "permission follows the file" rule to yourself: grep Failed /var/log/auth.log is denied too, but sudo grep Failed /var/log/auth.log works. Finish with dmesg | grep -i usb.
Watch out
Permission is about the file, not the command. If cat is denied on a log, so are grep, tail, and less... reach for sudo, don't switch tools hoping one sneaks through.
tail -f doesn't stop on its own. It's meant to stay open and stream; Ctrl+C ends it.
The journal is steadily replacing many of these files, so on some systems /var/log/syslog may be thin or absent and journalctl is the real record. Know both; reach for whichever your machine actually keeps.
Check yourself
1
What does tail -f do that plain tail doesn't, and when is it useful?
2
cat /var/log/auth.log is denied. Will grep on the same file work? Why or why not?
3
Which command shows the kernel's boot and hardware messages?
Next up: Lesson 18.4: A calm method for troubleshooting
Lesson 18.4
A calm method for troubleshooting
Builds on 18.2, 18.3, and 15.3 (systemctl status).
By the end you'll understand
a repeatable loop for fixing things instead of guessing
the single most valuable habit: read the actual error message
why you change one thing at a time, then verify
The big idea
Fixing a broken machine feels like it takes luck or genius. It doesn't... it takes a loop, and the loop is the same every time. Reproduce the problem so you can see it happen. Look at the state with systemctl status. Read the log with journalctl -u SERVICE. Form a hypothesis about the cause. Change one thing. Verify it worked... and if it didn't, you've learned something, so go round again. The whole discipline is refusing to guess: each turn of the loop, the machine tells you a little more.
And here is the habit that matters more than any command: read the actual error message. Out loud, if you have to. People skim past the one line that names the problem and start changing random settings. The log for our failed nginx says "could not bind to port 80: Permission denied." That's not noise... it's the answer. It says nginx tried to claim port 80 and wasn't allowed. So the hypothesis writes itself: either something already holds port 80, or nginx doesn't have the privilege to bind a low port. Now you have a specific thing to check (ss -tlnp to see who's on port 80, from Module 11) and a specific fix to try. One change, then verify. Compare that to rebooting and hoping.
Troubleshooting is a loop, not a lucky guess: reproduce, look with systemctl status, read the log with journalctl -u, form a hypothesis, change one thing, verify... and go round again if needed. At the centre: read the actual error message.
How it works
Walk the nginx case end to end. First look: systemctl status nginx shows it isn't running and points at recent log lines. Then read the log for the specifics: journalctl -u nginx -p err gives you the error, cleanly. Read it literally... "could not bind to port 80: Permission denied." Hypothesis: something else is on port 80, or nginx can't bind a low port. Check with ss -tlnp (who's listening?) or the config. Change one thing: free the port, or fix the config line. Verify: sudo systemctl start nginx, then systemctl is-active nginx and maybe curl localhost. If it still fails, the log will now say something new, and you loop. Change one thing per turn... change three and a fix tells you nothing about which one worked.
See it for real
sam@turtle:~$ systemctl status nginx # 1–2: reproduce and look
sam@turtle:~$ journalctl -u nginx -p err # 3: read the log, only the errors
Sep 23 08:15:59 turtle nginx[1180]: ERROR: could not bind to port 80: Permission denied
Sep 23 08:16:00 turtle systemd[1]: nginx.service: Failed with result exit-code.
sam@turtle:~$ ss -tlnp # 4: hypothesis check... who's holding the ports?
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
LISTEN 0 128 127.0.0.1:631 0.0.0.0:*
sam@turtle:~$# nothing's on port 80... so nginx just wasn't allowed to bind it. One fix, then verify.
sam@turtle:~$▌
Try it yourself
In the practice terminal, run the loop: systemctl status nginx, then journalctl -u nginx -p err, then ss -tlnp to check the ports. Read the error out loud. Then practise the verify half on a service that works: sudo systemctl restart ssh, then systemctl is-active ssh. The point isn't this one bug... it's the loop you'll run for every bug after it.
Watch out
Change one thing at a time. Change several and a fix teaches you nothing... you won't know which change did it, or what you'll have to undo.
The error message is data, not decoration. Read it literally before you touch anything; it usually names the exact problem.
"Turn it off and on again" (a restart) sometimes clears a stuck state, but it's the last step, not the first. If you restart without reading the log, the same failure just comes back... and you've erased the evidence.
Check yourself
1
Name the steps of the troubleshooting loop in order.
2
The log says "could not bind to port 80: Permission denied." What does that tell you to check next?
3
Why change only one thing at a time before you verify?
Next module → Module 19: Disks, partitions, and making storage stick ... down a layer into the disks themselves: the partition table, taking a blank disk from nothing to usable with mkfs, making a mount survive a reboot in /etc/fstab, and keeping storage healthy with space, swap, and SMART.
Module 18 of Linux for the Curious Kid (and Grown-Up), Course 301. The machine turned out to have been keeping a diary all along: the systemd journal, read with journalctl and narrowed by unit, priority, boot, and time; the classic text logs in /var/log, opened with tail -f and grep, with the sensitive ones behind sudo; and dmesg for the kernel's own voice. Best of all, "it's broken" stopped being a dead end and became a loop... reproduce, look, read the real error, change one thing, verify.
Module 19: Disks, partitions, and making storage stick
Module 8 showed you that the single tree is really many disks stitched together. Now you go down a layer into the disks themselves: how a slab of storage becomes usable space, how to take a blank disk from nothing to a place for files, and how to make a mount survive a reboot instead of vanishing every time the machine restarts.
4 lessonsbuilds on Module 8Course 301
Lesson 19.1
Under the mount point: disks and partitions
Builds on 8.1 (mount points) and 2.3 (everything is a file, including /dev).
By the end you'll understand
the difference between a whole disk (sda) and a partition on it (sda1)
the four-layer stack: disk → partition → filesystem → mount point
reading it with lsblk, and the partition table with sudo fdisk -l
The big idea
In Module 8 a mount point was where a disk joined the tree. Go one layer down and here's what's actually there. A physical disk shows up in /dev with a name like sda (the first one), sdb (the second), and so on. A disk is usually carved into partitions... sda1, sda2, sda3... which are just named slices of the one slab, so you can keep, say, the system on one and your files on another. Each partition gets a filesystem written onto it (the ext4 or vfat from Module 8), and each filesystem is mounted at a point in the tree. Four layers: disk, partition, filesystem, mount point.
The map that says how a disk is sliced is its partition table. Modern disks use GPT; very old ones use MBR (which fdisk still calls dos). You rarely touch it, but you should be able to read it. lsblk draws the whole picture as a tree... disks with their partitions and where each is mounted... and sudo fdisk -l prints the partition table itself, including what kind each partition is (you'll spot the small EFI System partition that lets the machine boot).
The four layers: a physical disk (/dev/sda), sliced by a partition table (GPT) into partitions (sda1/2/3), each carrying a filesystem (vfat/ext4), each mounted into the one tree.
How it works
Start with lsblk: it draws every disk and, indented under it, its partitions with the mount point of each. You'll see sda (your main disk) branching into sda1, sda2, sda3, and sdb (the USB stick) with its one sdb1. To see the partition table itself... the type of table and what each partition is for... use sudo fdisk -l (it reads disk hardware, so it needs sudo). The whole-disk name has no number (sda); a partition adds one (sda2). That one distinction matters more than any other in this module, because the next lesson's format command erases whatever you point it at.
See it for real
sam@turtle:~$ lsblk # disks, their partitions, and mount points
NAME SIZE MOUNTPOINT
sda 512G
|-sda1 512M /boot/efi
|-sda2 118G /
`-sda3 400G /home
sdb 29G
`-sdb1 29G /mnt/usb
sam@turtle:~$ sudo fdisk -l /dev/sda # the partition table itself
Disk /dev/sda: 512G, Samsung SSD 870
Disklabel type: gpt
Device Size Type
/dev/sda1 512M EFI System
/dev/sda2 118G Linux filesystem
/dev/sda3 400G Linux filesystem
sam@turtle:~$▌
Try it yourself
In the practice terminal: run lsblk and find the two disks and their partitions. Then sudo fdisk -l to see both partition tables... notice sda is gpt and the little EFI System partition, while the USB stick sdb is an old-style dos table. Ask lsblk /dev/sdb to look at just the stick.
Watch out
sda (no number) is the whole disk; sda2 is a partition on it. Confusing the two is how people erase a whole drive by mistake... hold onto this for the next lesson.
Partition numbers can have gaps or start oddly; don't read meaning into them. A USB stick is very often just one partition, sdb1.
fdisk -l reads raw disk hardware, so it needs sudo. Reading the table is safe; fdisk only changes anything if you open a disk to edit it.
Check yourself
1
What's the difference between /dev/sdb and /dev/sdb1?
2
Name the four layers between a bare disk and a file you can open.
3
Which command shows the partition table, and why does it need sudo?
Next up: Lesson 19.2: From a blank disk to a place for files
Lesson 19.2
From a blank disk to a place for files
Builds on 19.1 and 8.2 (filesystem types).
By the end you'll understand
the three-step lifecycle that makes a blank disk usable: partition → format → mount
that formatting with mkfserases the partition
how to reformat and remount the practice USB stick, safely
The big idea
A brand-new disk is blank: no partitions, no filesystem, nowhere to put a file. Three steps make it usable. First partition it... carve the space into one or more slices (with fdisk, parted, or a friendly graphical tool like GNOME Disks). Then format each partition... write a fresh, empty filesystem onto it with mkfs, choosing the type (mkfs.ext4 for a Linux disk, mkfs.vfat or mkfs.exfat for a stick you'll share with Windows or a Mac). Then mount it where you want it in the tree.
The middle step is the sharp one. Formatting erases.mkfs lays down a brand-new empty filesystem over whatever was there... every file on that partition is gone. That's exactly what you want for a fresh disk, and a disaster if you point it at the wrong one. So the rule from the last lesson earns its keep: name the right device, and format the partition (sdb1), not the whole disk (sdb), unless you truly mean to. You can prove the whole cycle on the camera stick: unmount it, reformat it to ext4, mount it again... and it comes back empty, because that's what format means.
The lifecycle: partition (carve the space), format (write an empty filesystem... this is the step that erases), then mount. Only after all three can a file land on it.
How it works
Partitioning is done with an interactive tool (sudo fdisk /dev/sdb or sudo parted) or a desktop app; a fresh stick usually already has one partition, so you can skip straight to formatting. To format, sudo mkfs.ext4 /dev/sdb1 writes an empty ext4 filesystem (use mkfs.vfat or mkfs.exfat for a share-anywhere stick). You must unmount first... you can't reformat a filesystem that's in use. Then sudo mount it back and it's ready. Confirm the new type with lsblk -f.
See it for real
sam@turtle:~$ sudo umount /mnt/usb # can't format it while it's in use
Writing superblocks and filesystem accounting information: done
New ext4 filesystem on /dev/sdb1 (UUID d4e9c072-3b6a-4f18-8c25-1a7b9e6f0d33)
sam@turtle:~$ sudo mount /dev/sdb1 /mnt/usb # attach it back
sam@turtle:~$ lsblk -f /dev/sdb1 # it's ext4 now, and empty
NAME FSTYPE LABEL MOUNTPOINT
sdb1 ext4 /mnt/usb
sam@turtle:~$▌
Try it yourself
In the practice terminal, run the cycle: ls /mnt/usb to see the camera photos first, then sudo umount /mnt/usb, sudo mkfs.ext4 /dev/sdb1, sudo mount /dev/sdb1 /mnt/usb, and finally ls /mnt/usb again... it's empty. That's format: the photos are gone. (Try sudo mkfs.ext4 /dev/sdb1without unmounting first and it refuses... a small safety net.)
Watch out
mkfs erases everything on the target. Read the device name out loud before you press Enter: sdb1 is the stick, sda2 is your running system. There is no undo.
Format the partition (sdb1), not the whole disk (sdb), unless you deliberately want to wipe the partition table too.
You must unmount a filesystem before formatting it; the system won't let you reformat one that's in use, and that refusal is protecting you.
Check yourself
1
What are the three steps from a blank disk to a place you can save files?
2
Which step erases data, and what command performs it?
3
Why must you unmount a partition before you can format it?
Next up: Lesson 19.3: Mounts that survive a reboot
Lesson 19.3
Mounts that survive a reboot: /etc/fstab
Builds on 19.2, 8.1 (mount), and 18.3 (config files in /etc).
By the end you'll understand
why a hand mount is forgotten at the next reboot
the six fields of an /etc/fstab line, and why you name disks by UUID
testing a new line with sudo mount -a... and the wall: a bad line can stop the boot
The big idea
sudo mount works right now, but the machine forgets it the moment it reboots. To make a mount permanent, you add a line to /etc/fstab, the list the system reads at every boot to decide what to mount and where. Each line has six fields: what to mount, where to mount it, the filesystem type, the options, and two old numbers (dump, almost always 0, and pass, the order fsck checks disks at boot: 1 for root, 2 for the rest, 0 to skip).
The field that trips people up is the first one. Don't name the disk /dev/sdb1... name it by its UUID. Here's why: /dev names are just handed out in the order disks appear, so plug in a second stick and yesterday's sdb1 might come up as sdc1. A UUID is a unique id baked into the filesystem itself when it's formatted; it never changes and never moves, so a UUID line always finds the right disk. You get UUIDs from blkid. And the safety rule that matters most: after you edit /etc/fstab, run sudo mount -a (which mounts everything in the file) to test it before you reboot... because a broken fstab line can stop the machine from booting at all.
An /etc/fstab line is six fields: device, mount point, type, options, dump, pass. Name the device by its UUID (from blkid), not /dev/sdb1, because /dev names shift when disks are added while a UUID never moves.
How it works
Read the current file with cat /etc/fstab... you'll see the lines for /, /home, the EFI partition, and the swap area, each named by UUID. To add your own, find the disk's UUID with blkid, then add a line like UUID=... /mnt/usb ext4 defaults,nofail 0 2 (edit the file with sudo nano /etc/fstab). The nofail option is worth knowing: it tells the boot "if this disk isn't here, carry on anyway" ... perfect for a removable stick. Then, crucially, run sudo mount -a to mount everything in the file right now. If your line is good, the disk mounts with no reboot; if it's bad, you find out safely at a prompt instead of at a stuck boot screen.
See it for real
sam@turtle:~$ cat /etc/fstab # what gets mounted at every boot
# /etc/fstab: what to mount at boot. Find UUIDs with blkid.
sam@turtle:~$ sudo mount -a # test fstab now; quiet = all good
sam@turtle:~$▌
Try it yourself
In the practice terminal, make a mount permanent end to end: sudo umount /mnt/usb, then sudo nano /etc/fstab and add a last line UUID=5E3A-9C71 /mnt/usb vfat defaults,nofail 0 2 (save with Ctrl+O, exit with Ctrl+X), then sudo mount -a. Run df -h /mnt/usb and watch the stick come back... no reboot, and now it always will.
Watch out
A bad /etc/fstab can stop the machine from booting. Always test with sudo mount -a before you reboot; if it errors, fix the line while you still have a running system.
Add nofail for any removable or optional disk, so a missing stick doesn't hang the whole boot waiting for it.
Name disks by UUID, not /dev/sdX. The /dev name can change when you add or reorder disks; the UUID never does.
Check yourself
1
Why doesn't a plain sudo mount survive a reboot?
2
Why name a disk by UUID in /etc/fstab instead of /dev/sdb1?
3
What does sudo mount -a do, and why run it before rebooting?
Next up: Lesson 19.4: Keeping storage healthy
Lesson 19.4
Keeping storage healthy: space, swap, and SMART
Builds on 8.4 (df/du), 6.x (memory), and 18.4 (a calm method).
By the end you'll understand
watching free space so a full disk never surprises you
what swap is, and reading it with free -h and swapon --show
checking a disk's health with smartctl, and repairing a filesystem with fsck
The big idea
Three habits keep storage from biting you. Watch space: a disk that fills to 100% breaks things quietly and strangely, so df -h for the overview and du to hunt the hog (Module 8) are worth a regular glance. Know your swap: when RAM fills up, Linux moves the coldest pages out to a reserved area on disk called swap, so the machine slows down instead of crashing; free -h shows memory and swap side by side, and swapon --show lists where swap lives (here, a /swapfile). And watch health: disks wear out, usually with early warning signs the drive itself records, called SMART. sudo smartctl -H asks a drive point-blank "are you healthy?" and it answers PASSED or, if it's dying, FAILING.
One more tool for when things go wrong. If a filesystem gets corrupted... usually from a bad shutdown or a yanked cable... fsck checks it and repairs what it can. The one rule: run it on an unmounted filesystem, never a live one, or you can make the damage worse. Together these are the "is my storage OK?" toolkit: space with df, memory pressure with free, hardware with smartctl, and repair with fsck.
The "is my storage OK?" toolkit: space (df/du), swap (free -h, swapon --show), health (smartctl -H), and repair (fsck, on an unmounted filesystem).
How it works
For space, df -h shows every mounted filesystem and how full it is; when one's alarming, du -h -d1 tracks down what's eating it. For memory, free -h prints RAM and swap together, and swapon --show lists the swap areas. For health, sudo smartctl -H /dev/sda returns a one-line verdict (add nothing else and you get just the health). And if a disk ever comes up dirty, sudo umount it and sudo fsck /dev/sdb1 checks and repairs it. None of these change anything by just looking... df, free, swapon --show and smartctl -H are all safe to run any time.
See it for real
sam@turtle:~$ df -h # space: is anything close to full?
Filesystem Size Used Avail Use% Mounted on
/dev/sda2 118G 64G 48G 58% /
/dev/sda3 400G 210G 170G 56% /home
/dev/sda1 511M 6.1M 505M 2% /boot/efi
/dev/sdb1 29G 1.8G 27G 7% /mnt/usb
sam@turtle:~$ free -h # memory and swap together
total used free shared buff/cache available
Mem: 31Gi 5.9Gi 20Gi 410Mi 5.1Gi 25Gi
Swap: 2.0Gi 0B 2.0Gi
sam@turtle:~$ swapon --show # where swap actually lives
NAME TYPE SIZE USED PRIO
/swapfile file 2.0Gi 0B -2
sam@turtle:~$ sudo smartctl -H /dev/sda # is the disk healthy?
SMART overall-health self-assessment test result: PASSED
sam@turtle:~$▌
Try it yourself
In the practice terminal: run free -h and find the swap line, then swapon --show to see the /swapfile. Ask the disk how it's doing with sudo smartctl -H /dev/sda. Then try a repair: sudo umount /mnt/usb and sudo fsck /dev/sdb1 (it reports clean). Try sudo fsck /dev/sda2 while it's mounted and watch it refuse... that refusal is the safety rule in action.
Watch out
Swap is a safety net, not a speed-up. A little idle swap is normal; constant heavy swapping ("thrashing") means you're out of RAM, and the fix is more memory, not more swap.
Never fsck a mounted filesystem. Unmount it first (you can't unmount / while running it, so that one is checked at boot instead). Running fsck live can corrupt the very thing you're trying to fix.
SMART ... PASSED is reassuring, not a promise... drives can still fail suddenly. It's a reason to keep backups current, which is the whole of the next module.
Check yourself
1
What is swap, and which command shows it alongside RAM?
2
What does smartctl -H tell you, and can you trust a PASSED completely?
3
What must be true of a filesystem before you run fsck on it?
Next module → Module 20: Backups that actually save you ... the copy that saves you when a disk dies or a file's deleted: the 3-2-1 rule, bundling with tar, the smart incremental copy with rsync, and a backup you've actually restored and automated.
Module 19 of Linux for the Curious Kid (and Grown-Up), Course 301. The disk stopped being a mystery slab and became a stack you can read: a physical disk sliced by a partition table into partitions, each carrying a filesystem, each mounted into the one tree. You can take a blank disk from nothing to usable (partition, mkfs, mount), make a mount stick across reboots with a UUID line in /etc/fstab (always tested with mount -a so a typo never traps the boot), and keep the whole lot healthy by watching space, swap, and SMART... with fsck waiting for the day a filesystem needs repair.
Disks fail, fingers slip, and files get deleted at exactly the wrong moment. A backup is the second copy that turns a catastrophe into a shrug. This module is the whole craft of it: what really counts as a backup, how to bundle and copy files efficiently, and... the part everyone skips... how to be sure you can get your data back.
4 lessonsbuilds on Module 19Course 301
Lesson 20.1
Why back up, and the 3-2-1 rule
Builds on 19.4 (disks fail) and 5.x (you can delete your own files).
By the end you'll understand
what a backup really is: a separate copy you can restore from
the 3-2-1 rule, the one guideline that captures decades of hard lessons
why a copy on the same disk, and even a live sync or RAID mirror, is not a backup
The big idea
A backup is a second copy, somewhere else, that you can restore from after the original is gone. Originals die three ways: hardware (a disk fails... SMART from Module 19 only warns you, it can't save the data), human (you rm the wrong folder or overwrite a file), and malice or loss (ransomware, a stolen laptop, a house fire). A backup is your answer to all three.
The guideline that survives every disaster story is the 3-2-1 rule: keep 3 copies of anything you care about, on 2 different kinds of media, with 1 of them offsite (another building, or the cloud). And here are the two traps that catch people who think they're safe. A copy in another folder on the same disk is not a backup... when that disk dies, both copies die together. And a live sync or RAID mirror is not a backup either: it copies changes instantly, so the moment you delete a file or ransomware encrypts one, that deletion or damage is faithfully mirrored to the other copy too. A real backup is separated in space (a different disk) and in time (yesterday's copy still exists).
The 3-2-1 rule: 3 copies, on 2 kinds of media, 1 of them offsite. A second folder on the same disk isn't a backup (they die together), and a RAID mirror or live sync isn't either (it copies your mistakes and ransomware straight through).
How it works
Decide what to protect (your documents, photos, and any config you've hand-edited... not the operating system itself, which you can reinstall). Pick a target on a different disk: the USB stick, another machine on your network, or a cloud service. Then make copies on a schedule, and keep a few older ones so you can go back in time. The rest of this module is the how: tar to bundle it, rsync to copy it cheaply, and a tested restore so you know it all works.
See it for real
sam@turtle:~$ ls -lh ~/documents # what I want to protect
-rw-r--r-- 1 sam sam 240K Aug 30 10:05 resume.pdf
-rw-r--r-- 1 sam sam 3.0M Sep 18 13:22 taxes-2025.pdf
sam@turtle:~$ df -h /mnt/usb # a separate disk to hold a copy
Filesystem Size Used Avail Use% Mounted on
/dev/sdb1 29G 1.8G 27G 7% /mnt/usb
sam@turtle:~$▌
Watch out
A copy on the same disk is not a backup. If the disk fails, you lose the original and the copy in one stroke. A backup lives on different hardware.
Sync and RAID are for availability, not backup. They mirror changes instantly, so a deletion or ransomware reaches the mirror just as fast. Keep older, separate copies.
Don't try to back up the whole OS. Back up your data and your hand-edited configs; the system itself you can reinstall.
Check yourself
1
State the 3-2-1 rule in your own words.
2
Why isn't a second folder on the same disk a real backup?
3
Why doesn't a RAID mirror protect you from deleting a file by accident?
Next up: Lesson 20.2: Bundling files with tar (and gzip)
Lesson 20.2
Bundling files with tar (and gzip)
Builds on 20.1 and 3.x (files and folders).
By the end you'll understand
tar: rolling a whole folder tree into one file (an "archive")
the flags that do it: c create, x extract, t list, plus z (compress) and f (file)
gzip for squeezing a single file smaller
The big idea
A backup is easier to move and store as one file than as a thousand loose ones. tar ("tape archive", from the old days) rolls an entire folder tree... files, subfolders, permissions and all... into a single archive file. Add compression and you also make it smaller. The classic combination is tar -czf name.tar.gz folder, and once you can read those flags you can drive tar forever: c = create, z = gzip-compress it, f = the archive filename comes next. Swap the first letter to work the other way: t = list what's inside (always look before you extract), x = extract it back out. Add v anywhere for a verbose, file-by-file view.
The naming is a convention, not magic: .tar is an uncompressed bundle, .tar.gz (or .tgz) is a gzip-compressed one. For a single file rather than a folder, gzip compresses it in place (report.log becomes report.log.gz), and gunzip brings it back. Text and logs shrink dramatically; already-compressed things like JPEGs and PDFs barely budge, which is normal.
One archive, three moves: tar -czf rolls a folder into backup.tar.gz, tar -tzf lists what's inside, tar -xzf extracts it back. The flags read as create / extract / list, plus z (gzip) and f (file).
How it works
Create a compressed archive of a folder: tar -czf documents.tar.gz documents. Before you ever trust or extract one, list it: tar -tzf documents.tar.gz shows every member. To extract, tar -xzf documents.tar.gz unpacks into the current folder, or add -C somewhere to unpack there instead (handy so you don't overwrite the originals while testing). For a lone file, gzip big.log squeezes it to big.log.gz and gunzip big.log.gz restores it. Reach for tar when it's a folder, gzip when it's one file.
See it for real
sam@turtle:~$ tar -czf documents.tar.gz documents # roll the folder into one file
sam@turtle:~$ ls -lh documents.tar.gz # one tidy, smaller file
-rw-r--r-- 1 sam sam 1.5M Sep 23 10:00 documents.tar.gz
sam@turtle:~$ tar -tzf documents.tar.gz # always look before extracting
In the practice terminal: tar -czf documents.tar.gz documents, then tar -tzf documents.tar.gz to peek inside. Extract it somewhere safe with mkdir restore and tar -xzf documents.tar.gz -C restore, then ls restore/documents. Separately, try single-file compression: gzip game.log (watch it become game.log.gz) and gunzip game.log.gz to bring it back.
Watch out
The f flag means "the next word is the filename". tar -czf backup.tar.gz folder works; putting the name elsewhere confuses it. Keep f last in the cluster, name right after.
List before you extract (tar -tzf). An archive can unpack a whole tree into your current folder; know what's coming, and use -C to send it somewhere clean.
Compression isn't magic. Text and logs shrink a lot; JPEGs, MP4s and PDFs are already compressed and barely change... that's expected, not a failure.
Check yourself
1
What do the letters in tar -czf each mean?
2
How do you see what's inside an archive without extracting it?
3
When would you use gzip instead of tar?
Next up: Lesson 20.3: The smart copy, rsync
Lesson 20.3
The smart copy: rsync
Builds on 20.2, 3.x (cp), and 11.4 (ssh).
By the end you'll understand
why rsync beats cp for backups: it copies only what changed
the everyday flags: -a (keep everything), -v (show it), --dry-run, --delete
the trailing-slash rule, and copying to another machine over ssh
The big idea
cp -r copies everything, every time. rsync is the smart copy: it compares source and destination and transfers only what actually changed. The first run copies everything; the second run, if nothing changed, copies almost nothing and finishes instantly. That's what makes it the backup workhorse... you can re-run it every night and it only moves the day's differences. The everyday incantation is rsync -av SRC/ DST/, where -a is "archive mode" (preserve permissions, timestamps, owners, and recurse into folders... everything you'd want in a faithful copy) and -v is verbose, so you see what moved.
Two flags are worth real respect. --dry-run shows you exactly what would happen without touching anything... always use it the first time you run a new rsync. --delete makes the destination an exact mirror of the source, which means it deletes files from the backup that you removed from the source. That's powerful and dangerous, so it lives behind --dry-run until you're sure. And there's a famous gotcha in the source path: a trailing slash matters. rsync -av docs/ backup/ copies the contents of docs into backup; rsync -av docs backup/ (no slash) copies the folder itself, giving you backup/docs. Best of all, the destination can be another machine: rsync -av docs/ sam@server:/backups/ copies straight over ssh to your offsite copy.
rsync transfers only the differences, so re-running it is cheap. --dry-run previews without changing anything, --delete makes an exact mirror, a trailing slash on the source copies its contents, and a user@host: destination sends it over ssh.
How it works
The backup you'll run most is rsync -av SRC/ DST/. Run it once and everything copies; run it again and it flies, because only changes move. Trying a new one? Put --dry-run in first and read what it says it will do. Want the destination to be a perfect mirror (so deleted files vanish from the backup too)? Add --delete... after a dry run. Mind the trailing slash on the source: SRC/ copies the contents, SRC copies the folder. And to reach another machine, just make the destination user@host:/path, and rsync rides ssh there.
See it for real
sam@turtle:~$ rsync -av documents/ /mnt/usb/docs/ # first run: everything copies
sam@turtle:~$ rsync -av --delete documents/ /mnt/usb/docs/ # mirror: also remove what's gone from source
sending incremental file list
sent 132 bytes received 35 bytes total size 3.2M
sam@turtle:~$▌
Try it yourself
In the practice terminal: rsync -av documents/ backup/, then run the same line again and watch the second run copy nothing... that's the magic. Now change something: echo hi > documents/new.txt, then rsync -av --dry-run documents/ backup/ to see it would copy new.txt without doing it, then run it for real. Delete it (rm documents/new.txt) and try rsync -av --delete documents/ backup/ to watch the mirror remove it too.
Watch out
--delete removes files from the destination. Point it at the wrong target and it happily empties it to match. Always --dry-run a --delete first.
The trailing slash changes the result: docs/ copies the contents, docs copies the folder. Pick deliberately, and dry-run if unsure.
rsync over ssh needs the same access ssh does (Module 11). Set up key-based login (next module) so a nightly backup doesn't stop to ask for a password.
Check yourself
1
What does rsync do that plain cp doesn't, and why does it matter for backups?
2
What does --dry-run do, and when should you always use it?
3
What's the difference between rsync -av docs/ backup/ and rsync -av docs backup/?
Next up: Lesson 20.4: A backup you can restore
Lesson 20.4
A backup you can restore (and automate)
Builds on 20.2, 20.3, 13.x (scripts), and 16.x (cron).
By the end you'll understand
the rule that matters most: an untested backup is not a backup
how to restore from a tar archive or an rsync copy
tying it together into a tiny script that cron runs for you every night
The big idea
Here's the hard truth that every veteran learns once, painfully: a backup you've never restored from is not a backup... it's a hope. Plenty of people run backups for years and discover, on the worst day, that the archive was empty, or the script was backing up the wrong folder, or the file won't unpack. The only way to know a backup works is to restore it and check. So the drill is always four beats: back up → verify it exists → restore a copy and check it → then trust it.
Restoring is just running your tools the other way. From a tar archive, tar -xzf it (into a scratch folder first, with -C, so you compare before overwriting anything real). From an rsync copy, rsync it back the other direction. Once the drill works by hand, you automate it: put the backup commands in a small script (Module 13), make it executable, and let cron (Module 16) run it every night while you sleep. That's the whole thing... the machine backs itself up, and because you tested the restore, you actually believe it.
The drill: back up, verify the archive is there, restore a copy and check it, then trust it. Then wrap the commands in a small backup.sh and let cron run it nightly... a machine that backs itself up, and a restore you've actually seen work.
How it works
To restore from an archive, unpack it somewhere safe first: tar -xzf documents.tar.gz -C /tmp/restore, then ls and open a file to be sure it's real before you copy it back over anything. To restore from an rsync backup, run rsync the other way (backup → original). To automate, write a two-line script that makes the archive (or runs the rsync), chmod +x it, and add a cron line like 0 2 * * * ~/backup.sh to run it at 2am every day. Then, once in a while, do the restore drill again... because the day you need it is the wrong day to find out it stopped working.
sam@turtle:~$ tar -xzf documents.tar.gz -C /tmp/restore
sam@turtle:~$ ls /tmp/restore/documents # it's really there... backup proven
resume.pdf taxes-2025.pdf
sam@turtle:~$ cat backup.sh # the whole job in a tiny script
#!/bin/bash
tar -czf /mnt/usb/docs-backup.tar.gz ~/documents
echo "backed up $(date +%F)"
sam@turtle:~$ ./backup.sh # run it by hand once
backed up 2026-09-23
sam@turtle:~$▌
Try it yourself
In the practice terminal, run the restore drill: tar -czf documents.tar.gz documents, mkdir -p /tmp/restore, tar -xzf documents.tar.gz -C /tmp/restore, and ls /tmp/restore/documents to prove it. Then automate: write a backup.sh in nano (the two lines above), chmod +x backup.sh, run ./backup.sh, and finally crontab -e to add 0 2 * * * ~/backup.sh so it runs itself every night.
Watch out
Test the restore, not just the backup. A backup that's never been restored is a guess. Unpack it to a scratch folder and actually open a file.
Restore into a scratch place first (-C /tmp/restore), then copy back. Extracting straight over your live files can overwrite good data with an old copy.
A cron backup runs with a bare environment (Module 16), so use full paths in the script and send its output to a log, so a silent failure doesn't hide for months.
Check yourself
1
Why is "an untested backup is not a backup" the most important rule here?
2
Why restore into a scratch folder instead of straight over the originals?
3
What two tools from earlier modules turn a manual backup into a nightly automatic one?
Next module → Module 21: Locking it down ... the finale, and the one that keeps all the others safe: the security mindset, SSH keys for logins that can't be guessed, a firewall with ufw, and the everyday habits that cover almost everything.
Module 20 of Linux for the Curious Kid (and Grown-Up), Course 301. Backups stopped being a vague good intention and became a craft: a real backup is a separate copy in another place (the 3-2-1 rule), you bundle a folder into one file with tar and squeeze single files with gzip, you copy only what changed with rsync (dry-run first, --delete with care, straight over ssh to offsite), and... the part that actually saves you... you restore a copy to prove it works, then let a tiny script and cron do it every night.
The last module, and the one that keeps all the others safe. Security isn't a product you install... it's a handful of habits and a couple of tools that, together, close the doors attackers walk through. You'll learn the mindset, set up passwordless logins that are also stronger, put up a firewall, and finish with the everyday habits that cover almost everything.
4 lessonsbuilds on Modules 5, 11, 17Course 301 · finale
Lesson 21.1
The security mindset
Builds on 17.4 (least privilege), 9.x (updates), and 15.x (services).
By the end you'll understand
that security is a posture, not a single tool you switch on
the four ideas that carry most of the weight: least privilege, patching, attack surface, defense in depth
what "attack surface" means, and how to see yours
The big idea
There's no button labelled "secure". Real security is a handful of ideas applied consistently, and four of them carry most of the weight. Least privilege (Module 17): give every person and program the least access that does the job, so a mistake or a break-in reaches as little as possible. Patching (Module 9): keep the system updated, because so many attacks use known holes that an update already closed... an out-of-date machine is the low-hanging fruit. Attack surface: every running service is a door someone can try, so turn off and uninstall what you don't use... fewer doors, fewer ways in. And defense in depth: don't rely on one wall; stack layers (a firewall and good passwords and least privilege), so if one fails the others still hold.
Behind all of it is a simple question: what are you actually defending against? For most machines the honest answer is automated bots that scan the whole internet trying default passwords (you saw their "Failed password" attempts in Module 18's auth.log), plus the everyday risks of a lost laptop or a careless click. You don't need to be a spy to be a target... you just need to be reachable. The good news: the same few habits that stop the bots stop almost everything else too.
Two halves of the mindset: shrink your attack surface (close doors you don't use) and build defense in depth (stack a firewall, keys, least privilege, and updates so one failure isn't fatal).
How it works
You already have the tools to act on this. See your open doors with ss -tln (Module 11)... each listening port is a service exposed to the network. Stop and disable the ones you don't need with systemctl (Module 15), or uninstall them with apt (Module 9). Keep everything patched with sudo apt update && sudo apt upgrade. Check you're not running as root out of habit with groups and a well-placed sudo (Module 17). The next two lessons add the two big pieces that aren't yet in your kit: strong logins (SSH keys) and a firewall (ufw).
See it for real
sam@turtle:~$ ss -tln # my open doors: what's listening on the network
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
LISTEN 0 128 127.0.0.1:631 0.0.0.0:*
sam@turtle:~$ sudo apt update # the single biggest security habit: stay patched
12 packages can be upgraded. Run 'apt list --upgradable' to see them.
sam@turtle:~$▌
Watch out
Port 22 (ssh) listens on 0.0.0.0... every network. Port 631 (printing) listens only on 127.0.0.1, the machine itself, so the network can't reach it. That difference is the whole idea of attack surface: what's exposed, and to whom.
"I'm not important enough to attack" is the belief that gets people owned. The bots don't know or care who you are; they scan everything. Being reachable is enough.
Security is layers. Don't lean everything on one measure... a firewall doesn't excuse a weak password, and a strong password doesn't excuse an unpatched hole.
Check yourself
1
What does "attack surface" mean, and how do you make it smaller?
2
Why is keeping the system updated one of the most important security habits?
3
What does "defense in depth" protect you from that a single strong wall doesn't?
Next up: Lesson 21.2: SSH keys, logins without passwords
Lesson 21.2
SSH keys: logins without passwords (that are also safer)
Builds on 21.1, 11.4 (ssh), and 7.x (file permissions).
By the end you'll understand
why a key pair beats a password for logging in
the two halves: a private key that never leaves your machine, and a public key you can share freely
making a pair with ssh-keygen and installing it with ssh-copy-id
The big idea
A password can be guessed, and the bots from the last lesson guess millions of them. An SSH key pair can't be guessed in any human timescale, and it's more convenient too. A key pair is two matching files: a private key (your secret half) and a public key (its harmless partner). The magic is that the two only work together, and you can't work out the private half from the public one. So the rule is simple and absolute: the private key never leaves your machine, and the public key you can hand out to anyone. You put your public key on a server; when you connect, the server sets a little math puzzle that only your private key can answer, and you're in... no password sent, nothing to guess.
You make a pair with ssh-keygen -t ed25519 (ed25519 is a modern, strong key type). It writes two files into ~/.ssh: id_ed25519 (private... guard it) and id_ed25519.pub (public... shareable). You install the public one on a server with ssh-copy-id user@host, which appends it to the server's ~/.ssh/authorized_keys. From then on, ssh user@host just lets you in. Two more things matter: put a passphrase on the private key when ssh-keygen asks, so a stolen laptop doesn't hand over the key; and the permissions matter... ~/.ssh must be 700 and the private key 600 (Module 7), or ssh refuses to use them, on purpose.
A key pair: ssh-keygen makes a private key (stays home, behind a passphrase) and a public key. ssh-copy-id carries only the public half to the server's authorized_keys, and then ssh logs you in with no password to guess.
How it works
Make the pair once: ssh-keygen -t ed25519, and when it asks, give it a passphrase. Look at what it made with ls -la ~/.ssh... two files, tight permissions. Your public key is plain text you can read and share: cat ~/.ssh/id_ed25519.pub. Put it on a server with ssh-copy-id user@host (it'll ask for the password one last time to install the key). After that, ssh user@host logs you in with the key. When you have keys working everywhere, the next lesson's habit is to turn password logins off entirely.
See it for real
sam@turtle:~$ ssh-keygen -t ed25519 # make a key pair (add a passphrase when asked)
Generating public/private ed25519 key pair.
Your identification has been saved in /home/sam/.ssh/id_ed25519
Your public key has been saved in /home/sam/.ssh/id_ed25519.pub
sam@turtle:~$ ls -la ~/.ssh # two files, and note the tight permissions
-rw------- 1 sam sam 126 Sep 23 10:00 id_ed25519
-rw-r--r-- 1 sam sam 83 Sep 23 10:00 id_ed25519.pub
sam@turtle:~$ ssh-copy-id sam@server # install the PUBLIC key on the server
Number of key(s) added: 1
(this practice terminal has no real server, so the install is simulated)
sam@turtle:~$▌
Try it yourself
In the practice terminal: run ssh-keygen -t ed25519, then ls -la ~/.ssh to see the pair and their permissions. Read your public key with cat ~/.ssh/id_ed25519.pub... that's the half you'd paste into a server or a service like GitHub. Install it with ssh-copy-id sam@server. Notice the private key's mode is 600 (only you can read it), which is exactly what SSH insists on.
Watch out
Never share or move the private key (id_ed25519, no .pub). It's the whole secret. Only ever copy the .pub. If a private key leaks, make a new pair and remove the old public key from your servers.
Put a passphrase on the private key. Without one, anyone who copies the file is you. With one, a stolen laptop is far less scary.
Permissions are enforced: ~/.ssh must be 700 and the private key 600. If they're looser, SSH refuses the key rather than trust it... a feature, not a bug.
Check yourself
1
Which half of the key pair can you share, and which must never leave your machine?
2
What does ssh-copy-id put where, and what does it let you stop typing?
3
Why put a passphrase on your private key?
Next up: Lesson 21.3: The firewall, ufw
Lesson 21.3
The firewall: ufw
Builds on 21.1 (attack surface), 11.3 (ports), and 17.4 (sudo).
By the end you'll understand
what a firewall does: decide which ports accept connections
ufw, the uncomplicated firewall: default-deny, then allow only what you need
the near-miss that catches everyone: enabling the firewall before allowing SSH
The big idea
Last lesson you saw your open doors with ss -tln. A firewall is the doorman: for every incoming connection it checks the port and decides allow or deny. On Linux the friendly front-end is ufw ("uncomplicated firewall"), and its whole philosophy fits in one line: deny incoming by default, allow outgoing, and then open only the specific doors you actually need. Your machine can still reach out to the internet, but nothing on the outside can start a connection to a port you haven't explicitly allowed. Even if some forgotten service is listening, the firewall keeps the network from ever reaching it... attack surface, closed at the door.
Setting it up is three commands: allow what you need, turn it on, check it. sudo ufw allow ssh opens port 22, sudo ufw enable turns the firewall on, and sudo ufw status shows the rules. But notice the order, because here is the single most famous foot-gun in this whole book: if you enable the firewall on a remote machine before allowing SSH, you lock yourself out the instant the connection drops... and you can't SSH back in to fix it, because you just blocked SSH. ufw even warns you. So the ironclad habit is: allow SSH first, always, then enable.
ufw is a doorman: deny incoming by default, then allow only the ports you need. The one rule that saves careers: allow SSH before you enable the firewall, so a remote machine doesn't slam the door on you.
How it works
Check the state first: sudo ufw status (a fresh box says inactive). Allow what you need before turning anything on: sudo ufw allow ssh (or sudo ufw allow 22), and sudo ufw allow 80/tcp if you run a web server. Then sudo ufw enable. Confirm with sudo ufw status verbose, which shows the default policy and every rule. To remove a rule, sudo ufw delete allow 80/tcp; to start over, sudo ufw reset. Everything needs sudo, because a firewall is a system-wide gate.
See it for real
sam@turtle:~$ sudo ufw status # fresh box: the firewall is off
Status: inactive
sam@turtle:~$ sudo ufw allow ssh # FIRST: open SSH so you don't lock yourself out
Rules updated
Rules updated (v6)
sam@turtle:~$ sudo ufw enable # now it's safe to turn on
In the practice terminal, feel the foot-gun safely: run sudo ufw enablewithout allowing SSH first and read the warning it gives you. Then do it right: sudo ufw allow ssh, sudo ufw enable, and sudo ufw status verbose to see your rules. Open a web port with sudo ufw allow 80/tcp, check the status again, then remove it with sudo ufw delete allow 80/tcp.
Watch out
Allow SSH before you enable, every time, especially on a machine you reach over the network. Enable first and you can lock yourself out with no way back in.
A firewall complements the other layers, it doesn't replace them. It stops the network from reaching a port; it doesn't fix a weak password or an unpatched service behind an allowed port.
Open only what you need. Every allow is a door you've chosen to leave open, so keep the list short and delete rules you no longer use.
Check yourself
1
What is ufw's default policy for incoming connections, and why is that the safe default?
2
Why must you allow ssh before you enable on a remote machine?
3
A firewall blocks a port. What kinds of problems does it not solve?
Next up: Lesson 21.4: Everyday habits (and the finish line)
Lesson 21.4
Everyday habits (and the finish line)
Builds on all of Course 301, and everything before it.
By the end you'll understand
the handful of habits that cover almost all real-world risk
a few "turn it off" hardening steps once your keys and firewall are working
the one instinct worth keeping for life: think before you paste
The big idea
You don't need to be a security expert to be a hard target... you need a short list of habits, done consistently. Keep it updated (turn on automatic security updates and don't ignore them). Use strong, unique passwords with a password manager, so one leaked site doesn't unlock the rest. Log in with keys, then turn passwords off: once SSH keys work everywhere, set PasswordAuthentication no and PermitRootLogin no in the SSH config so the bots have nothing to guess at. Use sudo, not root (Module 17): work as yourself and borrow power only when you need it. Back up (Module 20), because a good backup is what turns ransomware from a catastrophe into an afternoon. And lock your screen when you walk away... the most common "attacker" is someone already in the room.
One instinct outranks all the tools: think before you paste. The internet is full of "just run this to fix it" one-liners, and curl SOMEURL | sudo bash hands a stranger's script full control of your machine, sight unseen. Read a command before you run it; understand what each piece does (Module 4 gave you the grammar to); and be extra careful with anything that pipes a download straight into a shell. That habit... curiosity plus a little caution... is the same one that carried you through this whole book. You started at a blinking cursor not knowing what an operating system was. You now understand the system, can move and shape files, speak the shell, write programs, run services, schedule work, manage users, read the logs, handle disks, keep backups, and lock the whole thing down. That's not a beginner's toolkit anymore. That's someone who gets Linux. Welcome... you made it.
The habits that cover almost everything: update, strong unique passwords, keys then passwords off, sudo not root, back up, lock your screen, and... above all... think before you paste. That last one is the instinct that carried you through the whole book.
How it works
Most of these are one-time switches plus a standing attitude. Turn on unattended security updates and let apt do its job. Once keys work (last two lessons), edit the SSH server config to refuse passwords and root logins, then reload it. Keep working as your normal user and reach for sudo deliberately. Let your backups (Module 20) run on their cron schedule and test a restore now and then. And when someone online says "just paste this," slow down and read it first... the same command grammar you learned in Course 101 is exactly what lets you tell a helpful one-liner from a harmful one.
See it for real
sam@turtle:~$ groups # I work as sam, and borrow root only via sudo
sam sudo
sam@turtle:~$ sudo ufw status # firewall on since Lesson 21.3
Status: active
To Action From
-- ------ ----
22/tcp ALLOW Anywhere
22/tcp (v6) ALLOW Anywhere (v6)
sam@turtle:~$# read this before you ever run it:
sam@turtle:~$# curl https://some-site/install.sh | sudo bash ← hands a stranger full control. Don't.
sam@turtle:~$▌
Watch out
Turn off SSH password login only after your keys work and you've tested logging in with them. Do it in the wrong order and you can lock yourself out, just like the firewall.
curl ... | sudo bash runs code you never read, as root. If you must, download the script first, read it, then run it. Treat "paste this to fix it" with the same caution as a stranger's USB stick.
Security is maintenance, not a one-time setup. The machine you hardened last year is only as safe as its last update and its last tested backup.
Check yourself
1
Name three everyday habits that cover most real-world risk.
2
Why turn off SSH password login only after keys are working?
3
Why is curl ... | sudo bash dangerous, and what's the safer way?
Module 21 of Linux for the Curious Kid (and Grown-Up), and the finale of the whole book. Security turned out to be a posture, not a purchase: shrink the attack surface and stack your defenses, log in with SSH keys instead of guessable passwords, stand a firewall at the door with ufw (allowing SSH first, always), and keep the everyday habits... update, back up, least privilege, and think before you paste. And that's the book. Three courses ago you didn't know what an operating system was; now you understand the system from the kernel to the cursor, and you can run it, shape it, script it, and defend it. The machine isn't a black box anymore. It's yours. Go build something.