Mission Brief · 2025

LINUX CADET
TRAINING MANUAL

A 4-hour cosmic journey from ground zero to Linux hero. Master 10 systems, earn 1000 XP, graduate as a certified penguin pilot.

Mission · LNX-001
Cosmic Notebook Edition
4 hours · 10 chapters · 1000 XP
Pre-Flight Briefing

Your 4-Hour Cosmic Flight Plan

⏱ 15 min read ★ +50 XP on completion Difficulty: Ground Control

Welcome aboard, cadet. You're about to embark on a four-hour mission that will take you from "What is Linux?" all the way to writing your own shell scripts and debugging live processes on a remote server. This manual was crafted from the full transcript of the "Complete Linux Course with Networking and Shell Scripting" video series by Abhishek, structured into a single self-paced flight plan that fits in one focused afternoon.

The course itself is split into ten thematic sections across five episodes, plus a bonus shell-scripting tutorial. We've compressed every concept, every demo, every interview question, and every command from that ~10-hour video into a hands-on journey you can finish in 240 minutes. The trick is that we've front-loaded the practical skills — file permissions, process management, networking, and shell scripting get the lion's share of your time, because those are the muscles you'll actually flex in any DevOps, SRE, or backend engineering role.

How to use this manual

Each chapter follows the same cosmic structure so your brain can settle into a rhythm. Read the concept section first — it builds the mental model. Then type out every command in the code blocks in your own terminal. Tackle the Hands-on Lab — these are mandatory, not optional, because Linux lives in your fingers, not in your head. Take the Mini-Quiz to lock the knowledge in. Tick the chapter's checkbox, log your XP, and move on to the next system.

i Pre-flight checklist

Your 240-minute flight plan

This is the hands-on heavy time allocation. The intro and history chapters are deliberately short — you'll spend your real time on permissions, processes, networking, and scripting, because that's where the actual engineering happens.

00:15
Ch 1 · Liftoff — OS & Linux Fundamentals
Set up your Ubuntu environment via WSL or Docker. Learn what an OS is, why Linux exists, and how package managers work.
+50 XP
00:30
Ch 2 · Star Charts — Folder Structure
Tour the entire Linux filesystem: /etc, /var, /home, /opt, /tmp. Learn where everything lives and why.
+80 XP
00:25
Ch 3 · Crew Roster — User Management
Create users and groups, switch identities, understand /etc/passwd & /etc/shadow, set up SSH access.
+100 XP
00:20
Ch 4 · Cargo Handling — File Management
Create, read, write, copy, move, delete files. Master cat, less, head, tail, and the critical > vs >> distinction.
+80 XP
00:15
Ch 5 · Captain's Log — vi Editor
The three modes of vim, must-know shortcuts, save & quit rituals. You'll use this every single day.
+70 XP
00:40
Ch 6 · Security Force Field — File Permissions ★
The most important chapter. rwx, numeric chmod, the bank-and-locker analogy, chown. Includes the trick that breaks beginners.
+150 XP
00:30
Ch 7 · Engineering Bay — Process Management
ps aux vs ps -ef, kill vs kill -9, signals, renice priorities, systemctl services.
+130 XP
00:25
Ch 8 · Observatory — Monitoring & Disk
top, htop, free, df, du for monitoring. lsblk, mkfs, mount for disk management. Real troubleshooting tools.
+120 XP
00:25
Ch 9 · Comms Array — Networking
IP addresses, subnets, CIDR math, ports, the full OSI 7-layer model, TCP 3-way handshake. Essential for debugging.
+120 XP
00:25
Ch 10 · Automation Engine — Shell Scripting ★
Write your first scripts. Shebang, set -exo pipefail, variables, if/for loops, signals & trap, awk, curl, wget.
+200 XP
Total Mission XP Available 1100 / 1000 required to graduate
! Hands-on pledge

Reading alone will not make you a Linux engineer. Every command in this guide, you type yourself. Every lab, you complete. Every quiz, you answer before peeking at the answer. If you skip the labs, you'll forget 80% of this by tomorrow morning. The kindest thing you can do for your future self is to keep that terminal open right now and follow along.

Chapter 01 · Liftoff

Operating Systems & Linux Fundamentals

⏱ 15 min ★ +50 XP Difficulty: Ground Control

Why does an Operating System exist?

Hardware is dumb. A CPU by itself just sits there waiting for instructions. Memory chips store bits. Storage holds files. Network interfaces push electrons. None of these components know how to talk to a human or to an application — they only know how to receive electrical signals and respond with electrical signals. The operating system is the layer of software that sits between the hardware and everything else, translating "save this file" into the specific sequence of disk writes, memory allocations, and CPU instructions that actually make it happen.

The OS has four core responsibilities: process management (deciding which program runs when), memory management (allocating RAM fairly), device management (talking to keyboards, disks, GPUs), and network management (TCP/IP stacks, sockets). Without an OS, every application would have to ship with its own disk drivers and memory allocators — chaos. The OS is the universal translator that lets a Python script, a web browser, and a database server all coexist on the same machine without knowing anything about the underlying hardware.

A brief history of operating systems

In the 1960s, Unix was born at Bell Labs — the first widely-used multi-user operating system, and it was open source in spirit if not always in licensing. In the 1970s, Minix emerged as a small Unix-like teaching OS. In the 1980s, Microsoft released Windows, which revolutionised personal computing by giving the masses a rich graphical interface. Then, in 1991, a Finnish student named Linus Torvalds announced on a Usenet group that he was building a "free operating system" — just a hobby, won't be big and professional. That kernel became Linux, and today it runs roughly 90% of production servers, all Android phones, all Chromebooks, most of the public cloud, and the entire Top500 supercomputer list.

The reason Linux won the server war is straightforward: it's open source (anyone can read, modify, and audit the code), free (no licensing fees), secure (decades of peer review, granular permissions model), and community-backed (thousands of contributors, vast documentation, no single vendor lock-in). Windows still dominates the desktop because of its GUI, but for anything that runs in a datacenter — web servers, databases, AI training clusters, container workloads — Linux is the default.

The structure of a Linux system

Linux is layered, like an onion (or a planetary atmosphere). At the centre is the hardware: CPU, RAM, disk, NIC. Wrapping that is the Linux kernel — the engine room. The kernel talks directly to hardware, schedules processes, manages memory, and exposes system calls. Above the kernel sit system libraries and utilities (the GNU coreutils, glibc, etc.) that wrap raw system calls into usable functions like printf or open(). Finally, at the top, you have the shell and applications — bash, vim, python, your web server. You almost never touch the kernel directly; you talk to it through layers of libraries.

Linux distributions — pick your ship

Because the kernel is open source, anyone can build a complete OS on top of it. These complete packaged OSes are called distributions, or "distros" for short. Companies and communities take the Linux kernel, add a package manager, a set of default utilities, sometimes a GUI, and ship it as a product. The main distros you'll encounter:

DistroMaintainerPersonality
UbuntuCanonicalBeginner-friendly, comes with everything pre-installed. Recommended starting point.
Red Hat / RHELRed Hat (IBM)Enterprise-grade, paid support. Common in banks and large enterprises.
FedoraRed Hat communityBleeding-edge, testing ground for RHEL. Developer-friendly.
DebianDebian ProjectRock-solid stability, slow release cycle. Ubuntu's upstream parent.
AlpineAlpine communityTiny (~5MB), security-focused. Used in most Docker images.

For this manual, use Ubuntu. It's the friendliest, comes with apt pre-installed, and matches what the original course uses. Shell scripts written on one distro generally work on every other distro without modification — the kernel and system libraries are common across all of them. The differences are mostly in the package manager (apt on Debian/Ubuntu, yum/dnf on Red Hat, apk on Alpine) and the default configuration.

Package managers — your app store

A package manager is the Linux equivalent of an app store, but older, faster, and more powerful. Instead of downloading installers from random websites (the Windows way), you run one command and the package manager reaches out to a centralised repository, downloads the package, resolves all its dependencies, and installs everything in the right place. On Ubuntu, that manager is apt, and its repos live at archive.ubuntu.com and ports.ubuntu.com.

bash
# Always update package metadata before installing anything new $ apt update Hit:1 http://archive.ubuntu.com/ubuntu noble InRelease Reading package lists... Done # Search for a package by name $ apt search python3 python3/noble 3.12.3-0ubuntu2 amd64 interactive high-level object-oriented language # Install a package $ apt install python3 -y Setting up python3 (3.12.3-0ubuntu2) ... # List already-installed packages $ apt list --installed

Setting up your Ubuntu environment

You have three options for getting a Linux shell on your personal machine, ranked from easiest to most involved:

Option A — WSL on Windows (recommended for Windows users)

Open PowerShell as Administrator and run wsl --install. Reboot, then run wsl to drop into a Ubuntu shell. WSL2 is not a virtual machine — it's a real Linux kernel running natively on Windows, with full filesystem integration. You can edit files in Windows Explorer and run them from the WSL terminal.

Option B — Docker container (recommended for Mac/Linux)

Spin up a Ubuntu container with a bind-mounted volume so your files persist after the container is destroyed:

bash
# Create a Ubuntu container with a persistent volume $ docker run -it --name ubuntu-linux \ -v ~/ubuntu-data:/data \ ubuntu:latest /bin/bash # Later, if the container already exists, exec back in: $ docker ps -a # find the container id $ docker exec -it <container-id> /bin/bash

Option C — Cloud VM (last resort)

You can spin up an AWS EC2 or Azure VM, but the instructor explicitly discourages this for learning — cloud billing adds anxiety, and you don't actually need a remote machine to learn Linux. Use this option only if you're already comfortable with cloud consoles and want to practice on a "real" server.

Hands-on Lab 1 · Boot up your Ubuntu sandbox
  1. Pick option A or B above and get to a shell prompt that looks like root@<random>:/# or ubuntu@ip-10-0-0-1:~$.
  2. Run cat /etc/os-release and confirm you're on Ubuntu. Note the version number (24.04 LTS at time of writing).
  3. Run apt update followed by apt install python3 -y.
  4. Verify Python works: python3 --version should print Python 3.12.x.
  5. Run apt list --installed | head -20 and skim the output. Don't worry if you don't recognise most of them — you will by the end of this manual.
  6. Take a screenshot of your terminal. This is your "before" picture. By the end of Chapter 10, this terminal will be unrecognisable.
Troubleshooting
? Mini-Quiz · Chapter 1
  1. Who created Linux, and in what year?
  2. What are the four core responsibilities of an operating system?
  3. Why does the instructor recommend Ubuntu for beginners?
  4. What's the difference between apt update and apt upgrade?
  5. Why is WSL2 not considered a virtual machine?
Cheat Sheet · Chapter 1
CommandWhat it does
wsl --installInstall Windows Subsystem for Linux
docker run -it --name X ubuntu /bin/bashStart an interactive Ubuntu container
docker exec -it <id> /bin/bashRe-enter a running container
cat /etc/os-releaseShow OS version info
apt updateRefresh package metadata (always run first)
apt install <pkg> -yInstall a package non-interactively
apt search <name>Search for a package
apt list --installedList all installed packages
Chapter 1 Progress ☐ Done · +50 XP logged
Chapter 02 · Star Charts

The Linux Folder Structure

⏱ 30 min ★ +80 XP Difficulty: Navigation

Decoding your prompt

