A free, guided curriculum
Learn Linux by
running a real server.
Seven phases, start to finish: navigate the filesystem, understand permissions, process text and files, manage processes and services, work with networking and packages, write real shell scripts, and harden a server the way production systems are run. No prior command-line experience required.
Command Line
Phase 1 of 7
Core Concepts
The filesystem, navigation, and getting help without leaving the terminal.
Goal of this phase: Understand what Linux actually is, get comfortable navigating the filesystem, and build fluency with the handful of commands you'll type hundreds of times a day.
You already operate Linux EC2 instances regularly, so parts of this may feel like review — treat it as formalizing habits you may already have picked up informally.
1. What is Linux? (Concept)
Linux is an open-source operating system kernel, first released by Linus Torvalds in 1991. What most people call "Linux" is really a distribution (distro) — the kernel bundled with a package manager, system tools, and often a desktop environment.
Distros you'll actually run into
| Distro family | Examples | Package manager | Where you've likely seen it |
|---|---|---|---|
| Red Hat-based | RHEL, CentOS, Amazon Linux, Fedora | yum / dnf |
Your EC2 instances, Jenkins guide's native install |
| Debian-based | Ubuntu, Debian | apt |
Very common for Docker base images, many cloud VMs |
| Others | Alpine, SUSE | apk, zypper |
Alpine shows up constantly as a minimal Docker base image |
Under the hood, they're all "Linux" — the kernel and most core commands (ls, cd, grep, etc.) behave identically. What differs is mainly the package manager and some default configuration.
2. The Filesystem Hierarchy (Concept)
Unlike Windows (C:\, D:\), Linux has a single unified tree starting at / (root).
/
├── bin → essential user command binaries (often symlinked to /usr/bin now)
├── boot → bootloader files, kernel images
├── dev → device files (e.g. /dev/sda for a disk)
├── etc → system-wide configuration files
├── home → personal directories for each user (/home/ec2-user)
├── lib → shared libraries
├── opt → optional/third-party software
├── proc → virtual filesystem exposing running processes/kernel info
├── root → the root user's home directory (NOT the same as /)
├── tmp → temporary files, often cleared on reboot
├── usr → user programs, libraries, documentation
└── var → variable data: logs, caches, spool files
| Directory | You'll use it for |
|---|---|
/etc |
Editing config files (nginx, ssh, systemd unit files) |
/var/log |
Reading logs — you'll live here when debugging (Phase 7 goes deep on this) |
/home/<user> |
Your personal files, SSH keys, scripts |
/tmp |
Scratch space, safe to write throwaway files |
/opt |
Where manually-installed third-party software often lives |
3. Hands-On: Basic Navigation
pwd # print working directory - "where am I?"
ls # list files in current directory
ls -la # list ALL files (including hidden dotfiles), long format (permissions, size, owner)
ls -lh # long format with human-readable sizes (K/M/G instead of raw bytes)
cd /var/log # change directory - absolute path
cd .. # go up one level
cd ~ # go to your home directory
cd - # go to the PREVIOUS directory you were in
mkdir my-folder # create a directory
mkdir -p a/b/c # create nested directories in one go (-p = parents)
touch notes.txt # create an empty file (or update its timestamp if it exists)
cp notes.txt backup.txt # copy a file
cp -r my-folder my-folder-copy # copy a directory recursively
mv backup.txt archive/backup.txt # move (or rename) a file
mv notes.txt final-notes.txt # rename in place
rm final-notes.txt # remove a file
rm -r my-folder-copy # remove a directory and its contents
rm -rf a # force remove, no confirmation - USE CAREFULLY, no undo
Important habit: rm -rf has no trash bin and no confirmation. Before running it, especially with wildcards, run the equivalent ls first to see exactly what would be deleted.
Absolute vs. relative paths
- Absolute path: starts with
/, always means the same thing regardless of where you currently are — e.g./home/ec2-user/notes.txt - Relative path: starts from wherever you currently are — e.g.
notes.txtor../notes.txt(the..means "one directory up")
4. Getting Help (Concept + Hands-On)
You don't need to memorize every flag — Linux tells you.
man ls # full manual page for the ls command (press q to quit)
ls --help # quick summary of flags, faster than man
whatis ls # one-line description of what a command does
man pages follow a predictable structure: NAME, SYNOPSIS, DESCRIPTION, OPTIONS. Learning to skim a man page quickly is a genuinely valuable skill — it's faster than searching the web once you're used to the format.
Tab completion — use it constantly
Type the first few letters of a file or command name and press Tab. It auto-completes, or shows you all matching options if there's ambiguity (press Tab twice). This alone will save enormous time and typos.
5. Hands-On Mini-Project: Build a Practice Structure
mkdir -p ~/linux-practice/{projects,scripts,logs}
cd ~/linux-practice
ls -la
touch projects/app.py scripts/deploy.sh logs/app.log
# Explore what you just built
find ~/linux-practice -type f # list all files
find ~/linux-practice -type d # list all directories
# Practice moving around
cd scripts
pwd
cd ../logs
pwd
cd ~/linux-practice
This gives you a small sandbox to keep experimenting in throughout the rest of this curriculum.
6. Checkpoint — Can You Answer These?
- What's the difference between the Linux kernel and a distro?
- What's under
/etc,/var, and/home, in plain terms? - What's the difference between an absolute and a relative path?
- What does
mkdir -p a/b/cdo that plainmkdir a/b/cwouldn't? - Why should you be cautious with
rm -rf, and what's a good habit before running it? - How would you quickly check what flags a command supports, without leaving the terminal?
If you can answer all six, you're ready for Phase 2: File Permissions & Users — how Linux decides who can do what to a file, and how to manage that.
Quick Reference — This Phase
pwd
ls -la
cd <path>
mkdir -p <path>
touch <file>
cp -r <src> <dst>
mv <src> <dst>
rm -r <path>
man <command>
find <path> -type f
Next: Phase 2 — File Permissions & Users. Ask when ready.
Phase 2 of 7
Permissions & Users
rwx, chmod, chown, sudo vs. root, and special permission bits.
Goal of this phase: Understand how Linux decides who can read, write, or execute a file, and how users and groups fit into that model.
Builds on Phase 1.
1. Reading Permissions (Concept)
Run ls -l on any file and you'll see something like:
-rwxr-xr-- 1 ec2-user developers 220 Sep 9 10:15 deploy.sh
Break that first block apart:
- rwx r-x r--
│ │ │ │
│ │ │ └── OTHER: read only
│ │ └─────── GROUP: read + execute
│ └──────────── OWNER: read + write + execute
└──────────────── file type (- = regular file, d = directory, l = symlink)
| Symbol | Meaning |
|---|---|
r |
read — view file contents / list directory contents |
w |
write — modify file contents / create-delete files in a directory |
x |
execute — run the file as a program / enter (cd into) a directory |
- |
permission not granted |
Permissions are grouped into three sets: owner (the user who owns the file), group (users in the file's group), other (everyone else).
2. Hands-On: Reading and Changing Permissions
cd ~/linux-practice
touch script.sh
ls -l script.sh
chmod — changing permissions
Symbolic mode:
chmod u+x script.sh # add execute for the owner (user)
chmod g+w script.sh # add write for the group
chmod o-r script.sh # remove read for others
chmod a+x script.sh # add execute for everyone (a = all)
Numeric (octal) mode — each permission has a value: r=4, w=2, x=1, summed per group:
chmod 755 script.sh # owner: rwx (7) | group: r-x (5) | other: r-x (5)
chmod 644 script.sh # owner: rw- (6) | group: r-- (4) | other: r-- (4)
chmod 700 script.sh # owner: rwx (7) | group: --- (0) | other: --- (0)
| Common pattern | Numeric | Meaning |
|---|---|---|
755 |
rwxr-xr-x |
Standard for scripts/executables — owner can edit, everyone can run |
644 |
rw-r--r-- |
Standard for regular files — owner can edit, everyone can read |
600 |
rw------- |
Private files — only the owner can read/write (e.g. SSH private keys) |
700 |
rwx------ |
Private directories/scripts — only the owner has any access |
Try it
echo '#!/bin/bash' > script.sh
echo 'echo "Hello!"' >> script.sh
./script.sh # fails - "Permission denied" (no execute bit yet)
chmod +x script.sh # add execute permission
./script.sh # now it runs
3. Users & Groups (Concept)
whoami # who am I?
id # my user ID (UID), group ID (GID), and all groups I belong to
cat /etc/passwd # every user on the system (one line per user)
cat /etc/group # every group on the system
groups # groups the CURRENT user belongs to
chown — changing ownership
sudo chown ec2-user script.sh # change the owner
sudo chown ec2-user:developers script.sh # change owner AND group in one command
sudo chgrp developers script.sh # change only the group
You need sudo for chown in most cases — regular users can't give away files they don't own, or take ownership of files that aren't theirs.
4. sudo vs. root (Concept)
- root is the all-powerful superuser account (UID 0) — can do literally anything on the system, including things that can break it.
- sudo ("superuser do") lets a regular, permitted user run a single command with root privileges, temporarily, with logging of what was run and by whom.
whoami # e.g. ec2-user
sudo whoami # root
Why sudo is preferred over logging in as root directly:
- Every sudo command is logged (/var/log/secure or /var/log/auth.log) — an audit trail of who did what
- You only get elevated privileges for one command at a time, reducing the chance of an accidental system-wide mistake
- Cloud images (like the EC2 AMIs you use) typically disable direct root login entirely and expect you to use sudo
sudo su # switch to a full root shell (use sparingly - easy to make sweeping mistakes)
exit # leave the root shell, back to your normal user
Who can use sudo?
Controlled by the /etc/sudoers file (edit only with sudo visudo, which validates syntax before saving — a syntax error in this file directly, edited unsafely, can lock you out of sudo entirely).
sudo visudo # view/edit safely
5. Special Permissions (Concept — Awareness Level)
Beyond the standard rwx, three special permission bits exist. You won't set these often, but recognizing them matters:
| Bit | Symbol | What it does |
|---|---|---|
| SUID | s in owner's execute slot (e.g. -rwsr-xr-x) |
The program runs with the file owner's privileges, not the user running it (e.g. passwd runs as root momentarily so any user can change their own password) |
| SGID | s in group's execute slot |
Similar, but for group privileges; on a directory, new files created inside automatically inherit the directory's group |
| Sticky bit | t in other's execute slot (e.g. on /tmp) |
In a shared directory, users can only delete their own files, even if they have write access to the directory |
ls -ld /tmp
# drwxrwxrwt ← that trailing 't' is the sticky bit, exactly why /tmp is safe for multiple users to share
You'll recognize these in ls -l output far more often than you'll need to set them yourself — that recognition is the actual goal here.
6. Hands-On Mini-Project: Set Up a Shared Practice Directory
cd ~/linux-practice
mkdir shared-scripts
chmod 755 shared-scripts
cd shared-scripts
echo '#!/bin/bash' > backup.sh
echo 'echo "Backing up..."' >> backup.sh
chmod 750 backup.sh # owner: rwx, group: r-x, other: no access
ls -l backup.sh
Confirm your understanding by predicting the output of ls -l before running it — that prediction habit is what actually cements this.
7. Checkpoint — Can You Answer These?
- In
-rwxr-xr--, what can the file's group members do with it? - What does
chmod 644 file.txtset, in plain English? - Why is
sudogenerally preferred over logging in directly as root? - What command safely edits the sudoers file, and why does that matter?
- What does the sticky bit on
/tmpactually prevent? - Why do SSH private key files typically need
600permissions, and what happens if they're more open than that? (Hint: try it — SSH itself often refuses to use an overly-permissive key file.)
If you can answer all six, you're ready for Phase 3: Text Processing & Editors — the tools you'll use constantly to read, search, and edit files directly on a server.
Quick Reference — This Phase
ls -l
chmod 755 <file>
chmod u+x <file>
sudo chown <user>:<group> <file>
whoami
id
groups
sudo visudo
sudo su
| Numeric | Meaning |
|---|---|
| 755 | rwxr-xr-x — standard executable |
| 644 | rw-r--r-- — standard file |
| 600 | rw------- — private (SSH keys) |
| 700 | rwx------ — private, owner-only |
Next: Phase 3 — Text Processing & Editors. Ask when ready.
Phase 3 of 7
Text Processing & Editors
grep, sed, awk, pipes, redirection, and surviving in vim.
Goal of this phase: Build fluency with the commands you'll use constantly to read, search, filter, and edit files directly on a server — where you often won't have a GUI at all.
Builds on Phases 1–2.
1. Viewing File Contents (Concept + Hands-On)
cat file.txt # dump the entire file to the terminal
less file.txt # page through it - use arrow keys, / to search, q to quit
head file.txt # first 10 lines
head -n 20 file.txt # first 20 lines
tail file.txt # last 10 lines
tail -n 20 file.txt # last 20 lines
tail -f /var/log/messages # follow a file live as new lines are appended - critical for watching logs in real time
tail -f is one of the most-used commands in real DevOps work — it's how you watch a log file update in real time while something is running (an app starting up, a deployment happening, etc.). Press Ctrl+C to stop following.
less vs cat
Use cat for short files you want to see all at once. Use less for anything long — it doesn't dump the whole thing at once, loads faster on huge files, and lets you search and scroll.
2. Redirection and Pipes (Concept)
This is the single most powerful idea in the Linux command line: commands can feed their output into other commands, building small tools into much bigger ones.
| Operator | Meaning |
|---|---|
> |
Redirect output to a file, overwriting it |
>> |
Redirect output to a file, appending to it |
< |
Feed a file in as input to a command |
\| |
Pipe — send one command's output as the next command's input |
2> |
Redirect errors (stderr) specifically, separate from normal output (stdout) |
&> |
Redirect both output and errors together |
Hands-On
echo "line one" > notes.txt # create/overwrite notes.txt with this line
echo "line two" >> notes.txt # append a second line
cat notes.txt # confirm both lines are there
ls -la /etc > etc-listing.txt # save command output to a file
ls /nonexistent 2> errors.txt # save just the error message to a file
# Pipe example: list processes, filter for "ssh", count matching lines
ps aux | grep ssh | wc -l
That last line is the essence of Linux philosophy: small, focused tools (ps, grep, wc) chained together to do something none of them could do alone.
3. grep — Searching Text (Concept + Hands-On)
grep "error" app.log # find lines containing "error"
grep -i "error" app.log # case-insensitive
grep -r "TODO" ~/linux-practice # search recursively through a directory
grep -n "error" app.log # show line numbers
grep -v "debug" app.log # invert match - show lines that DON'T contain "debug"
grep -c "error" app.log # count matching lines instead of printing them
grep -E "error|warning" app.log # extended regex - match either word
Hands-On
cd ~/linux-practice
printf "INFO: starting up\nERROR: connection failed\nINFO: retrying\nERROR: timeout\n" > app.log
grep "ERROR" app.log
grep -c "ERROR" app.log
grep -v "ERROR" app.log
4. sed and awk — Editing and Extracting (Concept, Practical Level)
You don't need to master these deeply, but recognize the common patterns.
sed — stream editor, most commonly used for find-and-replace
sed 's/ERROR/WARNING/' app.log # replace first match per line (prints to terminal, doesn't modify file)
sed -i 's/ERROR/WARNING/g' app.log # -i = edit the file IN PLACE, g = replace ALL matches per line, not just the first
sed -n '2,3p' app.log # print only lines 2-3
Caution: sed -i modifies the file directly with no undo. On an important file, make a backup first (cp file.txt file.txt.bak) or use sed -i.bak 's/x/y/' file.txt, which automatically saves the original as file.txt.bak.
awk — pattern scanning and column-based text processing
echo "alice 25 engineer" | awk '{print $1}' # print the 1st field (fields split on whitespace by default)
echo "alice 25 engineer" | awk '{print $2, $3}' # print the 2nd and 3rd fields
# real example: print just the usernames from `ls -l` output
ls -l ~/linux-practice | awk '{print $3}'
Think of awk as: "for each line, split it into columns, then do something with specific columns." That covers the vast majority of real-world awk one-liners you'll encounter.
5. Finding Files and Commands (Concept + Hands-On)
find ~/linux-practice -name "*.log" # find files by name pattern
find ~/linux-practice -type d # find only directories
find ~/linux-practice -mtime -1 # files modified in the last 1 day
find ~/linux-practice -size +1M # files larger than 1MB
which python3 # show the full path of a command (where it will actually run from)
whereis python3 # similar, also shows man page location if available
locate app.log # fast filename search using a prebuilt index (may need `sudo updatedb` first, and may not be installed on minimal images)
find searches live, right now, on disk — slower but always accurate. locate searches a prebuilt index — much faster but can be stale if the index hasn't updated recently. Use find when you need certainty; locate when you need speed on a big filesystem.
6. Editors: nano and vim (Concept + Hands-On)
On a server, you often won't have a GUI editor — you'll edit files directly in the terminal.
nano — simple, beginner-friendly
nano notes.txt
Controls are shown at the bottom of the screen: ^O = write out (save), ^X = exit, ^W = search (the ^ means Ctrl).
vim — steeper learning curve, available on virtually every Linux system, extremely fast once learned
vim notes.txt
vim has modes — this trips up almost everyone at first:
| Mode | How to enter it | What it's for |
|---|---|---|
| Normal mode | Press Esc (default mode on open) |
Navigation, deleting, copying — NOT typing |
| Insert mode | Press i from normal mode |
Actually typing text, like a normal editor |
| Command mode | Press : from normal mode |
Saving, quitting, search-and-replace |
Minimal vim survival kit
i → enter insert mode (start typing)
Esc → back to normal mode
:w → save
:q → quit (fails if unsaved changes)
:wq → save and quit
:q! → quit WITHOUT saving (discard changes)
dd → delete the current line (normal mode)
/searchterm → search forward for text
n → jump to next search match
Hands-On
vim notes.txt
- Press
i, type a line of text - Press
Esc - Type
:wqand press Enter to save and quit - Run
cat notes.txtto confirm it saved
Practice this loop (i → type → Esc → :wq) a few times until it's automatic — this single sequence covers the vast majority of quick server-side edits you'll ever need.
7. Checkpoint — Can You Answer These?
- What's the difference between
>and>>? - When would you use
tail -f, and why is it useful for a running application? - What does
grep -vdo, and give a real scenario where you'd use it? - Why should you be cautious with
sed -i, and what's a safe habit around it? - What are vim's three core modes, and what does each one do?
- What's the practical difference between
findandlocate?
If you can answer all six, you're ready for Phase 4: Process & System Management — viewing and controlling running processes, background jobs, and checking system health.
Quick Reference — This Phase
cat file.txt
less file.txt
tail -f file.log
grep -i "pattern" file.txt
grep -r "pattern" directory/
sed -i 's/old/new/g' file.txt
awk '{print $1}' file.txt
find . -name "*.log"
which <command>
# vim survival kit
i insert mode
Esc normal mode
:w save
:wq save and quit
:q! quit without saving
dd delete line
/term search
Next: Phase 4 — Process & System Management. Ask when ready.
Phase 4 of 7
Process & System Management
ps, top, systemd services, tmux, and checking system health.
Goal of this phase: Learn to view and control running processes, manage background jobs, work with systemd services, and check system health — the daily-driver skills for keeping a server running well.
Builds on Phases 1–3. You've already used some of this (systemctl) in your Jenkins native-install work — this phase formalizes and extends it.
1. Viewing Processes (Concept + Hands-On)
Every running program on Linux is a process, identified by a PID (process ID).
ps # processes in YOUR current terminal session
ps aux # ALL processes on the system, all users, full detail
ps aux | grep nginx # find a specific process
Reading ps aux output:
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.1 1234 512 ? Ss 09:00 0:01 /sbin/init
nginx 842 0.1 0.5 9876 2048 ? S 09:05 0:00 nginx: worker
- PID — the process's unique ID, used to target it with
kill - %CPU / %MEM — current resource usage
- STAT — process state (
S= sleeping,R= running,Z= zombie,D= uninterruptible sleep) - COMMAND — what's actually running
top / htop — live, continuously updating view
top # built-in on virtually every Linux system
htop # nicer interface, color, easier to navigate - may need: sudo yum install -y htop
In top: press q to quit, k to kill a process by PID, M to sort by memory usage, P to sort by CPU usage.
2. Killing Processes (Concept + Hands-On)
kill <PID> # sends SIGTERM - a polite "please stop" request the process can catch and clean up after
kill -9 <PID> # sends SIGKILL - immediate, forceful termination, cannot be ignored or caught
killall nginx # kill ALL processes matching a name
pkill -f "python app.py" # kill processes matching a pattern in their full command line
Always try plain kill first. kill -9 skips any cleanup the process would normally do (closing files, releasing locks, finishing writes) — reach for it only when a process is truly unresponsive to a normal kill.
Hands-On
# Start something long-running in the background to practice on
sleep 300 &
jobs # show background jobs in this shell
ps aux | grep sleep
kill %1 # kill background job #1 (referenced by job number)
# or, using the PID directly:
# kill <PID>
3. Foreground, Background & Persistent Sessions (Concept + Hands-On)
sleep 100 # runs in the FOREGROUND - blocks your terminal until it finishes
sleep 100 & # runs in the BACKGROUND - you get your terminal back immediately
jobs # list background jobs in the current shell
fg %1 # bring job 1 back to the foreground
Ctrl+Z # suspend (pause) the current foreground job
bg %1 # resume a suspended job, but in the background
The problem: background jobs die when your SSH session ends
If you SSH into a server, start a background job, and then disconnect, the job normally gets killed too (it receives a SIGHUP — "hangup" signal). Two common solutions:
nohup — makes a specific command immune to hangup:
nohup python app.py &
# output goes to nohup.out by default unless redirected elsewhere
tmux / screen — create a persistent terminal session that keeps running even after you disconnect, and that you can reattach to later:
tmux new -s mysession # start a new named session
# ...do your work...
# press Ctrl+B then D to detach (session keeps running)
tmux ls # list active sessions
tmux attach -t mysession # reattach later, from the same or a different SSH connection
tmux is the more powerful, more commonly used option in real DevOps work today — worth building the habit of using it for any long-running or interactive server session.
4. systemd — Managing Services (Concept + Hands-On)
You've already used this pattern managing Jenkins (sudo systemctl start jenkins). Here's the fuller picture.
systemd is the init system on most modern Linux distros — it starts services at boot, restarts them if they crash (if configured to), and gives you a consistent interface for managing them.
sudo systemctl status jenkins # is it running? recent log lines? enabled at boot?
sudo systemctl start jenkins # start it now
sudo systemctl stop jenkins # stop it now
sudo systemctl restart jenkins # stop then start
sudo systemctl enable jenkins # start automatically on boot
sudo systemctl disable jenkins # don't start automatically on boot
sudo systemctl is-enabled jenkins # check if it's set to start on boot
A minimal custom systemd service (Hands-On)
Say you have a simple script you want managed as a proper service.
sudo tee /etc/systemd/system/mytool.service > /dev/null << 'EOF'
[Unit]
Description=My Practice Tool
After=network.target
[Service]
ExecStart=/usr/bin/sleep infinity
Restart=on-failure
User=ec2-user
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload # tell systemd to notice the new unit file
sudo systemctl start mytool
sudo systemctl status mytool
sudo systemctl enable mytool
Clean up when done experimenting:
sudo systemctl stop mytool
sudo systemctl disable mytool
sudo rm /etc/systemd/system/mytool.service
sudo systemctl daemon-reload
Restart=on-failure is exactly the kind of self-healing behavior you'd have configured for orchestrated containers too (echoes the Docker Phase 7 self-healing concept, but at the single-host/OS level instead of the cluster level).
5. Checking System Health (Concept + Hands-On)
df -h # disk space usage, human-readable, per filesystem/mount
du -sh /var/log # total size of a specific directory
du -sh /var/log/* # size of each item inside, individually
free -h # memory usage: total, used, free, available (human-readable)
uptime # how long the system has been running, plus load average
vmstat 2 5 # system performance snapshot, repeated every 2 seconds, 5 times
Reading free -h output
total used free shared buff/cache available
Mem: 7.8Gi 2.1Gi 3.2Gi 120Mi 2.5Gi 5.4Gi
available is the number that actually matters most in practice — it accounts for memory the kernel could reclaim from cache if an application needed it, so it's a more honest picture than raw free.
Reading uptime's load average
10:32:01 up 5 days, 3:14, 2 users, load average: 0.15, 0.22, 0.18
The three numbers are the average system load over the last 1, 5, and 15 minutes. As a rough rule of thumb, compare against your CPU core count — a load average close to or above your core count means the system is fully or over-utilized.
6. Checkpoint — Can You Answer These?
- What's the difference between
killandkill -9? - Why do background jobs typically die when you disconnect from SSH, and what are two ways to prevent that?
- What does
systemctl enabledo thatsystemctl startdoesn't? - In a systemd unit file, what does
Restart=on-failureaccomplish? - Why is
availableinfree -houtput usually more useful than rawfree? - What does the load average in
uptimeactually represent?
If you can answer all six, you're ready for Phase 5: Networking & Package Management — connecting to and diagnosing network issues, plus installing and managing software.
Quick Reference — This Phase
ps aux | grep <name>
top / htop
kill <PID>
kill -9 <PID>
nohup <command> &
tmux new -s <name>
tmux attach -t <name>
sudo systemctl status <service>
sudo systemctl start|stop|restart <service>
sudo systemctl enable|disable <service>
df -h
du -sh <path>
free -h
uptime
Next: Phase 5 — Networking & Package Management. Ask when ready.
Phase 5 of 7
Networking & Packages
curl, ss, yum/apt, SSH config, and firewall basics.
Goal of this phase: Diagnose network connectivity, install and manage software properly, set up SSH the right way, and understand basic firewall concepts.
Builds on Phases 1–4.
1. Basic Networking Commands (Concept + Hands-On)
ip addr # show network interfaces and their IP addresses (modern replacement for old `ifconfig`)
ip a # shorthand for the same thing
ping google.com # test basic connectivity - sends packets, waits for replies (Ctrl+C to stop)
ping -c 4 google.com # send exactly 4 pings then stop
curl https://api.github.com # fetch a URL and print the response body
curl -I https://api.github.com # fetch just the HEADERS (useful to check status codes quickly)
curl -o output.json https://api.github.com/users/octocat # save the response to a file
wget https://example.com/file.zip # download a file directly to disk
ss -tulpn # show listening ports and the process using each (modern replacement for `netstat`)
netstat -tulpn # older equivalent, still common in tutorials/muscle memory
Reading ss -tulpn output
Netid State Local Address:Port Process
tcp LISTEN 0.0.0.0:8080 users:(("java",pid=1234,fd=6))
This tells you: something is listening on port 8080, on all interfaces (0.0.0.0), and it's a Java process with PID 1234. Exactly the command you'd reach for when debugging "why can't I connect to my app" — confirm the process is actually listening on the port you expect, on the interface you expect.
DNS lookups
nslookup google.com # basic DNS lookup
dig google.com # more detailed DNS lookup, standard tool for DNS debugging
2. Package Management (Concept + Hands-On)
You've already used both of these in earlier guides (yum for Jenkins/Docker on EC2). Here's the fuller picture.
yum / dnf (Red Hat-based: RHEL, Amazon Linux, Fedora)
dnf is the modern successor to yum — on newer systems they're often aliases of each other.
sudo yum update # update the package index and upgrade all installed packages
sudo yum install -y htop # install a package (-y = auto-confirm)
sudo yum remove -y htop # uninstall a package
yum search "text editor" # search for packages by keyword
yum list installed # list everything currently installed
yum info htop # detailed info about a specific package
apt (Debian-based: Ubuntu, Debian)
sudo apt update # refresh the package index (does NOT upgrade anything yet)
sudo apt upgrade -y # actually upgrade installed packages
sudo apt install -y htop # install a package
sudo apt remove -y htop # uninstall a package
apt search "text editor" # search for packages
apt list --installed # list installed packages
Key habit: on apt systems, always update before install — the local package index can be stale, causing "package not found" errors for packages that do exist.
Where do packages actually come from?
Both yum and apt pull from configured repositories — remote servers hosting collections of packages. You saw this directly in the Jenkins guide when you added the Jenkins repo (jenkins.repo) before installing Jenkins itself — that's exactly this mechanism.
3. SSH (Concept + Hands-On)
You already use SSH to reach your EC2 instances. Here's what's happening underneath, and how to make it more convenient and secure.
Key-based authentication (Concept)
SSH keys come in pairs: a private key (stays on your machine, never shared) and a public key (goes on the server, in ~/.ssh/authorized_keys). The server can verify you hold the matching private key without that key ever crossing the network — far more secure than password authentication, and what AWS EC2 uses by default with your .pem files.
# Generate a new key pair (if you don't already have one for this purpose)
ssh-keygen -t ed25519 -C "your_email@example.com"
# Follow the prompts - default location is fine for most cases
# The public key is what you'd add to a server's authorized_keys
cat ~/.ssh/id_ed25519.pub
Connecting
ssh -i mykey.pem ec2-user@<public-ip> # connect using a specific private key file
SSH config file — stop typing long commands every time
Create/edit ~/.ssh/config:
Host myserver
HostName 54.123.45.67
User ec2-user
IdentityFile ~/.ssh/mykey.pem
Now you can just run:
ssh myserver
This is a genuinely high-value habit once you're regularly connecting to several servers — no more hunting for the right .pem file and IP each time.
scp and rsync — copying files to/from a remote server
# scp - simple, one-shot copy
scp -i mykey.pem localfile.txt ec2-user@<ip>:/home/ec2-user/ # upload
scp -i mykey.pem ec2-user@<ip>:/home/ec2-user/remotefile.txt . # download
# rsync - smarter, only transfers what changed, can resume interrupted transfers
rsync -avz -e "ssh -i mykey.pem" ./local-folder/ ec2-user@<ip>:/home/ec2-user/remote-folder/
-a = archive mode (preserves permissions, timestamps, etc.), -v = verbose, -z = compress during transfer. For anything beyond a single small file — especially repeated syncs of a folder — prefer rsync over scp; it's dramatically faster on the second run since it only sends the differences.
4. Firewall Basics (Concept)
A firewall controls what network traffic is allowed in or out of a machine. On AWS, you're already using Security Groups (a firewall at the AWS/EC2 layer) — but Linux also has its own OS-level firewall, worth understanding conceptually since not everything runs on EC2 with Security Groups available.
firewalld (common on RHEL/Amazon Linux)
sudo systemctl status firewalld
sudo firewall-cmd --list-all # see current rules
sudo firewall-cmd --add-port=8080/tcp --permanent # allow a port, persist across reboots
sudo firewall-cmd --reload # apply changes
sudo firewall-cmd --remove-port=8080/tcp --permanent # remove a rule
ufw (common on Ubuntu — "uncomplicated firewall," a simpler frontend)
sudo ufw status
sudo ufw allow 8080/tcp
sudo ufw deny 23
sudo ufw enable
iptables (the older, lower-level tool both of the above are built on)
Awareness-level only for now — iptables rules are powerful but verbose and easy to misconfigure. Both firewalld and ufw exist specifically to make this more manageable; reach for iptables directly only when you need very fine-grained control.
Practical note for your EC2 work: Security Groups (AWS-level) and an OS-level firewall (firewalld/ufw) are two independent layers — traffic has to be allowed by both to get through. If something isn't reachable, checking both layers, not just one, saves real debugging time.
5. Checkpoint — Can You Answer These?
- What does
ss -tulpnshow you, and what's a real scenario where you'd reach for it? - What's the practical difference between
yum updatebehavior and needing to runapt updatebeforeapt installon Debian-based systems? - Why is SSH key-based authentication considered more secure than password authentication?
- What does an SSH config file (
~/.ssh/config) save you from doing repeatedly? - When would you reach for
rsyncinstead ofscp? - Why might traffic still be blocked even after you open a port in a Security Group?
If you can answer all six, you're ready for Phase 6: Shell Scripting — automating real tasks instead of typing commands one at a time.
Quick Reference — This Phase
ip a
ping -c 4 <host>
curl -I <url>
ss -tulpn
sudo yum install -y <package> # RHEL/Amazon Linux
sudo apt update && sudo apt install -y <package> # Ubuntu/Debian
ssh-keygen -t ed25519
ssh -i <key.pem> <user>@<ip>
scp -i <key.pem> <file> <user>@<ip>:<path>
rsync -avz -e "ssh -i <key.pem>" <local>/ <user>@<ip>:<remote>/
sudo firewall-cmd --add-port=<port>/tcp --permanent && sudo firewall-cmd --reload # RHEL
sudo ufw allow <port>/tcp # Ubuntu
Next: Phase 6 — Shell Scripting. Ask when ready.
Phase 6 of 7
Shell Scripting
Variables, conditionals, loops, functions, and real automation.
Goal of this phase: Stop typing commands one at a time — write reusable bash scripts that automate real tasks, with proper error handling.
Builds on everything in Phases 1–5 — this is where it all gets combined into actual automation.
1. Your First Script (Concept + Hands-On)
mkdir -p ~/linux-practice/scripts
cd ~/linux-practice/scripts
hello.sh:
#!/bin/bash
echo "Hello from a shell script!"
echo "Today is $(date)"
chmod +x hello.sh
./hello.sh
The shebang line
#!/bin/bash on the very first line tells the system which interpreter should run this script. Without it, running ./hello.sh might fail or use the wrong shell. Always include it, always as the first line.
Running scripts: three ways
./hello.sh # requires execute permission (chmod +x) - uses the shebang to pick the interpreter
bash hello.sh # explicitly runs it with bash - doesn't need execute permission
source hello.sh # runs it in your CURRENT shell session, not a new one - variables it sets persist after
2. Variables (Concept + Hands-On)
#!/bin/bash
name="Anup"
echo "Hello, $name"
# Command substitution - capture a command's output into a variable
current_dir=$(pwd)
echo "You are in: $current_dir"
file_count=$(ls | wc -l)
echo "Files here: $file_count"
No spaces around = — name = "Anup" is a syntax error in bash; it must be name="Anup". This trips up almost everyone coming from other languages.
3. Arguments and Input (Concept + Hands-On)
greet.sh:
#!/bin/bash
echo "Script name: $0"
echo "First argument: $1"
echo "Second argument: $2"
echo "All arguments: $@"
echo "Number of arguments: $#"
chmod +x greet.sh
./greet.sh Anup Kumar
Reading user input interactively
#!/bin/bash
read -p "Enter your name: " user_name
echo "Hello, $user_name!"
4. Conditionals (Concept + Hands-On)
#!/bin/bash
read -p "Enter a number: " num
if [ "$num" -gt 10 ]; then
echo "That's greater than 10"
elif [ "$num" -eq 10 ]; then
echo "That's exactly 10"
else
echo "That's less than 10"
fi
Common test operators
| Numeric | Meaning | String | Meaning | File | Meaning |
|---|---|---|---|---|---|
-eq |
equal | = |
equal | -f |
is a regular file |
-ne |
not equal | != |
not equal | -d |
is a directory |
-gt |
greater than | -z |
is empty | -e |
exists |
-lt |
less than | -n |
is not empty | -x |
is executable |
File check example
#!/bin/bash
if [ -f "/etc/hosts" ]; then
echo "hosts file exists"
fi
if [ ! -d "/tmp/mydir" ]; then
echo "mydir doesn't exist yet, creating it"
mkdir /tmp/mydir
fi
Spacing matters in [ ] — you need a space after [ and before ]. [$num -gt 10] (no spaces) is a syntax error.
5. Loops (Concept + Hands-On)
for loop
#!/bin/bash
for i in 1 2 3 4 5; do
echo "Number: $i"
done
# Loop over files
for file in ~/linux-practice/*.txt; do
echo "Found: $file"
done
# Numeric range
for i in {1..5}; do
echo "Count: $i"
done
while loop
#!/bin/bash
count=1
while [ $count -le 5 ]; do
echo "Attempt $count"
count=$((count + 1))
done
Practical loop example: check if multiple servers are up
#!/bin/bash
servers=("google.com" "github.com" "does-not-exist-xyz.com")
for server in "${servers[@]}"; do
if ping -c 1 -W 2 "$server" > /dev/null 2>&1; then
echo "$server is UP"
else
echo "$server is DOWN"
fi
done
6. Functions (Concept + Hands-On)
#!/bin/bash
greet() {
local name=$1
echo "Hello, $name!"
}
check_disk_space() {
local threshold=$1
local usage=$(df / | tail -1 | awk '{print $5}' | tr -d '%')
if [ "$usage" -gt "$threshold" ]; then
echo "WARNING: disk usage is at ${usage}%, above threshold of ${threshold}%"
else
echo "OK: disk usage is at ${usage}%"
fi
}
greet "Anup"
check_disk_space 80
local inside a function keeps a variable scoped to that function only — without it, variables are global by default in bash, which can cause confusing bugs in longer scripts.
7. Exit Codes and Error Handling (Concept + Hands-On)
Every command returns an exit code when it finishes: 0 means success, anything else (1-255) means some kind of failure.
ls /etc > /dev/null
echo $? # prints 0 - success
ls /nonexistent > /dev/null 2>&1
echo $? # prints a non-zero code - failure
Using exit codes in scripts
#!/bin/bash
if ! ping -c 1 google.com > /dev/null 2>&1; then
echo "No internet connection - aborting"
exit 1
fi
echo "Connection confirmed, continuing..."
set -e — stop the script on any failure
#!/bin/bash
set -e # exit immediately if any command fails
mkdir /tmp/mydeploy
cd /tmp/mydeploy
cp important-file.txt . # if this fails (e.g. file doesn't exist), the script stops here instead of continuing blindly
echo "Deploy complete"
set -e is a strong habit for any script where continuing after a failure could cause worse problems (e.g., a deployment script that shouldn't proceed to "restart service" if "copy new files" failed).
set -u — catch typos in variable names
#!/bin/bash
set -u # error out if you reference an undefined variable (catches typos like $nmae instead of $name)
Many real scripts start with set -euo pipefail — a strict-mode combo (-e = exit on error, -u = error on undefined variables, -o pipefail = catch failures inside a pipe chain, not just the last command in it) that catches a wide class of silent bugs early.
8. Hands-On Project: A Real Automation Script
A script that backs up a directory, timestamps it, and cleans up backups older than 7 days — a genuinely useful pattern, and a preview of what a production backup job (like the Jenkins backup routine from your Jenkins Phase 7 guide) actually looks like under the hood.
backup.sh:
#!/bin/bash
set -euo pipefail
SOURCE_DIR="$HOME/linux-practice"
BACKUP_DIR="$HOME/backups"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
BACKUP_FILE="$BACKUP_DIR/backup-$TIMESTAMP.tar.gz"
mkdir -p "$BACKUP_DIR"
echo "Backing up $SOURCE_DIR..."
tar -czf "$BACKUP_FILE" -C "$(dirname "$SOURCE_DIR")" "$(basename "$SOURCE_DIR")"
echo "Backup created: $BACKUP_FILE"
echo "Removing backups older than 7 days..."
find "$BACKUP_DIR" -name "backup-*.tar.gz" -mtime +7 -delete
echo "Current backups:"
ls -lh "$BACKUP_DIR"
chmod +x backup.sh
./backup.sh
Run it a couple of times (it'll create multiple timestamped backups) and confirm the cleanup logic by checking ls -lh ~/backups.
9. Checkpoint — Can You Answer These?
- What does the shebang line do, and what happens if you leave it out?
- Why is
name = "value"a syntax error in bash, butname="value"isn't? - What's the difference between
$1,$@, and$#? - What does
set -edo, and give a scenario where you'd want it? - What does
localdo inside a bash function, and why does it matter? - In the backup script, what does
find ... -mtime +7 -deleteactually do?
If you can answer all six, you're ready for Phase 7: Production Practices — log management, cron scheduling, and basic security hardening, the final phase in this curriculum.
Quick Reference — This Phase
#!/bin/bash
set -euo pipefail
variable="value"
result=$(command)
if [ "$var" -gt 10 ]; then ... fi
for i in {1..5}; do ... done
while [ condition ]; do ... done
my_function() {
local x=$1
echo "$x"
}
echo $? # last command's exit code
exit 1 # exit the script with a failure code
Next: Phase 7 — Production Practices (logs, cron, security hardening). Ask when ready.
Phase 7 of 7
Production Practices
Log management, cron scheduling, and security hardening.
Goal of this phase: Learn how logs are managed, how to schedule recurring tasks, and basic security hardening — then connect everything you've learned back to the Jenkins and Docker work you've already built.
This is the final phase in the core Linux curriculum. Builds on everything in Phases 1–6.
1. Log Management (Concept + Hands-On)
Traditional log files under /var/log
ls -l /var/log
sudo tail -f /var/log/messages # Amazon Linux / RHEL - general system log
sudo tail -f /var/log/syslog # Ubuntu/Debian equivalent
sudo tail -f /var/log/secure # RHEL - authentication/sudo log
sudo tail -f /var/log/auth.log # Ubuntu equivalent
You've already used tail -f (Phase 3) — this is exactly where it earns its keep in real work: watching authentication attempts, service errors, or kernel messages live.
journalctl — the modern systemd logging system
Most current distros use journald alongside (or instead of) traditional flat log files.
journalctl # all logs, oldest first
journalctl -r # reverse - newest first
journalctl -u jenkins # logs for a SPECIFIC service (you'll recognize this from debugging Jenkins)
journalctl -u jenkins -f # follow a specific service's logs live
journalctl --since "1 hour ago" # time-filtered
journalctl --since "2024-01-01" --until "2024-01-02"
journalctl -p err # only error-priority and above
Log rotation (Concept)
Logs grow forever if nothing manages them. logrotate is the standard tool — it runs on a schedule (usually daily, via cron) and compresses, archives, or deletes old logs based on rules.
cat /etc/logrotate.conf # global config
ls /etc/logrotate.d/ # per-application configs (e.g., one for nginx, one for a custom app)
Example custom config, /etc/logrotate.d/myapp:
/var/log/myapp/*.log {
daily
rotate 7
compress
missingok
notifempty
}
This rotates myapp's logs daily, keeps 7 days of history, compresses old ones, and doesn't error if the log file is missing or empty. This is the production-grade version of the manual log-cleanup logic you wrote by hand in your Linux Phase 6 backup script.
2. Cron — Scheduling Recurring Tasks (Concept + Hands-On)
cron runs commands on a schedule, completely unattended — this is how you'd actually run your Phase 6 backup script every night rather than remembering to run it manually.
crontab -l # list your current scheduled jobs
crontab -e # edit your scheduled jobs (opens in your default editor)
Cron syntax
┌───────────── minute (0–59)
│ ┌───────────── hour (0–23)
│ │ ┌───────────── day of month (1–31)
│ │ │ ┌───────────── month (1–12)
│ │ │ │ ┌───────────── day of week (0–6, Sunday=0)
│ │ │ │ │
* * * * * command-to-run
Note: this is the same cron syntax you already used for Jenkins build triggers (Poll SCM, Build Periodically) in your Jenkins Phase 2 guide — cron is a universal scheduling format, not specific to any one tool.
Practical examples
0 2 * * * /home/ec2-user/linux-practice/scripts/backup.sh # every day at 2:00 AM
*/15 * * * * /home/ec2-user/scripts/healthcheck.sh # every 15 minutes
0 9 * * 1-5 /home/ec2-user/scripts/daily-report.sh # 9 AM, weekdays only
0 0 1 * * /home/ec2-user/scripts/monthly-cleanup.sh # midnight, first day of every month
Hands-On: Schedule Your Phase 6 Backup Script
crontab -e
Add this line (adjust the path to match your actual script location):
0 2 * * * /home/ec2-user/linux-practice/scripts/backup.sh >> /home/ec2-user/backups/cron.log 2>&1
Important habit: always redirect cron job output somewhere (>> logfile 2>&1), since cron jobs don't have a terminal to print to — without redirection, output either silently disappears or gets emailed to you (if mail is configured), which you'll rarely notice.
Checking cron actually ran
grep CRON /var/log/cron # RHEL/Amazon Linux
grep CRON /var/log/syslog # Ubuntu
cat ~/backups/cron.log # your own redirected output, from the example above
3. Security Hardening Basics (Concept + Hands-On)
Disable root SSH login
Root is the highest-value target for attackers — disabling direct root SSH login forces anyone (legitimate or not) to authenticate as a regular user first, then sudo if they need elevated access. This adds a layer of accountability and friction.
sudo nano /etc/ssh/sshd_config
Find (or add) this line:
PermitRootLogin no
Apply the change:
sudo systemctl restart sshd
Caution: before disabling root login, confirm you have a working non-root user with sudo access, and that you can SSH in as that user — test in a second terminal window before closing your current session, so you don't lock yourself out.
Disable password authentication (key-only)
Even stronger — once you're confident your SSH keys work reliably:
PasswordAuthentication no
Same caution applies: verify key-based login works in a separate session first.
fail2ban (Concept, Brief Hands-On)
fail2ban watches log files (like SSH auth logs) for repeated failed login attempts and automatically bans the offending IP address for a period of time — a lightweight, effective defense against brute-force attacks.
sudo yum install -y epel-release fail2ban # Amazon Linux/RHEL (needs the EPEL repo first)
sudo systemctl enable --now fail2ban
sudo fail2ban-client status # see active "jails" (protection rules)
sudo fail2ban-client status sshd # see banned IPs for the SSH jail specifically
Keep the system patched
sudo yum update -y # RHEL/Amazon Linux
sudo apt update && sudo apt upgrade -y # Ubuntu/Debian
In real production, this is usually automated on a schedule (cron, or a dedicated tool) rather than done manually — tying directly back into what you just learned in section 2.
4. Connecting Everything Back Together
This is the natural closing point across all three curriculums you've now built. Every piece of infrastructure you've worked with is Linux underneath:
| What you built elsewhere | What's actually running under it |
|---|---|
| Your Jenkins EC2 instance (Jenkins Phase 1) | A Linux box you systemctl start jenkins on — exactly what Linux Phase 4 covers |
| Terraform-provisioned EC2 instances (Jenkins Phase 6) | Fresh Linux servers that Ansible then configures over SSH — exactly what Linux Phase 5 covers |
| Docker containers (Docker curriculum, all phases) | Each container is, at its core, an isolated Linux process using kernel features like namespaces and cgroups — the same ps, permissions, and process concepts from Linux Phases 1, 2, and 4, just isolated per-container |
| Ansible playbooks | Ultimately just running the same package manager, systemd, and file commands from this curriculum — automated instead of typed by hand |
| Jenkins backup routine (Jenkins Phase 7) | The exact same tar/cron pattern you just built by hand in Linux Phase 6 and scheduled in this phase |
Understanding Linux at this level is genuinely what makes the rest of your stack make sense instead of feeling like memorized commands.
5. Checkpoint — Can You Answer These?
- What's the difference between traditional
/var/logfiles andjournalctl, and when would you reach for each? - What does
logrotatesolve, and why does acompress+rotate 7config matter for disk space? - Read this cron line and explain exactly when it runs:
*/15 * * * * - Why should cron job output always be redirected somewhere?
- Why should you test a new SSH login method in a second terminal before closing your current session when hardening SSH config?
- What does fail2ban actually do, in plain terms?
If you can answer all six, you've completed the full 7-phase Linux curriculum — from zero, to permissions and users, to process management, to writing real automation scripts, to production-grade log management, scheduling, and security hardening.
Quick Reference — This Phase
journalctl -u <service> -f
journalctl --since "1 hour ago"
crontab -e
crontab -l
# 0 2 * * * /path/to/script.sh >> /path/to/log 2>&1
sudo nano /etc/ssh/sshd_config
# PermitRootLogin no
# PasswordAuthentication no
sudo systemctl restart sshd
sudo fail2ban-client status
What's Next (Beyond This Curriculum)
- Kernel-level troubleshooting —
strace,dmesg, deeper performance tuning - Configuration management at scale — you already know Ansible; this Linux foundation is exactly what makes those playbooks make sense
- Container internals — namespaces, cgroups, and how Docker actually builds on core Linux kernel features (a natural follow-up now that you've completed the Docker curriculum too)
Congratulations on finishing the Linux curriculum — combined with your completed Jenkins and Docker curriculums, you now have a genuinely well-rounded foundation across the whole stack: the OS, the containers, and the automation on top of both.