Open a terminal and you'll see something like root@ubuntu:/# or ubuntu@ip-10-0-0-1:~$. That string is packed with information. The part before the @ is the logged-in user (root or ubuntu). The part after the @ and before the : is the hostname of the machine. The character after the colon is your present working directory: / means the root of the entire filesystem, ~ is a shorthand for the current user's home directory, and ~/projects would mean a folder called projects inside the home directory.

The single forward slash / is the most important character in Linux. It is the root of the entire filesystem — there is no C: drive, no D: drive, no drive letters at all. Everything — every file, every folder, every device, every mounted volume — lives somewhere under /. The tilde ~ is a convenience shorthand. For a regular user named ubuntu, ~ expands to /home/ubuntu. For the root user, ~ expands to /root — root is the one exception whose home is not under /home, and this trips up beginners constantly.

i Why the difference?

When you spin up a Docker container, you log in as root by default — that's why your prompt shows / as the working dir. On an EC2 instance or any properly-secured server, you log in as a less-privileged user (e.g. ubuntu) and your prompt shows ~ = /home/ubuntu. This deliberate privilege separation is a core Linux security principle: do daily work as a normal user, become root only when needed.

The cosmic map of folders

Run ls / on any Linux machine and you'll see roughly twenty top-level folders. Each one has a specific job. Below is the complete map you need to memorise — yes, memorise, because troubleshooting a Linux server without knowing where logs live is like flying blind.

FolderPurpose · What lives here
/binUser binaries — everyday commands like ls, cat, date, sed. Accessible to all users. Symlink to /usr/bin.
/sbinSystem binaries — admin commands like useradd, groupadd, fdisk. Reserved for admins. Symlink to /usr/sbin.
/libLibraries used by binaries in /bin and /sbin. Not user-facing. Symlink to /usr/lib.
/usrParent containing bin, lib, sbin, plus share (shared data), src (source code), local (locally-compiled software).
/etcSystem configuration files — the most important config folder. Like Windows' Registry, but plain-text files. Includes passwd, shadow, hosts, group, ssh/sshd_config.
/varVariable data — files that grow and change at runtime. /var/log (system logs), /var/lib (third-party libs), /var/cache.
/homeHome directory for each non-root user: /home/ubuntu, /home/abishek, etc.
/rootHome directory of the root user. The one exception — not under /home, for security.
/tmpTemporary files — auto-cleared periodically (like "Recently Deleted" on mobile). Don't put anything here you want to keep.
/optOptional third-party software — custom Java installs, vendor tools. Org-wide, unlike /home which is per-user.
/srvServer data — config files for web servers, FTP servers, etc.
/mntMount point for temporary volumes — new disks, NFS shares, EBS volumes. See Chapter 8.
/mediaAuto-mounted removable media (USB drives, CDs). Rarely used by DevOps.
/bootFiles needed to boot Linux (kernel image, bootloader config). Empty in Docker containers because containers don't really "boot".
/procVirtual filesystem for process & system info. Doesn't exist on disk — generated on the fly by the kernel.
/devDevice files — every disk, terminal, and peripheral is exposed as a file here (e.g. /dev/sda).
/sysSystem files — virtual, like /proc. Exposes kernel parameters and hardware info.
/runRuntime data of running processes — PID files, sockets, lock files.
/dataNot standard — but commonly created by orgs to share data across admins/users.

The five /etc files you must know

The /etc folder is the brain of a Linux system. You will return to it constantly when troubleshooting. These five files are the ones you'll touch most often:

FileWhat it contains
/etc/passwdList of all users — username, UID, home dir, default shell. Readable by anyone.
/etc/shadowEncrypted passwords (SHA-256 hashed, one-way). Readable only by root.
/etc/groupList of all groups and their members.
/etc/hostsLocal DNS cache — manually map hostnames to IPs (overrides DNS).
/etc/os-releaseOS version info (distro name, version number, codename).
/etc/ssh/sshd_configSSH server config — password auth on/off, port, key settings.

Navigation commands

bash
# Where am I right now? $ pwd /home/ubuntu # List files in current directory (short form) $ ls demo_file.txt projects .bashrc # Long listing, reverse time-sorted (newest at bottom) $ ls -ltr -rw-r--r-- 1 ubuntu ubuntu 0 Jul 12 10:15 demo_file.txt drwxr-xr-x 2 ubuntu ubuntu 4096 Jul 12 10:20 projects # ^ first char: 'd' = directory, '-' = file # Navigate to root of filesystem $ cd / # Go back one level $ cd .. # Make a new folder $ mkdir my_folder # View OS version $ cat /etc/os-release # Where does the 'ls' binary actually live? $ which ls /usr/bin/ls # What folders does Linux search for commands? $ echo $PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
i The PATH variable

When you type ls, Linux doesn't search the entire disk. It looks only in the folders listed in your $PATH environment variable, in order. If /usr/bin wasn't in PATH, ls would say "command not found" — but the binary would still exist at /usr/bin/ls, you'd just have to type the full path. This is why "command not found" almost always means either (a) the package isn't installed, or (b) it's installed somewhere not in your PATH.

Hands-on Lab 2 · Tour the filesystem
  1. Run cd / to go to the root, then ls -ltr to see all top-level folders.
  2. Visit each of these folders and list their contents: /etc, /var/log, /home, /usr/bin (just ls | head -20 — there are thousands), /tmp.
  3. Run cat /etc/passwd — find your own username in the list. Each line is one user.
  4. Run cat /etc/os-release and identify your distro's codename (e.g. "noble" for Ubuntu 24.04).
  5. Run which ls, which cat, which useradd. Notice useradd lives in /usr/sbin — that's the admin folder.
  6. Run echo $PATH and confirm both /usr/bin and /usr/sbin are listed.
  7. Create a folder mkdir ~/cosmic-notebook and cd into it. This will be your workspace for the rest of the manual.
Troubleshooting
? Mini-Quiz · Chapter 2
  1. Where is the home directory of the root user? (Trick question.)
  2. What's the difference between /bin and /sbin?
  3. Name three files in /etc and what each one does.
  4. What does ls -ltr do, and what does the first character of each line tell you?
  5. Why is /boot empty inside a Docker container?
  6. What happens when you type a command and Linux says "command not found" — what's actually happening under the hood?
Cheat Sheet · Chapter 2
CommandWhat it does
pwdPrint working directory (where am I?)
lsList files in current dir
ls -ltrLong listing, reverse-time sort (newest at bottom)
cd /pathChange directory
cd ..Go up one directory
cd ~ or just cdGo to your home directory
mkdir <name>Make a new folder
which <cmd>Show full path of a command's binary
echo $PATHShow folders Linux searches for commands
cat /etc/os-releaseShow OS version
cat /etc/passwdList all users
Chapter 2 Progress ☐ Done · +80 XP logged
Chapter 03 · Crew Roster

User Management & SSH Access

⏱ 25 min ★ +100 XP Difficulty: Bridge Crew

Why user management matters

A Linux server in production is rarely used by one person. Developers deploy code, QA engineers run tests, DevOps admins configure infrastructure, managers occasionally log in to check status. If everyone shared the root password, you'd have zero accountability — when something breaks, you couldn't tell who did it. Worse, every person would have god-mode privileges, so a single typo (rm -rf /) by anyone would destroy the entire system.

The Linux answer to this is per-user identities with permission-based access. Each human (or service) gets their own user account with a unique username. When they perform any action that requires root privileges — installing software, modifying system configs, killing other users' processes — they must explicitly elevate using sudo, which logs who did what. Admins use a naming convention (typically firstname-surname or firstname.lastname) to guarantee uniqueness across thousands of users in a large organisation.

Once you have hundreds or thousands of users, managing permissions one user at a time becomes impossible. That's where groups come in. A group is a named collection of users. Instead of granting access to "alice, bob, charlie, dave, ...", you grant access to the devops group, and add or remove people from that group as they join or leave the team. One change affects everyone in the group instantly.

Creating users — two commands, one big difference

Linux gives you two commands to create a user, and the difference is a classic interview question. useradd is the low-level command — it's fast, non-interactive, and creates the user with minimal configuration. It does NOT create a home directory by default, does NOT prompt for a password, and does NOT ask for any metadata. Use it in shell scripts where you need speed and control. adduser is a higher-level wrapper — it's interactive, prompts you for the user's full name, room number, phone numbers, and password, and creates the home directory automatically. Use it for one-off human users.

bash
# Quick, non-interactive — no home dir, no password prompt $ useradd abi # Interactive — prompts for full name, phone, password, creates home $ adduser abhishek Adding user `abhishek' ... Adding new group `abhishek' (1001) ... Adding new user `abhishek' (1001) with group `abhishek' ... Creating home directory `/home/abhishek' ... New password: ******** Full Name, Room Number, Work Phone, Home Phone, Other []: # Set or change a password for any user (as admin) $ passwd abi New password: ******** passwd: password updated successfully # Delete a user $ userdel abi # Verify user exists $ cat /etc/passwd | grep abhishek abhishek:x:1001:1001:,,,:/home/abhishek:/bin/bash

The /etc/passwd & /etc/shadow duo

Every user account is recorded in /etc/passwd as a single colon-separated line: username:x:UID:GID:comment:home:shell. The x in the second field is a historical placeholder — it used to contain the hashed password, but that was moved to /etc/shadow for security. The reason: /etc/passwd must be world-readable (lots of programs need to look up usernames), so storing hashes there meant anyone could try to crack them. /etc/shadow is only readable by root, and stores the actual hashes.

Those hashes are one-way — encrypted with SHA-256 (or similar), they cannot be decrypted. When you log in, Linux hashes your typed password and compares it to the stored hash. If they match, you're in. If you forget your password, the only fix is for an admin to set a new one — there is no recovery, no "forgot password" link, no decrypt tool. Any tool that claims to recover a Linux password is lying or unsafe for production use.

! Interview Question

Q: Can you decrypt or restore a Linux user's password?
A: No. Passwords in /etc/shadow are hashed with a one-way function (SHA-256 or stronger). The original password cannot be recovered. An admin can only set a new password using passwd <username>.

Switching users & the sudo privilege

bash
# Who am I currently logged in as? $ whoami ubuntu # Switch to another user (will prompt for their password) $ su - abhishek Password: ******** abhishek@ubuntu:~$ # Switch to root (from a normal user) — the dash loads root's environment $ sudo su - # Or just run ONE command as root without switching $ sudo apt install vim

The sudo command (short for "substitute user do") lets a normal user run a single command with root privileges, without switching their entire session. It logs who ran what, which is critical for auditing. sudo su - is the shortcut to drop into a full root shell — useful when you have many admin commands to run in sequence.

Groups — managing permissions at scale

bash
# Create a new group $ groupadd devops # Add an existing user to an existing group # -a = append (don't replace), -G = secondary group $ usermod -aG devops a_virala # Verify $ cat /etc/group | grep devops devops:x:1010:a_virala # Lock a user account (no login possible) $ usermod -L suspicious_user # Unlock it again $ usermod -U suspicious_user # Force password change every 90 days $ chage -M 90 abhishek

SSH — how humans actually log in

In the real world, you almost never sit at a physical Linux server. You SSH into it from your laptop. The server runs an sshd daemon (a background service) listening on port 22. You install an SSH client on your laptop (Terminal on Mac, Git Bash on Windows, ssh from any Linux shell) and run ssh user@server-ip. The client and server negotiate an encrypted tunnel, then authenticate you with either a password or an SSH key.

bash
# From your laptop, log in to a remote server $ ssh a_virala@3.110.45.67 a_virala@3.110.45.67's password: ******** a_virala@ip-10-0-0-1:~$ # On the server — view SSH config $ cat /etc/ssh/sshd_config | grep PasswordAuth PasswordAuthentication no # cloud default — keys only # Enable password auth (edit file, then restart service) $ sudo vim /etc/ssh/sshd_config # change PasswordAuthentication no → yes $ sudo systemctl restart ssh # Get system info on remote machine $ uname -a Linux ip-10-0-0-1 6.8.0-1018-aws #20-Ubuntu SMP ... x86_64 GNU/Linux

Cloud providers (AWS, Azure, GCP) disable password authentication by default — you must use an SSH key file (a .pem file on AWS). This is much more secure than passwords because keys can't be brute-forced. To log in with a key, you'd use ssh -i my-key.pem user@ip. The original course covers key-based auth in more depth in later episodes; for now, understand that the sshd_config file controls everything about how SSH behaves, and changes require a systemctl restart ssh to take effect.

Hands-on Lab 3 · Build your crew
  1. As root, create two users: adduser developer and adduser qe. Set passwords for each.
  2. Verify they exist: cat /etc/passwd | grep -E "developer|qe". Note their UIDs and home directories.
  3. View the encrypted passwords: cat /etc/shadow | grep developer. Notice you can't read the actual password.
  4. Switch to the developer user: su - developer. Run whoami to confirm.
  5. As developer, try to delete /sbin: rm -rf /sbin. You should get "Permission denied" — that's user management working.
  6. Switch back to root: exit. Create a group: groupadd devops.
  7. Add both users to it: usermod -aG devops developer and usermod -aG devops qe.
  8. Verify: cat /etc/group | grep devops should show both users listed.
Troubleshooting
? Mini-Quiz · Chapter 3
  1. What's the difference between useradd and adduser?
  2. Can you recover a lost Linux user password? Why or why not?
  3. What does the x in the second field of /etc/passwd mean?
  4. What does usermod -aG devops alice do, and why is the -a flag critical?
  5. Why do cloud providers disable password authentication for SSH by default?
  6. After editing /etc/ssh/sshd_config, what command must you run for changes to take effect?
Cheat Sheet · Chapter 3
CommandWhat it does
useradd <name>Create user (quick, non-interactive, no home)
adduser <name>Create user (interactive, prompts for info, creates home)
passwd <name>Set or change a user's password
userdel <name>Delete a user
usermod -aG <group> <user>Add user to a group (-a = append, don't replace)
usermod -L <user>Lock a user account
usermod -U <user>Unlock a user account
groupadd <name>Create a new group
chage -M 90 <user>Force password change every 90 days
su - <user>Switch to another user (dash loads their env)
sudo su -Switch to root shell
whoamiShow current user
ssh user@ipLog in to a remote server
systemctl restart sshRestart SSH service (after config changes)
Chapter 3 Progress ☐ Done · +100 XP logged
Chapter 04 · Cargo Handling

File Management & Redirection

⏱ 20 min ★ +80 XP Difficulty: Cargo Bay

The daily-driver file commands

Most of your time on a Linux server is spent creating, reading, moving, and deleting files. These commands form the absolute bedrock of everyday work — if you only memorise ten commands from this entire manual, eight of them should be from this chapter. The good news is they're all short and intuitive. The bad news is that one tiny character (> vs >>) can mean the difference between appending a log line and wiping out a 1000-line file. Read carefully.

bash
# Create an empty file (or update timestamp if it exists) $ touch mission_log.txt # Create a new folder $ mkdir cargo_bay # Remove an empty folder $ rmdir cargo_bay # Remove a file $ rm mission_log.txt # Force-remove a folder AND everything inside it (DANGER) $ rm -rf cargo_bay # Copy a file $ cp source.txt destination.txt # Move OR rename a file (same command for both) $ mv old_name.txt new_name.txt $ mv file.txt /home/ubuntu/archive/

Note that mv does double duty — it moves files between directories AND renames them. There's no separate rename command in daily use (there is one, but mv is what everyone actually uses). The rm -rf combination is infamous: -r means recursive (descend into subfolders), -f means force (don't ask for confirmation). Together they will happily delete anything you own — including your entire home directory if you point them at ~. Triple-check the path before pressing Enter.

Reading files — five tools, five use cases

Linux gives you many ways to read a file, and each is suited to a different situation. cat dumps the entire file to the terminal at once — great for short files, terrible for 10,000-line logs. less opens an interactive pager where you can scroll up and down with arrow keys, search with /, and quit with q — this is what you want for medium-to-large files. head shows you the first N lines; tail shows the last N. And tac (cat spelled backwards) prints the file in reverse line order — fun, rarely useful.

bash
# Print entire file to terminal $ cat small_file.txt Hello world This is line 2 # Open interactive pager (q to quit, / to search, arrow keys to scroll) $ less big_log.txt # First 10 lines $ head -10 big_log.txt # Last 20 lines (super useful for "what just happened in this log?") $ tail -20 big_log.txt # Live-follow a log as new lines are written (tail -f) $ tail -f /var/log/syslog # Reverse line order (last line first) $ tac small_file.txt This is line 2 Hello world

Writing to files — > vs >> (CRITICAL)

This is the single most important distinction in this chapter, and the one that causes the most beginner tears. The single greater-than > overwrites the entire file — whatever was there before is gone. The double greater-than >> appends — it adds your new content to the end of the file, preserving what was already there. Mix these up and you will destroy data.

bash
# Start with a 1000-line file $ wc -l demo_file.txt 1000 demo_file.txt # Use single > to "write hello" — this OVERWRITES the file $ echo "hello" > demo_file.txt $ wc -l demo_file.txt 1 demo_file.txt # 💥 999 lines gone! # Use double >> to APPEND instead — preserves existing content $ echo "world" >> demo_file.txt $ cat demo_file.txt hello world
The #1 beginner mistake

You typed echo "log entry" > important.log instead of >>. Your 50,000-line log file is now 1 line. There is no undo. There is no recycle bin. The data is gone. The fix is prevention: always use >> unless you specifically intend to overwrite. Some engineers even alias > to prompt for confirmation, though that's overkill for most people.

Hands-on Lab 4 · File handling in anger
  1. In your ~/cosmic-notebook folder, create a 1000-line file: open vim, type :call append(0, repeat(["line of text"], 1000)), save as big.txt. (Or just paste any large text in.)
  2. Verify the line count: wc -l big.txt.
  3. Read the first 5 lines: head -5 big.txt.
  4. Read the last 5 lines: tail -5 big.txt.
  5. Open it in less, scroll around with arrow keys, search for "line" by typing /line, then quit with q.
  6. Demonstrate the > vs >> disaster: echo "oops" > big.txt, then wc -l big.txt — confirm it's now 1 line.
  7. Restore the 1000-line file (re-do step 1). This time use >> to append: echo "appended line" >> big.txt, verify line count is now 1001.
  8. Make a folder backup, copy big.txt into it, then rename the copy to big.bak.txt.
Troubleshooting
? Mini-Quiz · Chapter 4
  1. What's the difference between > and >>?
  2. Which command would you use to read the last 50 lines of a log file?
  3. How do you follow a log file in real-time as new lines are written?
  4. What does mv do that cp doesn't?
  5. What does the first character of ls -ltr output tell you, and why is it more reliable than color?
  6. Why is rm -rf considered dangerous? Name one safe-guard you can use.
Cheat Sheet · Chapter 4
CommandWhat it does
touch <file>Create empty file (or update timestamp)
mkdir <dir>Make a folder
rmdir <dir>Remove empty folder
rm <file>Remove a file
rm -rf <dir>Force-remove folder + contents (DANGEROUS)
cp <src> <dest>Copy a file
mv <src> <dest>Move OR rename a file
cat <file>Print entire file (use for short files only)
less <file>Interactive pager — scroll, search, q to quit
more <file>Simpler pager (older, less powerful)
head -N <file>Show first N lines
tail -N <file>Show last N lines
tail -f <file>Live-follow a log file
tac <file>Print file in reverse line order
echo "x" > fileWrite "x" — OVERWRITES entire file
echo "x" >> fileAppend "x" — preserves existing content
echo "x" > fileWrite (overwrites) — use >> to append
Chapter 4 Progress ☐ Done · +80 XP logged
Chapter 05 · Captain's Log

The vi / vim Editor

⏱ 15 min ★ +70 XP Difficulty: Helm

Why vi, in 2025?

Every Linux distribution ships with vi or vim installed by default. Every single one. When you SSH into a random server at 3am to fix an outage, vi is the editor that will be there. Nano might be there. Emacs almost certainly won't be. VSCode definitely won't be. If you can't operate vi, you can't operate a production Linux server under pressure. That's the pragmatic reason.

The deeper reason is that vi's modal editing model is extraordinarily efficient once you internalise it. Your hands never leave the home row. There's no reaching for arrow keys, no Ctrl-Alt-Shift combinations. Every editing operation is a few keystrokes. Many senior engineers use vim (the modern fork of vi) as their daily editor even on their powerful local machines, precisely because the modal workflow scales to complex editing tasks better than most "modern" editors.

vi vs vim: vim ("vi improved") is a wrapper over vi with more features (syntax highlighting, plugins, better search). All vi commands work in vim. For learning, install vim: apt install vim (or sudo apt install vim if not root). For the rest of this chapter, "vi" and "vim" are interchangeable.

The three modes — the source of all beginner confusion

vi has three modes, and 95% of "I'm stuck in vi!" panic comes from not knowing which mode you're in. Memorise this table before you touch a file.

ModeHow to enterWhat you can doHow to exit
Normal modeDefault on opening a fileNavigate, delete, copy, paste. Cannot type text.Press i for Insert, or : for Command
Insert modePress iType text freely. Cannot save or quit from here.Press Esc to return to Normal
Command modePress Esc, then :Run save/quit/search commands at the bottom of the screenPress Enter to execute, or Esc to cancel

The workflow for any edit is: open file → press i → type your changes → press Esc → type :wq! to save & quit. If you only remember one sequence, remember that one. The ! at the end forces the operation — useful when vim complains about read-only files or unsaved changes.

The must-know shortcuts

vim
# Open a file (creates it if it doesn't exist) $ vim mission_log.txt --- In Normal mode (after pressing Esc) --- i # Enter Insert mode (you can now type text) Esc # Return to Normal mode (do this whenever in doubt) # Navigation (Normal mode) :0 # Jump to first line :500 # Jump to line 500 Shift+G # Jump to last line arrow keys # Move up/down/left/right (or use h j k l) # Editing (Normal mode) Shift+O # Create a new blank line ABOVE cursor, enter Insert U # Undo the last line change --- In Command mode (after Esc, then :) --- :wq! # Save and quit (force) — the daily driver :q! # Quit WITHOUT saving (when you messed up) :w # Save but don't quit :w <newname> # Save as a new filename (save-as)
i The "I'm stuck" escape hatch

If you're ever confused in vim, do this exact sequence: Esc Esc : q ! Enter. This abandons all changes and exits. You're back at the shell prompt. No matter what state vim is in, this sequence will get you out. Memorise it. Tattoo it on your hand if you have to. Once you're out, you can re-open the file and try again with a clear head.

The save & quit rituals

Beginners always struggle with quitting vim. There's a reason it's a meme. The trick is that you can't quit from Insert mode — you must first return to Normal mode by pressing Esc. Then enter Command mode by pressing :. Then type your command and press Enter. The three commands you'll use 99% of the time:

CommandWhat it doesWhen to use
:wq!Write (save), quit, forceYou made edits and want to save them
:q!Quit, force — discard all changesYou made edits you DON'T want to save
:wWrite (save) but don't quitSave progress mid-edit, keep working
Hands-on Lab 5 · Vim bootcamp
  1. In your ~/cosmic-notebook folder, open a new file: vim first_log.txt.
  2. Press i to enter Insert mode. You should see -- INSERT -- at the bottom of the screen.
  3. Type five lines of anything — your name, today's date, a haiku, doesn't matter.
  4. Press Esc to return to Normal mode. The -- INSERT -- indicator should disappear.
  5. Type :wq! and press Enter. You should be back at the shell.
  6. Verify: cat first_log.txt — your five lines should be there.
  7. Re-open the file: vim first_log.txt.
  8. Press Esc, then :0 Enter to jump to line 1.
  9. Press Shift+G to jump to the last line. Press :0 again to return to the top.
  10. Press Shift+O to insert a new blank line at the top. Type "HEADER LINE".
  11. Press Esc, then U — the new line should disappear (undo).
  12. Finally, type :q! Enter to quit WITHOUT saving. Verify with cat first_log.txt — the header line should NOT be there because you discarded it.
Troubleshooting
? Mini-Quiz · Chapter 5
  1. Name vim's three modes and what each one is for.
  2. What's the difference between :wq! and :q!?
  3. You opened a file, typed some garbage, and want to exit WITHOUT saving. What exact sequence do you type?
  4. How do you jump directly to line 250 of a file in vim?
  5. Why can't you save a file directly from Insert mode?
  6. What's the universal "I'm stuck" escape sequence in vim?
Cheat Sheet · Chapter 5
KeyWhat it does
vim <file>Open file in vim (creates if missing)
iEnter Insert mode
EscReturn to Normal mode (do this whenever in doubt)
:wq!Save and quit (force)
:q!Quit WITHOUT saving (discard all changes)
:wSave but keep editing
:0Jump to first line
:N (e.g. :500)Jump to line N
Shift+GJump to last line
Shift+OInsert new line above cursor, enter Insert mode
UUndo last line change
arrow keys or h j k lNavigate (left/down/up/right)
Chapter 5 Progress ☐ Done · +70 XP logged
Chapter 06 · Security Force Field ★

File Permissions

⏱ 40 min ★ +150 XP Difficulty: Shield Operator
! Most important chapter

The instructor calls file permissions "a very, very important concept of Linux" and dedicates an entire episode to it. If you only deeply understand one chapter in this manual, make it this one. Every interview for a DevOps, SRE, or backend role will ask you about permissions. Every production outage you debug will eventually involve a permissions issue. Spend the full 40 minutes here.

Why permissions exist

Without file permissions, any user on a Linux system could read, modify, or delete any file — including other users' private keys, the system password hashes in /etc/shadow, the binaries in /sbin, or the entire /etc directory. One careless or malicious user could destroy the entire machine. Permissions are Linux's answer to "who is allowed to do what to which file."

Every file and folder on a Linux system has a permission string attached to it, set by default when the file is created. As an admin, you can read and modify these permissions using chmod (change mode) and chown (change owner). Understanding the model is the difference between a sysadmin who can debug anything and one who's constantly confused by "Permission denied" errors.

Anatomy of a permission string

Run ls -ltr on any file and the first column you see is a 10-character string like drwxr-xr-x or -rw-r--r--. This is the permission string, and it encodes everything about who can do what with that file. Let's decode it character by character.

bash
$ ls -ltr hello_world.sh -rwxr-xr-- 1 developer developer 42 Jul 12 10:30 hello_world.sh #│└─┘└─┘└─┘ #│ │ │ └── 'others' permissions: r-- (read only) #│ │ └───── 'group' permissions: r-x (read + execute) #│ └──────── 'user/owner' perms: rwx (read + write + execute) #└────────── type: '-' = file, 'd' = directory, 'l' = symlink

The first character is the type: - for a regular file, d for a directory, l for a symbolic link. The remaining nine characters split into three groups of three: user (the file's owner), group (members of the file's group), and others (everyone else on the system). Each group has three slots: r for read, w for write, x for execute. If a slot has a dash - instead of a letter, that permission is denied.

What r, w, and x actually mean

LetterOn a fileOn a directoryNumeric value
rRead the contents (cat, less)List the directory's contents (ls)4
wModify the contents (vim, echo >)Create or delete files inside it2
xExecute the file (run as a program)Enter the directory (cd) and access files by name1

Notice that r, w, and x mean different things for files vs directories. This is a common source of confusion. In particular: to enter a directory with cd, you need execute permission on the directory — not read. To list its contents with ls, you need read. To add or remove files inside it, you need write.

The numeric (octal) system — 4, 2, 1

Instead of writing rwx, you can express the same information as a single digit from 0-7 by adding up the values: r=4, w=2, x=1. So rwx = 4+2+1 = 7. r-x = 4+0+1 = 5. r-- = 4+0+0 = 4. --- (no permissions) = 0. Three groups of these digits form the famous three-digit "chmod number" you see in tutorials.

NumberLettersMeaningCommon use
7rwxRead + Write + ExecuteFull control
6rw-Read + WriteEditable text file
5r-xRead + ExecuteExecutable you don't want edited
4r--Read onlyConfig file for normal users
0---No accessLocked out

Common permission patterns you'll see in the wild

PatternStringWhat it means
777rwxrwxrwxEveryone has full access. Almost always a security mistake.
755rwxr-xr-xOwner full, others can read + execute. Default for executables & directories.
750rwxr-x---Owner full, group read + execute, others locked out. Default for home directories.
700rwx------Only owner has access. Used for SSH keys (~/.ssh).
644rw-r--r--Owner can edit, others can read. Default for regular files.
600rw-------Only owner can read/write. Used for ~/.bashrc, private keys.
444r--r--r--Read-only for everyone. System config files.
400r--------Only owner can read. Used for SSH private keys.

chmod — changing permissions

You can use chmod in two modes: numeric (faster, less readable) and symbolic (slower, more readable). Most engineers use numeric in scripts and symbolic for one-off changes.

bash
# Numeric mode — set owner=rwx, group=r-x, others=--- $ chmod 750 mission_log.txt $ ls -ltr mission_log.txt -rwxr-x--- 1 ubuntu ubuntu 42 Jul 12 10:30 mission_log.txt # Make a script executable by everyone $ chmod 755 deploy.sh # Lock down an SSH private key (CRITICAL — SSH will refuse to use keys with looser perms) $ chmod 400 my-key.pem # Symbolic mode — set just the user perms to rwx, leave others untouched $ chmod u=rwx mission_log.txt # Symbolic — remove all permissions for "others" $ chmod o= mission_log.txt # Symbolic — grant execute to "others" only $ chmod o+x deploy.sh # Change owner AND group in one shot $ chown qe:qe test.sh

The bank-and-locker analogy (CRITICAL)

This is the single most important concept in this chapter, and a frequent interview question. Here's the analogy: a file inside a folder is like a locker inside a bank vault. To access the locker (file), you must first be able to enter the bank (folder). Folder permissions take priority over file permissions. If you don't have execute permission on the parent folder, you cannot access any file inside it — no matter how permissive the file's own permissions are.

bash
# As root, lock down /tmp so only root can enter $ chmod 700 /tmp # Now create a file inside /tmp with WIDE OPEN permissions $ touch /tmp/demo $ chmod 777 /tmp/demo # Switch to a normal user $ su - qe # Try to read the file — should work, right? It's 777! qe$ cat /tmp/demo cat: /tmp/demo: Permission denied # 💥 Even though the FILE is 777, the FOLDER is 700. # qe cannot enter /tmp, so cannot reach /tmp/demo. Folder wins.

The takeaway: whenever you see "Permission denied" on a file, check the file's permissions AND every parent folder's permissions. Beginners often spend hours debugging a file's chmod when the real problem is that they don't have execute permission on the great-grandparent directory.

! Interview Question

Q: A file has permission 777, but a user still can't access it. Why?
A: Check the parent directory's permissions. Folder permissions take priority over file permissions (the bank-and-locker rule). The user must have x on every directory in the path to reach the file.

chown — changing ownership

Every file has an owner (a user) and a group. Use chown to change either. The syntax is chown user:group file. Only root can chown a file to another user — regular users get "Operation not permitted". This prevents you from "giving" a malicious file to another user.

bash
# As developer, create a file developer$ touch test.sh # Try to give it to qe — fails (only root can chown) developer$ chown qe:qe test.sh chown: changing ownership of 'test.sh': Operation not permitted # As root, transfer ownership root# chown qe:qe test.sh root# ls -ltr test.sh -rw-r--r-- 1 qe qe 0 Jul 12 10:35 test.sh

The "new script won't execute" trap

When you create a brand-new shell script with touch my_script.sh or vim my_script.sh, Linux gives it default permissions of 644 (rw-r--r--). Notice there's no x — execute permission is NOT granted by default. If you try to run ./my_script.sh, you'll get "Permission denied". You must explicitly add execute permission first.

bash
# Create a new script $ touch deploy.sh $ vim deploy.sh # type #!/bin/bash + echo "deploying..." # Try to run it — fails $ ./deploy.sh bash: ./deploy.sh: Permission denied # Grant execute permission $ chmod +x deploy.sh # or: chmod 755 deploy.sh # Now it works $ ./deploy.sh deploying...
Hands-on Lab 6 · Build & breach a force field
  1. Switch to your developer user from Chapter 3: su - developer.
  2. Create a script: vim /tmp/hello_world.sh with this content:
    #!/bin/bash
    echo "hello world"
  3. Make it executable: chmod +x /tmp/hello_world.sh. Verify with ls -ltr /tmp/hello_world.sh — you should see -rwxr-xr-x.
  4. Run it: /tmp/hello_world.sh. Should print "hello world".
  5. Switch to qe: su - qe.
  6. As qe, try to read the file: cat /tmp/hello_world.sh — should work because others have r.
  7. As qe, try to write to it: echo "hacked" >> /tmp/hello_world.sh — should fail because others don't have w.
  8. Switch back to developer. Revoke read access for others: chmod o= /tmp/hello_world.sh. Now qe can't even cat it.
  9. The bank-and-locker test: as root, chmod 700 /tmp. As qe, try cat /tmp/hello_world.sh — fails even though the file is still 750, because qe can't enter /tmp.
  10. Restore /tmp to 1777 (the default for /tmp on most distros).
  11. As developer, create a file. As developer, try to chown qe:qe it — fails. Switch to root, try the same chown — succeeds.
Troubleshooting
  • "Permission denied" running a script — file isn't executable. chmod +x script.sh.
  • "Permission denied" reading a file — check (a) the file's r for your category, (b) every parent folder's x for your category (the bank-and-locker rule).
  • "Operation not permitted" on chown — only root can chown. sudo chown ....
  • SSH key rejected with " Permissions are too open" — your private key must be 400 or 600. chmod 400 my-key.pem.
  • Can write to a file but can't save it in vim — likely the parent directory lacks w for you. You can edit existing files but not create new ones.
  • Set 777 "just to make it work" — security sin. Always use the minimum permissions needed. 755 for executables, 644 for files, 600 for secrets.
? Mini-Quiz · Chapter 6
  1. Decode -rwxr-xr-- — what type is it, and what can each of user/group/others do?
  2. Convert rwxrw-r-- to numeric form.
  3. A file has permissions 777 but a user gets "Permission denied". What's the most likely cause?
  4. What's the difference between chmod 755 file and chmod 750 file?
  5. Why does a freshly-created shell script refuse to execute, even though you wrote it correctly?
  6. What's the recommended permission for an SSH private key, and why?
  7. Why does Linux prevent regular users from running chown to give files to other users?
  8. What does x permission mean on a directory, and why is it different from r?
Cheat Sheet · Chapter 6 — The Big One
CommandWhat it does
ls -ltr <file>View permission string (first column)
chmod 755 <file>Owner rwx, group & others r-x (executables)
chmod 644 <file>Owner rw, group & others r-- (regular files)
chmod 600 <file>Owner rw, others nothing (private files)
chmod 700 <dir>Only owner can access (SSH ~/.ssh)
chmod 400 <key.pem>Owner read only (SSH private keys)
chmod 777 <file>EVERYONE full access (avoid in production)
chmod +x <file>Add execute to all (shortcut for scripts)
chmod u=rwx,g=r,o= <f>Symbolic — set explicit perms per category
chown <user>:<group> <file>Change owner & group (root only)
chown <user> <file>Change owner only (group unchanged)
./<script>Execute a script in current dir (requires +x)

Numeric decoder: r=4, w=2, x=1. Sum per category (user/group/others).
Rule of thumb: 755 for executables & dirs, 644 for files, 600 for secrets, 400 for SSH keys, 700 for ~/.ssh.

Chapter 6 Progress — Force Field Calibrated ☐ Done · +150 XP logged
Chapter 07 · Engineering Bay

Process Management

⏱ 30 min ★ +130 XP Difficulty: Chief Engineer

What is a process?

A process is a running instance of a program. When you run python3 app.py, the Python interpreter loads your code into memory and starts executing it — that running instance is a process. The same program file can have many processes running simultaneously (think of three terminals each running python3 app.py against different inputs). Each process gets a unique PID (process ID) — an integer the kernel uses to track it.

Why does this matter to you as an engineer? Because real servers run hundreds of processes simultaneously, and sometimes they misbehave. A Python app might leak memory and start eating 8GB. A Java service might spawn 200 threads and starve other workloads. A stuck cron job might lock a file. Your job is to find the misbehaving process, understand what it's doing, and either kill it, pause it, or reprioritise it — without taking down the whole server.

Process vs Service — know the difference

A process is any running program — your Python script, your terminal, your vim session. It does NOT auto-restart on reboot. A service is a special category of process that's been registered with the system's init daemon (systemd on modern Linux) to auto-start at boot and be manageable via systemctl. Web servers like Apache and Nginx, when installed properly, register themselves as services. The SSH daemon sshd is a service. Your one-off python3 app.py is just a process.

Listing processes — ps aux vs ps -ef

The ps command lists processes. With no arguments it shows only your own terminal's processes — not very useful. The two forms you'll actually use are ps aux (BSD syntax) and ps -ef (System V syntax). They show similar info, but with one critical difference: ps aux shows CPU and memory utilisation per process, while ps -ef does not. For day-to-day debugging, use ps aux.

bash
# List ALL processes with CPU + memory $ ps aux USER PID %CPU %MEM VSZ RSS TTY STAT START COMMAND root 1 0.0 0.5 168400 11200 ? Ss Jul11 /sbin/init root 542 0.0 0.8 98720 18240 ? Ss Jul11 /usr/sbin/sshd ubuntu 843 1.2 2.1 312544 41200 ? S 10:15 python3 app.py # List all processes WITHOUT cpu/mem (alternative format) $ ps -ef UID PID PPID C STIME TTY TIME CMD root 1 0 0 Jul11 ? 0:01 /sbin/init root 542 1 0 Jul11 ? 0:00 /usr/sbin/sshd # Count total processes $ ps aux | wc -l 109 # Filter for a specific process name $ ps aux | grep java ubuntu 1243 0.5 3.2 4021884 262144 ? Sl 10:20 java -jar app.jar # The grep itself shows up — filter it out with -v $ ps aux | grep java | grep -v grep ubuntu 1243 0.5 3.2 4021884 262144 ? Sl 10:20 java -jar app.jar

Decoding the ps aux columns

ColumnMeaningWhy you care
USERUser that owns the processSpot processes running as the wrong user
PIDUnique process IDUse this to kill or signal the process
%CPUCPU utilisation (0-100+%)Find runaway processes
%MEMRAM utilisation (% of total)Find memory leaks
VSZVirtual memory size (KB)Total memory allocated
RSSResident set size (KB)Actual physical RAM used
TTYTerminal attachedSkip — usually ? for daemons
STATProcess state (R=running, S=sleeping, Z=zombie)Spot stuck or zombie processes
STARTWhen the process startedIdentify long-running vs just-started
COMMANDThe command that launched itIdentify what the process actually is

Killing processes — graceful vs forceful

Use kill <PID> to send a signal to a process. The default signal is SIGTERM (signal 15) — a polite "please shut down" that lets the process clean up (close files, flush logs, save state) before exiting. Some processes ignore SIGTERM, either intentionally (sticky daemons) or because they're hung. For those, you need kill -9 <PID>, which sends SIGKILL — the kernel immediately destroys the process, no cleanup, no warning. Use -9 as a last resort; it can leave half-written files and corrupted state.

bash
# Find a misbehaving process $ ps aux | grep python3 | grep -v grep ubuntu 4040 85.2 12.3 312544 412000 ? R 10:30 python3 runaway.py # Graceful kill — let it clean up $ kill 4040 # Check if it's still alive $ ps aux | grep 4040 | grep -v grep ubuntu 4040 85.2 12.3 312544 412000 ? R 10:30 python3 runaway.py # still there — escalate # Forceful kill — no cleanup, instant death $ kill -9 4040 $ ps aux | grep 4040 | grep -v grep # gone # For Java apps — get a thread dump before killing $ kill -3 4040 # dumps all threads to stdout/log # Pause a process temporarily (SIGSTOP) $ kill -stop 4048 # Resume it later (SIGCONT) $ kill -cont 4048

The full signal menu

SignalNumberWhat it does
SIGTERM15Polite "please shut down" — process can clean up (default for kill)
SIGKILL9Forceful instant kill — no cleanup, no escape. kill -9
SIGINT2Interrupt — what Ctrl+C sends in your terminal
SIGSTOP19Pause a process — kill -stop
SIGCONT18Resume a paused process — kill -cont
SIGQUIT3Quit with core dump — for Java, dumps all thread stacks
SIGHUP1Hang up — many daemons reload config on SIGHUP

renice — reprioritising processes

The Linux scheduler assigns CPU time using a priority value called nice. The range is -20 (highest priority) to +19 (lowest priority). Default is 0. A process with nice = -20 gets more CPU time than one with nice = +19. Use renice to change a running process's priority — positive values deprioritise (give it less CPU), negative values prioritise.

bash
# Deprioritise a CPU hog (give it less CPU time) $ renice -n 10 -p 4040 4040 (process ID) old priority 0, new priority 10 # Prioritise a critical process (needs root) $ sudo renice -n -5 -p 4040 4040 (process ID) old priority 10, new priority -5
! Use renice carefully

Only renice processes on servers you own and understand. Setting a process to nice -20 can starve everything else on the box, including SSH — meaning you can't even log back in to fix it. The doctor-and-patients analogy: nice 0 is a normal ward, nice -20 is the ICU. You don't put a stubbed toe in the ICU.

Managing services with systemctl

Services (auto-starting background processes) are managed via systemctl on modern Linux. You can list, start, stop, restart, and check status of any registered service.

bash
# List all running services $ systemctl list-units --type=service ssh.service loaded active running OpenBSD Secure Shell server cron.service loaded active running Regular background program processing nginx.service loaded active running A high performance web server # Stop a service $ sudo systemctl stop cron # Start it again $ sudo systemctl start cron # Restart (stop + start) — use after config changes $ sudo systemctl restart ssh # Check status $ systemctl status ssh

The pipe operator |

You've seen | in many examples already. The pipe takes the standard output of the command on its left and feeds it as standard input to the command on its right. It's how you compose small utilities into powerful pipelines. ps aux | grep java means "list all processes, then filter the output for lines containing 'java'". You can chain as many as you want: ps aux | grep java | grep -v grep | awk '{print $2}' extracts just the PIDs of all Java processes.

Hands-on Lab 7 · Hunt and kill a runaway process
  1. List all running processes: ps aux. Note the total count with ps aux | wc -l.
  2. Find all python processes: ps aux | grep python | grep -v grep. (The grep -v grep excludes the grep process itself from results.)
  3. Find all ssh-related processes: ps aux | grep ssh | grep -v grep.
  4. Start a long-running process in the background: sleep 500 &. Note the PID it prints.
  5. Verify it's running: ps aux | grep <PID>.
  6. Graceful kill: kill <PID>. Check if it died: ps aux | grep <PID> (should return nothing).
  7. Start another: sleep 500 &. Try to graceful-kill — should work. Now start one more, and use kill -9 <PID> to force-kill it.
  8. Pause a process: start sleep 500 &, then kill -stop <PID>. Verify state changed to "T" (stopped) in ps aux.
  9. Resume: kill -cont <PID>. State should return to "S" (sleeping).
  10. List all running services: systemctl list-units --type=service.
  11. Stop the cron service: sudo systemctl stop cron. Verify with systemctl list-units --type=service | grep cron — should say "inactive".
  12. Start it back up: sudo systemctl start cron.
Troubleshooting
  • kill <PID> didn't kill the process — process is ignoring SIGTERM. Use kill -9 <PID>.
  • "No such process" — the PID is wrong, or the process already died. Check with ps aux | grep <PID>.
  • "Operation not permitted" — you don't own the process. Use sudo kill <PID> or switch to root.
  • grep itself appears in ps output — normal. Filter with grep -v grep.
  • systemctl stop hangs — service is stuck. Use sudo systemctl kill <service> to send SIGKILL.
  • Service won't start after config change — check syntax: sudo systemctl status <service> shows the error.
? Mini-Quiz · Chapter 7
  1. What's the difference between ps aux and ps -ef?
  2. What's the difference between kill and kill -9? When would you use each?
  3. What does kill -3 do, and why is it specifically useful for Java apps?
  4. What's the difference between kill -stop and kill -9?
  5. What's the nice range, and what does a negative value mean?
  6. What's the difference between a process and a service?
  7. Why does grep appear in ps aux | grep java output, and how do you filter it out?
  8. What does systemctl restart ssh do that systemctl reload ssh doesn't?
Cheat Sheet · Chapter 7
CommandWhat it does
ps auxList all processes WITH cpu & mem
ps -efList all processes (no cpu/mem)
ps aux | wc -lCount total processes
ps aux | grep X | grep -v grepFilter for X, exclude grep itself
kill <PID>Graceful kill (SIGTERM)
kill -9 <PID>Force kill (SIGKILL, no cleanup)
kill -3 <PID>Thread dump (Java)
kill -stop <PID>Pause a process (SIGSTOP)
kill -cont <PID>Resume a paused process (SIGCONT)
renice -n 10 -p <PID>Lower priority (less CPU)
renice -n -5 -p <PID>Raise priority (more CPU, needs root)
systemctl list-units --type=serviceList all running services
systemctl start <svc>Start a service
systemctl stop <svc>Stop a service
systemctl restart <svc>Restart (use after config changes)
cmd1 | cmd2Pipe: feed stdout of cmd1 as stdin to cmd2
Chapter 7 Progress ☐ Done · +130 XP logged
Chapter 08 · Observatory

Monitoring & Disk Management

⏱ 25 min ★ +120 XP Difficulty: Sensor Officer

Why monitoring matters

Production servers are like spacecraft — they generate constant telemetry, and you need to read it before things go wrong. When a user reports "the server is slow", you have about thirty seconds to figure out whether it's CPU saturation, memory exhaustion, disk I/O, network latency, or an application-level issue. Linux gives you a handful of lightweight commands that answer these questions in seconds. In modern production environments, you'd also integrate with Prometheus + Grafana (or AWS CloudWatch) for dashboards and alerts, but the command-line tools remain essential for ad-hoc debugging.

There are two flavours of monitoring: real-time (interactive tools like top and htop that update continuously while you watch) and static (one-shot commands like free -h that print a snapshot and exit). Real-time tools are great for "watching the server sweat"; static tools are great for scripts and alerts because their output is predictable and parseable.

CPU & process monitoring — top & htop

bash
# Real-time process monitor — updates every second # Sorted by CPU% descending; press q to quit $ top top - 10:45:01 up 12 days, 3:21, 2 users, load average: 0.42, 0.35, 0.30 Tasks: 109 total, 1 running, 108 sleeping, 0 stopped, 0 zombie %Cpu(s): 5.0 us, 2.0 sy, 0.0 ni, 92.5 id, 0.5 wa MiB Mem : 575.0 total, 220.0 free, 180.0 used, 175.0 buff/cache PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 1243 ubuntu 20 0 312544 41200 12000 S 1.2 7.0 0:08.34 python3 # htop — friendlier, colorised, with graphs (install: apt install htop) $ htop

top shows you the highest resource consumers in real time — the load average at the top tells you how busy the system has been over the last 1, 5, and 15 minutes (a load of 1.0 means one core is fully utilised; on a 4-core machine, 4.0 means fully saturated). htop is a wrapper around top with a much nicer UI — colour-coded bars, scrollable process list, and you can kill processes by pressing F9. Same underlying data, easier to read. Use top when you're on a stripped-down server without htop installed; use htop when you can.

Memory & CPU snapshot — free & nproc

bash
# Memory in megabytes (static, scriptable) $ free -m total used free shared buff/cache available Mem: 575 180 220 12 175 350 Swap: 0 0 0 # Memory in human-readable (best for scripts) $ free -h total used free shared buff/cache available Mem: 575Mi 180Mi 220Mi 12Mi 175Mi 350Mi Swap: 0B 0B 0B # Number of CPU cores $ nproc 1 # System performance report (free mem + cache + swap) $ vmstat procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa 1 0 0 225428 84212 91204 0 0 12 18 35 50 3 1 96 0

free -h is your go-to for "how much RAM do I have free?" The "available" column is the most useful — it's what apps can actually use, including reclaimable cache. nproc returns the number of CPU cores, which is essential context for interpreting load averages (a load of 4.0 on a 1-core machine is bad; on an 8-core machine it's fine). vmstat is more detailed, showing swap activity and I/O wait — useful when diagnosing "the server feels sluggish but CPU is low".

Disk usage — df & du

bash
# Disk space across all mounted filesystems (human-readable) $ df -h Filesystem Size Used Avail Use% Mounted on /dev/root 16G 500M 15.5G 3% / tmpfs 288M 0 288M 0% /dev/shm /dev/xvdf 10G 24M 10G 1% /mnt/demo_volume # Per-folder disk usage (sizes inside /opt) $ du -sh /opt/* 24M /opt/logs 4.0K /opt/scripts 4.0K /opt/python # Per-folder usage in current directory $ du -sh *

The two disk commands answer different questions: df -h tells you "how full is each filesystem?" (think of it as the fuel gauge for the whole disk), while du -sh * tells you "which folders are eating my disk?" (the drill-down). When a server runs out of disk, the typical workflow is: df -h to confirm which filesystem is full, then du -sh /* to find the biggest top-level folder, then drill into that folder (du -sh /var/*) until you find the culprit — usually a giant log file in /var/log.

Disk management — adding a new volume

When a server runs out of disk, you don't reboot it — you add a new volume. On AWS, that's an EBS (Elastic Block Store) volume. On Azure, a Managed Disk. On bare metal, a new hard drive. But adding the physical volume is only half the battle — you also have to format it and mount it before Linux will let you use it. Apps cannot use raw block storage directly; they need a filesystem.

The two-step ritual for any new volume: (1) format with a filesystem type (ext4 or xfs are most common — ext4 is the Ubuntu default), then (2) mount to a folder so apps can read/write it. A volume can also be split into partitions, each independently formatted and mounted. On AWS, the volume must be in the same Availability Zone as the EC2 instance — otherwise you can't attach it.

bash
# List all attached block devices (volumes) $ lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT xvda 202:0 0 8G 0 disk ├─xvda1 202:1 0 7G 0 part / ├─xvda14 202:14 0 4M 0 part └─xvda15 202:15 0 106M 0 part /boot/efi xvdf 202:80 0 10G 0 disk # ← new unmounted volume # More detail (use sudo) $ sudo fdisk -l # Step 1: Format the new volume as ext4 (writes filesystem superblocks) $ sudo mkfs -t ext4 /dev/xvdf mke2fs 1.47.0 (5-Feb-2023) Creating filesystem with 2621440 4k blocks and 655360 inodes ... # Step 2: Create a mount point (an empty folder) $ sudo mkdir -p /mnt/demo_volume # Step 3: Mount the formatted volume to that folder $ sudo mount /dev/xvdf /mnt/demo_volume # Verify — lsblk should now show the mount point $ lsblk xvdf 202:80 0 10G 0 disk /mnt/demo_volume # Verify with df -h $ df -h | grep demo_volume /dev/xvdf 10G 24M 10G 1% /mnt/demo_volume # Write a test file to the new volume $ touch /mnt/demo_volume/hello.txt # Unmount later (cannot be in the folder when you unmount!) $ cd / $ sudo umount /mnt/demo_volume
i Production monitoring stack

For real production systems, you wouldn't stare at top all day. The standard modern stack is Prometheus (a time-series database that scrapes metrics from your servers via "node exporter") + Grafana (dashboards and alerting on top of Prometheus). You set up alerts like "if CPU > 90% for 5 minutes, page the on-call engineer" or "if disk > 85%, send a Slack message". AWS CloudWatch serves the same purpose if you're fully on AWS. The command-line tools in this chapter are for ad-hoc debugging when something is wrong right now.

Hands-on Lab 8 · Survey your ship & mount a drive
  1. Run top. Watch it update for 10 seconds. Press q to quit. Note the load average at the top.
  2. If htop isn't installed, install it: apt install htop. Run htop, press F6 to sort by MEM%, press q to quit.
  3. Run free -h. Note your total, used, and available memory.
  4. Run nproc. How many CPUs do you have? (On Docker it might be 1 or 2.)
  5. Run df -h. Identify your root filesystem and how much is used.
  6. Run du -sh /var/* to find which subfolder of /var uses the most space. Then drill into that folder with du -sh /var/<big>/*.
  7. Run lsblk. Identify your root volume (usually xvda or sda).
  8. (Optional, cloud only) In your AWS console, create a 10GB EBS volume in the same AZ as your EC2 instance. Attach it as /dev/sdf. Back in the terminal, run lsblk again — you should see xvdf appear.
  9. Format it: sudo mkfs -t ext4 /dev/xvdf.
  10. Mount it: sudo mkdir -p /mnt/demo_volume then sudo mount /dev/xvdf /mnt/demo_volume.
  11. Verify: df -h | grep demo should show the new 10GB filesystem mounted.
  12. Write a test file: touch /mnt/demo_volume/hello.txt. Confirm with ls /mnt/demo_volume.
  13. Unmount: cd / then sudo umount /mnt/demo_volume. Confirm with lsblk — the MOUNTPOINT should now be empty.
Troubleshooting
  • Server slow, CPU low — likely I/O bound. Check vmstat for high wa (I/O wait) or swap usage.
  • "disk full" but df shows space — could be inodes. Check df -i. Tiny files can exhaust inodes before disk space.
  • New EBS volume not in lsblk — check (a) AZ matches instance, (b) volume state is "in-use" in AWS console, (c) you attached it correctly.
  • "mount: wrong fs type" — you forgot to mkfs first. Raw block storage can't be mounted.
  • "target is busy" trying to unmount — you're inside the directory. cd / first.
  • Mount doesn't survive rebootmount is temporary. Add an entry to /etc/fstab for permanent mounts (beyond this manual's scope).
  • du -sh shows huge /var/log — check log rotation (logrotate) and consider deleting old logs.
? Mini-Quiz · Chapter 8
  1. What's the difference between top and free -h? When would you use each?
  2. What does nproc return, and why does it matter for interpreting load average?
  3. What's the difference between df -h and du -sh *?
  4. What two steps must you complete before you can write files to a freshly-attached EBS volume?
  5. Why does an EBS volume need to be in the same Availability Zone as your EC2 instance?
  6. What command would you use to find which folder inside /var is consuming the most disk?
  7. Why might a server report "disk full" even when df -h shows free space?
Cheat Sheet · Chapter 8
CommandWhat it does
topReal-time process monitor (q to quit)
htopFriendlier top (colorised, F9 to kill)
vmstatSystem perf report — memory, swap, IO
free -hMemory snapshot, human-readable
free -mMemory in megabytes
nprocNumber of CPU cores
df -hDisk free per filesystem, human-readable
df -iInode usage per filesystem
du -sh *Per-folder disk usage in current dir
du -sh /var/*Per-folder usage inside /var
lsblkList block devices (volumes)
sudo fdisk -lDetailed partition info
sudo mkfs -t ext4 /dev/xvdfFormat volume as ext4 filesystem
sudo mount /dev/xvdf /mnt/xMount volume to folder
sudo umount /mnt/xUnmount (must not be in the folder)
mkdir -p /mnt/xCreate mount-point folder (-p = parents)
Chapter 8 Progress ☐ Done · +120 XP logged
Chapter 09 · Comms Array

Networking Fundamentals

⏱ 25 min ★ +120 XP Difficulty: Comms Officer

Why networking matters even for backend engineers

Every production issue eventually involves networking. "The API is slow" — is it the database, the network, or the application? "Users can't reach the site" — is it DNS, the load balancer, the security group, or the server? "The microservice can't connect to Redis" — is it the wrong port, the wrong IP, a firewall, or a routing issue? You cannot debug any of these without understanding IP addresses, subnets, ports, and the OSI model.

The original course defers detailed networking to a separate "Networking Fundamentals" playlist (~3 hours). This chapter condenses the essentials you need to be productive: IP addresses, subnets, CIDR math, ports, and the OSI 7-layer model. This is the minimum viable networking knowledge for any Linux engineer.

IP addresses — the universal device ID

An IP address is a unique number that identifies a device on a network. The current version is IPv4, which uses 32 bits — written as four numbers (each 0-255) separated by dots. Examples: 172.16.3.4, 10.1.2.4, 192.168.12.14, 8.8.8.8 (Google's DNS server). Each of those four numbers is one byte (8 bits = 256 values, 0 to 255). That's why no number in an IPv4 address can exceed 255.

To find your own machine's IP: on Linux/Mac run ifconfig (or the newer ip addr); on Windows run ipconfig in PowerShell. Look for the inet or IPv4 Address line — that's your IP on the local network.

Subnets — splitting networks for security

A subnet is a smaller network carved out of a larger one. The classic example: an office network has 65,000 IP addresses. You split it into a finance subnet (256 IPs, highly secure, restricted access) and a general subnet (everyone else). If a hacker compromises a laptop in the general subnet, the finance subnet is still protected by the firewall between them. Subnets are about isolation and security.

There are two flavours: private subnets have no direct internet access (used for databases, internal APIs), and public subnets have internet access via a route table that points to an internet gateway (used for web servers, load balancers). On AWS, your VPC (Virtual Private Cloud) is your private network — you request a CIDR range like 10.0.0.0/16, then carve it into multiple subnets.

CIDR — the math of subnet sizing

CIDR (Classless Inter-Domain Routing) is the notation used to specify how many IP addresses a subnet contains. The format is <base-ip>/<number>, like 172.16.3.0/24. The number after the slash tells you how many IPs the subnet has, using this formula: 2^(32 - CIDR-number) IP addresses.

CIDRFormulaIP countTypical use
/322^01Single host (a security group rule for one IP)
/312^12Point-to-point link
/302^24Tiny subnet
/292^38Small pool
/282^416Small pool
/272^532Medium pool
/242^8256Classic "Class C" — common office subnet
/162^1665,536"Class B" — common VPC size on AWS
/82^2416,777,216"Class A" — huge organisation

The math: a /24 subnet gives you 2^(32-24) = 2^8 = 256 IPs. A /16 gives you 2^16 = 65,536. A /28 gives you 2^4 = 16. The smaller the CIDR number, the more IPs you get. There are online CIDR calculators — use them, because mental math with negative exponents is error-prone.

! Private IP ranges — don't pick random numbers

Private subnets MUST use one of these three reserved ranges:

  • 10.0.0.0/8 — anything starting with 10.
  • 172.16.0.0/12 — anything starting with 172.16. through 172.31.
  • 192.168.0.0/16 — anything starting with 192.168.

Never build a private subnet with public IPs like 8.8.8.8 or 1.1.1.1 — you'll conflict with real internet hosts and break DNS.

Ports — the apartment number for apps

An IP address gets you to a machine. A port gets you to a specific application on that machine. Ports are numbers from 0 to 65535. When you browse to https://example.com, your browser connects to example.com's IP at port 443 (the HTTPS default). When you SSH, you connect to port 22. When a MySQL client connects to a database, it uses port 3306.

Ports below 1024 are well-known ports reserved for specific protocols (22=SSH, 80=HTTP, 443=HTTPS, 3306=MySQL, 5432=PostgreSQL, 6379=Redis, 8080=Jenkins). When you run your own app, pick a "high" port above 9000 to avoid conflicts — common safe choices are 9000, 9191, 8081. You access an app at a specific port by appending :port to the IP: 3.4.5.8:9191.

i Common reserved ports to avoid
PortService
22SSH
80HTTP
443HTTPS
3306MySQL
5432PostgreSQL
6379Redis
8080Jenkins / alt-HTTP
9200Elasticsearch

The OSI model — 7 layers of networking

The OSI model is a conceptual framework that breaks network communication into seven layers. Each layer has a specific job, and data flows down through them (at the sender) and back up (at the receiver). You won't memorise every detail, but you should know what each layer is responsible for, because debugging often means "which layer is broken?"

#LayerWhat happensWhere
7ApplicationBrowser initiates HTTP/HTTPS request with headers & authBrowser
6PresentationData encryption (TLS for HTTPS) & formattingBrowser
5SessionSession created (cookies, auth tokens) so you don't re-auth every pageBrowser
4TransportData split into segments; protocol chosen (TCP for HTTP, UDP for DNS)Browser → OS
3NetworkSource + dest IP added → becomes packets. Routers decide path.Router
2Data LinkPackets → frames; MAC addresses added; switches handle framesSwitch
1PhysicalFrames → electrical/light signals over cablesCable

Layers 7, 6, 5 happen in the browser itself. Layers 3, 2, 1 happen in network hardware (router, switch, cable). At the receiving end, the layers reverse: signals → frames → packets → segments → session → presentation → application. The TCP/IP model is a simplified version that collapses L7+L6+L5 into one "Application" layer — same concept, fewer layers.

The TCP three-way handshake

Before any data is sent over TCP (which is what HTTP, HTTPS, SSH, and most app protocols use), the client and server perform a three-way handshake to establish the connection:

network
# Step 1: Client sends SYN (synchronise) — "I want to talk" Client ── SYN ──────────────────────► Server # Step 2: Server replies SYN-ACK — "OK, I'm ready, you?" Client ◄────────────────── SYN-ACK ── Server # Step 3: Client replies ACK — "Confirmed, let's go" Client ── ACK ──────────────────────► Server # Now data flows both ways until one side sends FIN to close.

This handshake confirms both sides are ready before any application data is sent. It's why a "connection refused" error happens fast (the server immediately rejects SYN), while a "connection timed out" happens slowly (no SYN-ACK ever returns, usually due to a firewall silently dropping packets). Understanding this helps you diagnose whether a network issue is "the server isn't listening" vs "the network is dropping my packets".

DNS — names to IPs

Before any of the above happens, your browser needs to turn google.com into an IP address. That's DNS (Domain Name System). Your laptop checks its local DNS cache first, then asks your ISP's DNS server, then walks the global DNS hierarchy until it gets an answer (e.g. google.com → 142.250.190.46). Without DNS resolution, the rest of the network journey never starts. The /etc/hosts file on your machine is a manual DNS override — you can hardcode a domain to an IP there for testing.

Hands-on Lab 9 · Map your communications grid
  1. Find your machine's IP: ifconfig (Linux/Mac) or ipconfig (Windows). Identify your IPv4 address.
  2. CIDR math practice: how many IPs are in /22? (Answer: 2^(32-22) = 2^10 = 1024.) How about /26? (2^6 = 64.)
  3. List the private IP ranges. Confirm your machine's IP is in one of them (most home networks use 192.168.x.x).
  4. List 5 well-known ports and what they're for. Then list 3 "safe" high ports you could use for your own app.
  5. Draw the OSI 7-layer model from memory on paper. Check your work against the table above.
  6. Explain out loud (or write down) what happens when you type https://google.com in your browser. Cover: DNS resolution, TCP handshake, OSI layers, and which device handles each layer.
  7. SSH into a remote machine (or just simulate it): ssh user@localhost. Explain which OSI layers SSH traverses.
  8. Look at your /etc/hosts file: cat /etc/hosts. Add a test entry: echo "127.0.0.1 myapp.local" | sudo tee -a /etc/hosts. Then ping it: ping myapp.local. You've just overridden DNS locally.
Troubleshooting
  • "Connection refused" — server received your SYN but no service is listening on that port. Check if the service is running: systemctl status <service>.
  • "Connection timed out" — your SYN packets are being silently dropped, usually by a firewall. Check security groups (AWS) or iptables.
  • Can't SSH to a server — check (a) PasswordAuthentication in /etc/ssh/sshd_config, (b) SSH service running: systemctl status ssh, (c) port 22 open in security group.
  • Can't reach an IP:port — verify the IP is correct, the port matches what the app is listening on, and no firewall blocks it.
  • DNS not resolving — check cat /etc/resolv.conf for configured DNS servers, and cat /etc/hosts for manual overrides.
  • CIDR math doesn't match expected — use an online CIDR calculator. Mental math with powers of 2 is error-prone.
? Mini-Quiz · Chapter 9
  1. Why are IPv4 addresses limited to 4 numbers, each 0-255?
  2. What's the formula for calculating how many IPs are in a /N CIDR block?
  3. How many IPs are in /20? In /28?
  4. Name the three private IP address ranges. Why must private subnets use them?
  5. What's the difference between a private subnet and a public subnet?
  6. Name the 7 OSI layers in order, top-down, and where each one happens.
  7. Describe the TCP three-way handshake. Why does it exist?
  8. Why might you see "connection refused" quickly but "connection timed out" slowly?
  9. You picked port 8080 for your custom app and it conflicts with something. What's likely the conflict, and how would you fix it?
Cheat Sheet · Chapter 9
Tool / ConceptWhat it does
ifconfig (Linux/Mac)View your IP address
ipconfig (Windows)View your IP address
ssh user@ipLog in to a remote server (port 22)
cat /etc/hostsLocal DNS overrides
cat /etc/resolv.confConfigured DNS servers
CIDR formula2^(32 - N) = number of IPs in /N
Private ranges10.x.x.x, 172.16-31.x.x, 192.168.x.x
OSI layers (top-down)Application → Presentation → Session → Transport → Network → Data Link → Physical
TCP handshakeSYN → SYN-ACK → ACK
Common ports22 SSH, 80 HTTP, 443 HTTPS, 3306 MySQL, 5432 PG, 6379 Redis, 8080 Jenkins
Safe custom ports9000, 9191, 8081, 3000 (above 9000 to avoid conflicts)
Chapter 9 Progress ☐ Done · +120 XP logged
Chapter 10 · Automation Engine ★

Shell Scripting

⏱ 25 min ★ +200 XP Difficulty: Lead Engineer
! Second-most important chapter

This chapter is worth the most XP because shell scripting is where Linux goes from "tool you use" to "tool you wield". Once you can write shell scripts, you can automate any repetitive task: deploy code to 50 servers, monitor 10,000 VMs for high CPU, rotate logs nightly, install a complete development environment with one command. This is the skill that turns a junior engineer into a force multiplier.

Why shell scripting?

Picture a DevOps engineer named John at Amazon. He's responsible for 10,000 VMs. Every morning, he needs to check each one's CPU and memory usage, find any that look suspicious, and email a summary to his team. Doing this manually would take weeks. With a 20-line shell script, he can SSH into each VM, run free -h and df -h, parse the output, and email a report — all in under a minute. That's the power of shell scripting: amplifying one engineer to do the work of a hundred.

Shell scripts are just sequences of Linux commands saved in a file, executed in order. There's no compilation, no special IDE, no runtime to install. The only requirements are: (1) the file starts with a shebang line that tells the kernel which interpreter to use, (2) the file has execute permission, and (3) you call it with ./script.sh or sh script.sh.

The shebang — always #!/bin/bash

The first line of every shell script is the shebang: #! followed by the path to the interpreter. For bash scripts, that's #!/bin/bash. This tells the kernel "when this file is executed, run it with /bin/bash". Without it, the kernel won't know how to interpret the file.

! Interview Question

Q: What's the difference between #!/bin/sh and #!/bin/bash?
A: Historically, /bin/sh was a soft link to /bin/bash, so they were identical. But modern Ubuntu (and several other distros) now link /bin/sh to /bin/dash instead — a leaner, faster shell with fewer features. Scripts written for bash may behave differently under dash. Always use #!/bin/bash explicitly when you're using bash features.

Your first script

bash · sample_shell_script.sh
#!/bin/bash # Always include metadata at the top of every script # Author: Your Name # Date: 2025-07-12 # Purpose: Create a workspace folder with starter files # Version: v1.0 mkdir workspace cd workspace touch README.md touch .gitignore echo "Workspace created!"
bash
# Save the script as sample_shell_script.sh # Grant execute permission (REQUIRED — new files aren't executable by default) $ chmod 777 sample_shell_script.sh # Run it $ ./sample_shell_script.sh Workspace created! # Alternative invocation (doesn't need +x but spawns a subshell) $ sh sample_shell_script.sh

The three "set" commands — make your scripts safe

By default, bash scripts are dangerous: they keep going even when a command fails, and they ignore errors inside pipes. Three lines at the top of every script fix this:

bash
#!/bin/bash set -x # Debug mode: print every command AND its output set -e # Exit immediately if ANY command fails (don't continue) set -o pipefail # Catch failures inside pipes (set -e alone misses these) # You can combine them: set -exo pipefail # But keeping them on separate lines lets you comment out individual ones for debugging.
FlagWhat it doesWhy it matters
set -xPrint each command before executing itBetter than echo for debugging — you see exactly what ran
set -eExit script if ANY command returns non-zeroStops cascading failures — don't continue after cd fails
set -o pipefailTreat pipe failure as script failurecmd1 | cmd2 only checks cmd2 by default — pipefail checks both
! Why you need BOTH set -e AND set -o pipefail

set -e alone only checks the last command in a pipe. So cat missing_file | grep x would fail silently — cat errors, but grep succeeds (with no input). Adding set -o pipefail makes the pipe fail if any command in it fails. Always use both together.

Variables & control flow

bash · if_else.sh
#!/bin/bash # CRITICAL: NO SPACES around = in variable assignment a=4 b=10 # CRITICAL: SPACES required inside [ ] brackets if [ $a -gt $b ]; then echo "a is greater than b" else echo "b is greater than a" fi # closes the if (reverse of 'if')
bash · for_loop.sh
#!/bin/bash # Print numbers 1 through 100 for i in {1..100}; do echo $i done # closes the for (no 'rof', just 'done') # Other loop types: while, until, select (covered in advanced tutorials)
The two syntax traps that catch every beginner
  • Variable assignment: NO spaces around =. a=4 works. a = 4 fails (bash thinks you're running a command called a).
  • Conditions: SPACES required inside [ ]. [ $a -gt $b ] works. [$a -gt $b] fails (no separator).

Get these two right and 80% of "why doesn't my script work?" disappears.

The node_health.sh script — your first real automation

Let's build a script that monitors a server's health — the kind of thing a DevOps engineer would run on every VM in their fleet. It checks disk space, memory, CPU count, and lists specific processes.

bash · node_health.sh
#!/bin/bash # Author: Cadet <you> # Date: 2025-07-12 # Purpose: Output node health — disk, memory, CPU, processes # Version: v1.1 set -x # debug: print every command set -e # exit on error set -o pipefail # catch pipe failures df -h # disk usage free -g # memory in GB nproc # CPU count ps -ef | grep amazon | awk '{print $2}' # PIDs of amazon processes

The data-wrangling trio: grep, awk, & pipes

Three tools turn raw command output into useful data. grep filters lines by pattern. awk extracts columns. | (pipe) chains them together.

bash
# grep = filter lines that match a pattern $ ps aux | grep java # only lines containing "java" # awk = extract specific columns (whitespace-separated by default) $ ps -ef | grep amazon | awk '{print $2}' # ↑ print column 2 (the PID) 1234 5678 # Combined: get just the PIDs of all running java processes $ ps -ef | grep java | grep -v grep | awk '{print $2}'

curl & wget — fetching from the internet

Both retrieve content from URLs, but they behave differently. curl streams the content to your terminal (stdout) — perfect for piping into grep or awk. wget downloads the content as a file to your disk — better for "save this for later".

bash
# curl — streams URL content to stdout (terminal) $ curl https://raw.githubusercontent.com/example/log.txt [INFO] Starting app... [ERROR] Database unreachable [INFO] Retrying... # Pipe curl output through grep to find errors only $ curl https://example.com/log.txt | grep ERROR [ERROR] Database unreachable # Explicit GET request $ curl -X GET https://api.example.com/health # wget — downloads URL content as a file on disk $ wget https://example.com/log.txt Saving to: 'log.txt' $ ls log.txt log.txt # now exists on your disk
! Interview Question

Q: Difference between curl and wget?
A: curl streams content to stdout (one step: download + display in terminal, can pipe to grep). wget downloads content to a file on disk (two steps: download, then separately read the file). Use curl for "show me what's at this URL"; use wget for "save this for later".

find — searching the filesystem

bash
# Find a file named pam.d anywhere on the system (needs sudo for system dirs) $ sudo find / -name pam.d /etc/pam.d # Find all .log files in /var $ sudo find /var -name "*.log"

Signals & the trap command

Linux uses signals to communicate with processes. Ctrl+C sends SIGINT (interrupt). kill -9 sends SIGKILL. The trap command lets your script intercept a signal and run custom code instead of dying.

bash · trapped.sh
#!/bin/bash # If user presses Ctrl+C, print this instead of dying trap 'echo "don't use Ctrl+C, this is a long-running import! Try Ctrl+Z instead."' SIGINT echo "Importing data... (will take 10 minutes)" while true; do sleep 1 done

Real-world use case: you have a script that imports 10 million rows into a database. It takes 30 minutes. If someone presses Ctrl+C halfway through, you don't want the database left in a half-imported state. trap could intercept the SIGINT, run a cleanup function (delete partial rows, restore from backup), then exit cleanly.

Other daily-driver commands

CommandWhat it does
man <cmd>Show the manual page for any command (e.g. man awk, man grep)
historyShow all commands you've previously run (great for finding that thing you typed yesterday)
grep <pattern>Filter input by pattern
awk '{print $2}'Extract column 2 from input
find <path> -name <x>Search filesystem for files matching a name
curl <url>Stream URL content to stdout
wget <url>Download URL content to a file
touch <file>Create empty file (use this in scripts, NOT vim — vim is interactive)
Hands-on Lab 10 · Build your first automations
  1. Create ~/cosmic-notebook/sample_shell_script.sh with the first script above (the one that creates a workspace folder). Don't forget #!/bin/bash at the top.
  2. Make it executable: chmod +x sample_shell_script.sh.
  3. Run it: ./sample_shell_script.sh. Verify the workspace folder was created with README.md and .gitignore inside.
  4. Create node_health.sh with the full script above (metadata header + set -exo pipefail + the four monitoring commands). Make it executable and run it.
  5. Observe how set -x prints each command before running it. Comment out set -x and re-run — output should be cleaner.
  6. Create if_else.sh with a=4, b=10, and the if-else block. Run it. Then change a=15 and run again — output should flip.
  7. Create for_loop.sh that prints 1 to 100. Run it.
  8. Practice pipes: run ps -ef | grep <something> | awk '{print $2}' to extract just the PIDs of some process.
  9. Use curl: curl https://raw.githubusercontent.com/torvalds/linux/master/MAINTAINERS | head -20 — fetch the first 20 lines of a real file from GitHub.
  10. Use wget: wget https://raw.githubusercontent.com/torvalds/linux/master/MAINTAINERS — check that the file was saved to disk.
  11. Find a config file: sudo find /etc -name "*.conf" | head -10.
  12. Create trapped.sh with the trap example. Run it, press Ctrl+C, observe the message. Kill it with kill -9 from another terminal.
  13. Run history and look at everything you've typed today. You can re-run any command with !<number>.
Troubleshooting
  • "Permission denied" running ./script.sh — file isn't executable. chmod +x script.sh.
  • "command not found" inside a script — check the spelling, check your PATH, and use set -x to see exactly what's failing.
  • Script behaves differently on Ubuntu vs other distros — you used #!/bin/sh. Change to #!/bin/bash explicitly.
  • Variable assignment fails — there are spaces around =. Remove them: a=4, not a = 4.
  • Condition fails — missing spaces inside [ ]. Use [ $a -gt $b ], not [$a -gt $b].
  • Pipe failures silently ignored — add set -o pipefail alongside set -e.
  • Script keeps running after a command fails — add set -e at the top.
  • Script opens vim and hangs — you used vim instead of touch in the script. Use touch for non-interactive file creation.
  • "date | grep X" doesn't work — that's a famous gotcha: date outputs to stdin (not stdout), and pipe only captures stdout. Use date | cat | grep X or echo $(date) | grep X instead.
? Mini-Quiz · Chapter 10
  1. What does the shebang #!/bin/bash actually do?
  2. What's the difference between #!/bin/sh and #!/bin/bash on Ubuntu?
  3. What do set -e, set -x, and set -o pipefail each do? Why do you need both -e and -o pipefail?
  4. Why does a = 4 fail but a=4 succeed?
  5. Why does [$a -gt $b] fail but [ $a -gt $b ] succeed?
  6. What's the difference between curl and wget?
  7. What does awk '{print $2}' do?
  8. Why does date | grep today not work as expected?
  9. What does the trap command do? Give one real-world use case.
  10. Why must you use touch instead of vim inside an automated script?
Cheat Sheet · Chapter 10 — The Scripting Engine
ConceptWhat it does
#!/bin/bashShebang — always first line, specifies bash interpreter
chmod +x script.shMake script executable (required before ./script.sh)
./script.shExecute a script in current dir
sh script.shAlternative execution (spawns subshell)
set -xDebug mode — print every command before running
set -eExit immediately on any error
set -o pipefailCatch failures inside pipes (use WITH set -e)
var=valueAssign variable (NO spaces around =)
$var or ${var}Read variable
if [ ... ]; then ... fiIf-else (SPACES inside [ ])
for i in 1..100; do ... doneFor loop
trap 'cmd' SIGINTIntercept a signal (e.g. Ctrl+C) and run custom code
grep <pattern>Filter lines by pattern
awk '{print $N}'Extract column N from input
cmd1 | cmd2Pipe: stdout of cmd1 → stdin of cmd2
curl <url>Stream URL content to terminal
wget <url>Download URL to file on disk
find / -name XSearch filesystem for files named X
man <cmd>Show manual for any command
historyShow all previously-typed commands
touch <file>Create empty file (use in scripts, not vim)
Chapter 10 Progress — Automation Engine Online ☐ Done · +200 XP logged
★ Mission Complete ★
1100 XP
Linux Cadet → Hero

Certificate of Cosmic Mastery

This certifies that the bearer has successfully completed the 4-hour Linux Cadet Training Mission — mastering ten systems from operating system fundamentals through shell scripting, and graduating as a certified Linux Hero.

 
Cadet Name · Sign Here
◆ Suggested Next Missions
  • Networking Fundamentals playlist (~3 hrs) — deeper dive into OSI, TCP/IP, subnetting
  • AWS & Azure Networking playlists — VPCs, route tables, security groups in the cloud
  • Advanced Shell Scripting — functions, arrays, sed, awk one-liners
  • Configuration Management — Ansible, Terraform, Puppet for managing fleets
  • Containers & Kubernetes — Docker deep-dive, k8s for orchestrating 10,000 containers
  • CI/CD Pipelines — Jenkins, GitHub Actions, GitLab CI for automated deployments

"Talk is cheap. Show me the code."
— Linus Torvalds