Linux Toolbox 0/20

Backend engineeringThe Linux command line20 modules, terminal open

Linux
Toolbox

The commands you reach for every day on a laptop or a server, in the order you meet them: move around, work with files, search, wire tools together, then run and watch real services. Press Run on any session to replay it.

LinuxBashUbuntu 24.04
01

Move around

Every session starts with three questions: where am I, what is here, and how do I get there. pwd, ls and cd answer them.

pwdlscdFirst day

Linux arranges everything as one tree that starts at /. Your home folder is /home/you, written as ~ for short. A path that starts with / is absolute; anything else is relative to where you stand.

pwd

Prints the folder you are standing in.

See the example
Where am I
ls -lah

Lists what is here, with hidden files, sizes and owners.

See the example
What is here
cd path

Moves you. cd ~ goes home, cd - jumps back.

See the example
Go there
navigate.shBASH
pwd
ls
ls -lah
cd /var/log
cd ~        # home
cd -        # back to the previous folder

Read the first column of ls -l. A leading d is a folder and - is a file. Names that start with a dot are hidden until you add -a.

shree@devbox: ~Ubuntu 24.04
# a recorded session, replayed when you press Run
pwd
/home/shree
ls
deploy.sh  notes.txt  projects
ls -lah
total 20K
drwxr-x--- 4 shree shree 4.0K Oct  3 09:12 .
-rw-r--r-- 1 shree shree  220 Jul 14 10:02 .bashrc
-rwxr-xr-x 1 shree shree  412 Oct  2 18:40 deploy.sh
drwxr-xr-x 5 shree shree 4.0K Oct  1 21:30 projects
cd /var/log && pwd
/var/log
cd -
/home/shree

Worth remembering

FlagWhat it addsTry
-lLong view: permissions, owner, size, datels -l
-aHidden dot filesls -a
-hSizes as K, M, Gls -lh
-tNewest firstls -lt
-REvery subfolder tools -R src
02

Files and folders

Make, copy, move, rename and delete. Five verbs cover most of the work you do on files.

mkdircpmvrmFirst day

There is no rename command: renaming is mv to a new name in the same folder. There is also no recycle bin, so rm is final.

mkdir -p a/b/c

Makes folders, with every missing parent when you add -p.

See the example
Create
touch file

Makes an empty file, or bumps the date on one that exists.

See the example
Create
cp -r src dest

Copies a file. Add -r to copy a whole folder.

See the example
Copy
mv old new

Moves or renames in one step.

See the example
Move
rm -r path

Deletes for good. Add -i to be asked first.

See the example
Delete
ln -s target link

Makes a shortcut that points at another path.

See the example
Link
files.shBASH
mkdir -p app/src/utils
touch app/README.md
cp app/README.md app/README.bak
cp -r app app-copy
mv app/README.bak app/docs.md
rm app/docs.md
rm -ri app-copy      # asks before each delete
ln -s /var/log/nginx logs

Slow down before rm -r. Run ls on the exact path first. A stray space in rm -r ./ build deletes the folder you are in.

shree@devbox: ~Ubuntu 24.04
# a recorded session, replayed when you press Run
mkdir -p app/src/utils
touch app/README.md
cp -r app app-copy
mv app/README.md app/docs.md
ls app
docs.md  src
rm app-copy
rm: cannot remove 'app-copy': Is a directory
# folders need rm -r, and check the path first
rm -r app-copy && ls
app  deploy.sh  notes.txt  projects

Worth remembering

CommandDoesSafer with
cpCopies, overwriting quietly-i asks before overwriting
mvMoves or renames, overwriting quietly-i or -n never overwrites
rmDeletes with no undo-i asks each time, -I once
mkdirMakes one folder-p makes parents, no error if it exists
lnLinks a name to a path-s for a symbolic link
03

Read files

Look inside a file without opening an editor: all of it, a page at a time, the start, the end, or live as it grows.

catlessheadtailFirst day

Use cat for short files and less for long ones, because less never loads the whole file. tail -f is the one you will keep open while a server runs.

cat file

Prints the whole file at once. Best under a screen long.

See the example
Short files
less file

Scrolls a page at a time and searches with /.

See the example
Long files
head -n 5

Shows the first lines.

See the example
Start
tail -f

Shows the last lines and keeps following new ones.

See the example
Live logs
read.shBASH
cat notes.txt
less /var/log/syslog      # q quits, /word searches
head -n 5 access.log
tail -n 3 access.log
tail -f access.log        # Ctrl+C to stop following

Follow a log while you test. Open tail -f in one terminal, hit your API from another, and watch each request land.

shree@devbox: ~Ubuntu 24.04
# a recorded session, replayed when you press Run
cat notes.txt
rotate db password on friday
bump node to 22 on staging
head -n 2 access.log
192.168.1.20 - - [03/Oct/2026:09:14:02 +0530] "GET /api/health HTTP/1.1" 200 17
10.0.0.15 - - [03/Oct/2026:09:14:05 +0530] "GET /favicon.ico HTTP/1.1" 404 162
tail -f access.log
192.168.1.20 - - [03/Oct/2026:09:15:41 +0530] "POST /api/login HTTP/1.1" 200 412
# waiting for new lines, Ctrl+C to stop

Worth remembering

Key in lessDoes
Space / bNext page / back a page
/wordSearch forward, n for the next match
g / GJump to the start / the end
FFollow new lines like tail -f
qQuit
04

Find files

find walks a folder tree and matches files by name, type, size or age, then can act on every match.

findwhichtypeDaily

Give find a starting folder, then tests. Quote any pattern with a star in it, so the shell hands it to find untouched instead of expanding it first.

find . -name

Matches by name. -iname ignores case.

See the example
By name
find -size +10M

Matches by size, handy for a full disk.

See the example
By size
find -mtime +7

Matches by age in days.

See the example
By age
which cmd

Shows which program runs when you type a name.

See the example
Programs
Find files fast$ find . -name "filename"Built in
In simple words

Looks through this folder and every folder inside it, and prints the path of each file whose name matches. Swap filename for the real name, or a pattern like "*.pdf".

Under the hood

find walks the directory tree depth first from the starting point (.) and evaluates each test per entry. -name matches the base name against a shell glob, case sensitive; quote the pattern so Bash does not expand it before find sees it.

Try it$ find . -name "docker-compose.yml"Lists every compose file in the current project, however deep it sits.

find.shBASH
find . -name "notes.txt"
find /var/log -type f -size +10M
find . -name "*.tmp" -mtime +7 -delete
find src -type f -name "*.ts" -exec wc -l {} +
which node
type ll

Dry run before -delete. Run the same find without -delete first and read the list. Put -delete last, or it acts before your other tests.

shree@devbox: ~Ubuntu 24.04
# a recorded session, replayed when you press Run
find . -name "*.log"
./projects/api/logs/app.log
./projects/api/logs/error.log
find /var/log -type f -size +10M
/var/log/syslog.1
/var/log/journal/3f2c9a/system.journal
which node
/usr/bin/node
type ll
ll is aliased to `ls -alF'
find . -name "notes.txt"
./notes.txt
./projects/api/docs/notes.txt

Worth remembering

TestMatches
-name "*.js"Names by pattern, case sensitive
-type f / -type dFiles only / folders only
-size +100MBigger than 100 MB
-mtime -1Changed in the last day
-exec cmd {} +Runs cmd on the matches
-maxdepth 2Stops two levels down
05

Search text

grep prints every line that matches a pattern. It is the fastest way into an unfamiliar codebase or a noisy log.

grep-r-i-vDaily

Point grep at a file, a folder with -r, or the output of another command through a pipe. Flags combine, so -rin means recursive, any case, with line numbers.

grep -n

Line numbers, so you can jump straight there.

See the example
Where
grep -r

Searches every file under a folder.

See the example
Everywhere
grep -v

Keeps lines that do not match.

See the example
Filter out
grep -c

Counts matching lines.

See the example
How many
grep.shBASH
grep "ERROR" app.log
grep -i "timeout" app.log
grep -n "ERROR" app.log
grep -c "ERROR" app.log
grep -rn "TODO" src/
grep -v "/api/health" access.log
grep -E " (4|5)[0-9]{2} " access.log

Add context with -C. grep -C 3 ERROR app.log shows three lines either side, which usually holds the cause.

shree@devbox: ~Ubuntu 24.04
# a recorded session, replayed when you press Run
grep -n "ERROR" app.log
42:2026-10-03 09:20:11 ERROR db connection refused
57:2026-10-03 09:21:40 ERROR retry limit reached
grep -c "ERROR" app.log
2
grep -rn "TODO" src/
src/routes/user.ts:18:  // TODO: validate email
src/db/pool.ts:7:  // TODO: read pool size from env

Worth remembering

FlagMeans
-iIgnore case
-nShow line numbers
-rSearch folders recursively
-vInvert: lines that do not match
-wWhole words only
-lOnly the names of files that match
-EExtended regex: |, +, {n}
-A / -B / -C nn lines after / before / around
06

Pipes and redirects

The idea that makes Linux click: every command reads input and writes output, and you can wire them together or into files.

|>>>2>&1Daily

A pipe | hands one command's output to the next. Redirects send output to a file. Errors travel on a separate stream, number 2, which is why they still reach the screen after a plain >.

Overwriteecho hi > file.txtReplaces whatever the file held.
Appendecho hi >> file.txtAdds a line to the end.
pipes.shBASH
ls /etc | wc -l
echo "deploy started" > deploy.log
echo "build ok" >> deploy.log
npm run build > build.log 2>&1
sort names.txt | uniq | tee unique.txt
find . -name "*.tmp" | xargs rm

Order matters in 2>&1. Write > file 2>&1. Reversed, errors go to the screen because they were pointed there before stdout moved.

shree@devbox: ~Ubuntu 24.04
# a recorded session, replayed when you press Run
ls /etc | wc -l
214
echo "deploy started" > deploy.log
echo "build ok" >> deploy.log
cat deploy.log
deploy started
build ok
ls missing.txt 2> errors.log
cat errors.log
ls: cannot access 'missing.txt': No such file or directory

Worth remembering

SymbolSends
a | bOutput of a into b
> fileOutput into file, replacing it
>> fileOutput onto the end of file
< fileFile into the command as input
2> fileErrors into file
> file 2>&1Output and errors into file
| tee fileTo the screen and a file at once
| xargs cmdEach line as arguments to cmd
07

Slice text

Small tools that each do one thing to lines of text. Chained with pipes, they answer real questions about your logs.

wccutsortuniqtrDaily

The classic question: which IPs hit my server most? Take the first column, sort it so repeats sit together, count each run, then sort by the count.

Top IPs, one tool at a time

cut -f1→sort→uniq -c→sort -rn→head -3→3Each tool reads the last one's output.
top-ips.shBASH
wc -l access.log
cut -d' ' -f1 access.log | sort | uniq -c | sort -rn | head -3
cut -d, -f2 users.csv
sort -t, -k3 -n users.csv
echo "Hello" | tr 'a-z' 'A-Z'

uniq only merges neighbours. Always sort before uniq, or repeats that are far apart are counted separately.

shree@devbox: ~Ubuntu 24.04
# a recorded session, replayed when you press Run
wc -l access.log
1842 access.log
cut -d' ' -f1 access.log | sort | uniq -c | sort -rn | head -3
    612 192.168.1.20
    388 10.0.0.15
    120 172.16.4.9
echo "Hello" | tr 'a-z' 'A-Z'
HELLO

Worth remembering

ToolFlags worth knowing
wc-l lines, -w words, -c bytes
cut-d delimiter, -f field numbers
sort-n numbers, -r reverse, -k column, -u unique, -h sizes
uniq-c count, -d only repeats
tr-d delete chars, -s squeeze repeats
08

sed and awk

sed edits lines as they stream past. awk treats every line as columns and can count, filter and add up.

sedawkPower user

Reach for sed to find and replace, and awk when the data has columns. In awk, $1 is the first column, $NF the last, and -F sets the separator.

sedsed 's/old/new/g' fileRewrites text in every line.
awkawk '{print $1, $9}' filePicks columns and works with them.
sed-awk.shBASH
sed 's/http:/https:/g' urls.txt
sed -i.bak 's/PORT=3000/PORT=8080/' .env
sed -n '10,20p' app.log
awk '{print $1, $9}' access.log
awk '$9 >= 500 {print $7}' access.log
awk -F, '{sum += $3} END {print sum}' orders.csv

Keep a backup with -i.bak. sed -i edits the file in place. The .bak suffix leaves the original next to it.

shree@devbox: ~Ubuntu 24.04
# a recorded session, replayed when you press Run
sed 's/http:/https:/g' urls.txt
https://api.example.com
https://cdn.example.com
sed -i.bak 's/PORT=3000/PORT=8080/' .env && cat .env
NODE_ENV=production
PORT=8080
awk '{print $1, $9}' access.log | head -2
192.168.1.20 200
10.0.0.15 404
awk -F, '{sum += $3} END {print sum}' orders.csv
48250

Worth remembering

PieceMeans
sed 's/a/b/g'Replace every a with b
sed -n '5p'Print only line 5
sed '/^#/d'Delete comment lines
awk '{print $2}'Second column
awk 'NR > 1'Skip the header line
awk 'END {print NR}'Count the lines
09

Permissions

Every file has an owner, a group and three sets of rights. Read them once and Permission denied stops being a mystery.

chmodchownumaskDaily

Rights come in three slots: the owner, the group, then everyone else. Each slot is read r (4), write w (2) and execute x (1). Add them up per slot and you get the number chmod takes.

Reading -rwxr-xr-x

rwxr-xr-xowner · group · others→755r is 4, w is 2, x is 1. Add them per slot.
permissions.shBASH
ls -l deploy.sh
chmod +x deploy.sh
chmod 640 .env
chmod -R 755 public/
sudo chown www-data:www-data /var/www/app
umask

Secrets get 600 or 640. A .env or private key that everyone can read is a leak waiting to happen. ssh refuses keys that are too open.

shree@devbox: ~Ubuntu 24.04
# a recorded session, replayed when you press Run
./deploy.sh
bash: ./deploy.sh: Permission denied
# the file has no x bit yet
chmod +x deploy.sh && ls -l deploy.sh
-rwxr-xr-x 1 shree shree 412 Oct  2 18:40 deploy.sh
chmod 640 .env && ls -l .env
-rw-r----- 1 shree shree 64 Oct  3 09:30 .env
umask
0002

Worth remembering

NumberRightsUse it for
755rwxr-xr-xScripts and public folders
644rw-r--r--Ordinary files
640rw-r-----Config the group may read
600rw-------Private keys and secrets
700rwx------Your own private folder
10

Users and sudo

Know who you are, borrow root for one command, and give a service its own account instead of running it as you.

whoamiidsudousermodOps

sudo runs one command as root and logs it. Use it per command rather than living in a root shell, so every powerful action is a choice you made on purpose.

whoami

Your user name.

See the example
Who
id

Your user id and every group you belong to.

See the example
Groups
sudo cmd

Runs one command as root.

See the example
Power
usermod -aG group user

Adds a user to a group.

See the example
Access
users.shBASH
whoami
id
sudo apt update
sudo adduser deploy
sudo usermod -aG docker deploy
sudo -u deploy whoami
passwd                 # change your own password

Never drop the -a. usermod -G docker without -a replaces every group the user had, sudo included.

shree@devbox: ~Ubuntu 24.04
# a recorded session, replayed when you press Run
whoami
shree
id
uid=1000(shree) gid=1000(shree) groups=1000(shree),4(adm),27(sudo),110(docker)
sudo adduser --disabled-password --gecos "" deploy
info: Adding user `deploy' ...
info: Adding new group `deploy' (1001) ...
sudo usermod -aG docker deploy && groups deploy
deploy : deploy docker
# new groups apply at the user's next login

Worth remembering

CommandDoes
sudo cmdRun one command as root
sudo -u name cmdRun as another user
sudo -iOpen a root shell; exit to leave
su - nameSwitch to another user's login
adduser nameCreate a user with a home folder
usermod -aG g nameAdd name to group g
groups nameList a user's groups
11

Processes

Every running program is a process with a number. Find it, watch it, and stop it politely before you stop it hard.

pstoppgrepkillDaily

kill sends a signal, and the default asks the process to finish cleanly. Only reach for -9 when it ignores you, because a forced kill skips closing files and connections.

ps aux

A snapshot of every process.

See the example
List
top

A live view sorted by CPU. htop is friendlier if installed.

See the example
Watch
pgrep -a name

Finds process ids by name.

See the example
Find
kill pid

Asks a process to stop. -9 forces it.

See the example
Stop
Monitor system resources$ htopInstall: sudo apt install htop
In simple words

A live, colourful task manager inside the terminal. It shows how busy each CPU core is, how much memory is in use, and which programs are using it, updating every second or two.

Under the hood

htop reads /proc for per process and per core statistics and redraws on an interval. Bars break CPU time into user, system and other states, and memory into used, buffers and cache. F6 sorts by any column, F4 filters, F5 shows the process tree and F9 sends a signal.

Try it$ htop -u deployShows only the processes owned by the deploy user.

processes.shBASH
ps aux --sort=-%mem | head -3
pgrep -a node
top                 # q quits, M sorts by memory
kill 4821
kill -9 4821        # last resort
pkill -f "node server.js"
htop                # F6 sort, F9 kill, F10 quit

Try TERM, wait, then KILL. Give a server a few seconds to drain requests after kill before you escalate to kill -9.

shree@devbox: ~Ubuntu 24.04
# a recorded session, replayed when you press Run
ps aux --sort=-%mem | head -3
USER     PID %CPU %MEM    VSZ   RSS TTY   STAT START  TIME COMMAND
postgres 1290  1.2  6.4 412880 262144 ?   Ss   08:01  0:42 postgres: main
shree    4910  0.8  3.1 1180244 126720 pts/1 Sl 09:05 0:09 node worker.js
pgrep -a node
4821 node server.js
4910 node worker.js
kill 4821 && pgrep -a node
4910 node worker.js
htop
  0[|||||        21.4%]   4[||            6.1%]
  Mem[||||||||||||     5.21G/15.5G]  Tasks: 142, 418 thr; 2 running
  Swp[                  0K/2.00G]  Load average: 0.42 0.37 0.31
    PID USER      PRI  NI  VIRT   RES   SHR S CPU% MEM%   TIME+  Command
   4910 shree      20   0 1127M  123M 41.2M S  3.2  0.8  0:09.41 node worker.js
# a live screen: F3 search, F4 filter, F6 sort, F9 kill, F10 quit

Worth remembering

SignalNumberMeans
TERM15Please finish up; the default
INT2What Ctrl+C sends
HUP1Reload config, for many daemons
KILL9Stop now; cannot be caught
STOP / CONT19 / 18Pause and resume
12

Jobs and sessions

Run things in the background, bring them back, and keep them alive after you close the laptop.

&jobsnohuptmuxOps

A trailing & frees your prompt, but the job still dies with the terminal. On a server, start long work inside tmux so a dropped SSH connection costs you nothing.

Backgroundnpm run dev &Ends when this terminal closes.
Detachedtmux new -s deployKeeps running after you log out.
jobs.shBASH
npm run dev &
jobs
fg %1               # Ctrl+Z pauses it again
bg %1               # resume it in the background
nohup node server.js > server.log 2>&1 &
tmux new -s deploy  # Ctrl+B then D detaches
tmux attach -t deploy

tmux beats nohup for humans. nohup keeps a process alive, but tmux keeps the whole shell, so you can come back and see the output.

shree@devbox: ~Ubuntu 24.04
# a recorded session, replayed when you press Run
npm run dev &
[1] 5120
jobs
[1]+  Running                 npm run dev &
nohup node server.js > server.log 2>&1 &
[2] 5188
tmux ls
deploy: 1 windows (created Sat Oct  3 09:41:10 2026)

Worth remembering

Keys or commandDoes
Ctrl+CStop the foreground job
Ctrl+ZPause it
jobsList this shell's jobs
fg / bgBring to front / resume behind
disownLet a job outlive the shell
tmux attachReturn to a detached session
13

Disk and memory

Answer the two questions behind most outages: is the disk full, and where did the memory go.

dfdufreelsblkOps

df reports whole drives; du measures folders. Start with df to find the full drive, then du to find what filled it.

df -h

Free space per mounted drive.

See the example
Drives
du -sh *

Size of each item in this folder.

See the example
Folders
free -h

Memory and swap in use.

See the example
Memory
lsblk

Disks and their partitions as a tree.

See the example
Devices
Find storage hogs$ du -sh ~/* | sort -hBuilt in
In simple words

Measures every file and folder in your home folder and lists them smallest to largest, so the biggest space users end up at the bottom.

Under the hood

du -s prints one total per argument and -h uses K, M and G. The shell expands ~/* to each item in your home (dot files excluded), and sort -h orders human readable sizes correctly, so 900M sorts below 1.2G.

Try it$ du -sh ~/* ~/.cache | sort -h | tail -5The five biggest items, including the hidden cache folder.

Analyze disk usage visually$ ncdu ~Install: sudo apt install ncdu
In simple words

Scans your home folder once, then lets you walk through it with the arrow keys, biggest folders first, so you can see exactly where the space went and delete what you do not need.

Under the hood

ncdu (NCurses Disk Usage) does a single du style scan into memory, then serves an interactive tree sorted by size, with bar graphs relative to the parent. -x stays on one filesystem; -o file exports the scan so you can browse a server's results elsewhere with -f.

Try it$ sudo ncdu -x /Scans the root filesystem only, skipping other mounts such as /proc or network drives.

disk.shBASH
df -h /
du -sh ~/* | sort -h
du -h --max-depth=1 /var | sort -h | tail -5
free -h
lsblk
ncdu ~              # arrows move, d deletes, q quits

Read available, not free. Linux uses spare memory as cache and hands it back on demand, so available is the number that matters.

shree@devbox: ~Ubuntu 24.04
# a recorded session, replayed when you press Run
df -h /
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda1        78G   41G   34G  55% /
du -sh * | sort -h
4.0K	deploy.sh
8.0K	notes.txt
1.3G	projects
free -h
               total        used        free      shared  buff/cache   available
Mem:            15Gi       5.2Gi       1.9Gi       312Mi       8.4Gi        10Gi
Swap:          2.0Gi          0B       2.0Gi
ncdu ~
ncdu 1.19 ~ Use the arrow keys to navigate, press ? for help
--- /home/shree ---------------------------------------
    1.1 GiB [##########] /projects
  210.4 MiB [#         ] /.cache
   48.2 MiB [          ] /Downloads
    8.0 KiB [          ]  notes.txt
 Total disk usage:   1.4 GiB  Apparent size:   1.3 GiB  Items: 18342

Worth remembering

CommandAnswers
df -hWhich drive is full?
du -sh *Which folder here is big?
du -h --max-depth=1 /Which top folder is big?
free -hHow much memory is left?
lsblkWhich disks are attached?
ncdu ~Where exactly did my space go?
14

System info

The first five commands to run on a machine you have never seen: what it is, how big, how busy, and what time it thinks it is.

unameos-releaseuptimenprocOps

Before you debug anything on a new server, learn the distribution and kernel, the number of CPUs, and the load. Two minutes here saves an hour of advice meant for a different system.

uname -r

The kernel version.

See the example
Kernel
cat /etc/os-release

The distribution and its release.

See the example
Distro
uptime

Time since boot and the load averages.

See the example
Busy
nproc

How many CPUs you can use.

See the example
Size
Check CPU temperature$ sensorsInstall: sudo apt install lm-sensors && sudo sensors-detect
In simple words

Prints the temperature of the CPU, its cores and other parts such as the SSD, next to the high and critical limits, so you can tell if the machine is overheating.

Under the hood

sensors comes from lm-sensors and reads hardware monitoring chips through the kernel hwmon interface in /sys/class/hwmon. sensors-detect probes for chips and loads the right kernel modules. Virtual machines and many cloud servers expose no sensors at all.

Try it$ watch -n 2 sensorsRefreshes the readings every two seconds while you run a heavy build.

Get full system details$ inxi -FxxxzInstall: sudo apt install inxi
In simple words

One command that prints a tidy report of the whole machine: operating system, CPU, graphics, memory, disks, network and drivers. The z hides private details, so the report is safe to paste into a forum or a ticket.

Under the hood

-F requests the full report, -xxx raises extra detail to level 3 (driver versions, bus IDs, chip IDs), and -z filters personal data such as serial numbers, MAC and IP addresses. inxi gathers this from /proc, /sys, lspci, dmidecode and similar sources.

Try it$ inxi -GxxOnly the graphics section, with driver details, when debugging a display problem.

sysinfo.shBASH
uname -r
grep PRETTY /etc/os-release
hostnamectl
uptime
nproc
date
sensors             # needs lm-sensors
inxi -Fxxxz         # needs inxi

Load means little without nproc. A load of 4 is saturated on 4 CPUs and relaxed on 16. Compare the three load numbers with nproc.

shree@devbox: ~Ubuntu 24.04
# a recorded session, replayed when you press Run
uname -r
6.8.0-45-generic
grep PRETTY /etc/os-release
PRETTY_NAME="Ubuntu 24.04.1 LTS"
uptime
 09:48:02 up 12 days,  3:41,  2 users,  load average: 0.42, 0.37, 0.31
nproc
8
date
Sat Oct  3 09:48:05 IST 2026
sensors
coretemp-isa-0000
Adapter: ISA adapter
Package id 0:  +52.0°C  (high = +100.0°C, crit = +100.0°C)
Core 0:        +49.0°C  (high = +100.0°C, crit = +100.0°C)
Core 1:        +51.0°C  (high = +100.0°C, crit = +100.0°C)
inxi -Fxxxz | head -12
System:
  Kernel: 6.8.0-45-generic arch: x86_64 bits: 64 compiler: gcc v: 13.2.0
  Desktop: GNOME v: 46.0 Distro: Ubuntu 24.04.1 LTS (Noble Numbat)
CPU:
  Info: quad core model: 11th Gen Intel Core i5-1135G7 bits: 64 type: MT MCP
Graphics:
  Device-1: Intel TigerLake-LP GT2 [Iris Xe Graphics] driver: i915 v: kernel
Drives:
  Local Storage: total: 80 GiB used: 41 GiB (51.3%)
Info:
  Memory: total: 16 GiB available: 15.36 GiB used: 5.21 GiB (33.9%)

Worth remembering

CommandTells you
uname -aKernel, architecture, host name
/etc/os-releaseDistribution name and version
hostnamectlHost, OS, kernel and hardware in one view
uptimeTime up and 1, 5, 15 minute load
lscpuCPU model, cores and threads
timedatectlTime zone and clock sync
sensorsCPU, core and drive temperatures
inxi -FxxxzA full hardware and driver report, private data hidden
15

Networking

Is the network up, is my service listening, and does the name resolve? Six commands narrow down most connection problems.

ippingcurlssdigOps

Work from the inside out. Check your own address, then whether a known host answers, then whether your service is listening, then whether DNS points where you expect.

ip a

Your interfaces and addresses.

See the example
Me
ping -c 3 host

Whether a host answers at all.

See the example
Reach
ss -tulpn

What is listening on which port.

See the example
Ports
curl -I url

Talks HTTP and shows the response.

See the example
HTTP
dig +short name

What a name resolves to.

See the example
DNS
Download files from the terminal$ wget URLBuilt in
In simple words

Downloads a file from a web address straight into the current folder, no browser needed. Handy on servers that have no screen at all.

Under the hood

wget is a non interactive HTTP, HTTPS and FTP client. It follows redirects, names the file after the URL, -c resumes a partial download, -O picks the output name, and -q silences progress. Unlike curl, it writes to a file by default.

Try it$ wget -c https://nodejs.org/dist/v22.11.0/node-v22.11.0-linux-x64.tar.xzDownloads the Node.js 22.11 archive and resumes if the connection drops.

network.shBASH
ip a
ping -c 3 1.1.1.1
curl -s localhost:3000/api/health
curl -I https://example.com
sudo ss -tulpn | grep 3000
dig +short api.example.com
wget https://nodejs.org/dist/v22.11.0/node-v22.11.0-linux-x64.tar.xz

0.0.0.0 versus 127.0.0.1. A service bound to 127.0.0.1 only answers from the same machine. Bind to 0.0.0.0 to accept outside traffic.

shree@devbox: ~Ubuntu 24.04
# a recorded session, replayed when you press Run
ping -c 3 1.1.1.1
64 bytes from 1.1.1.1: icmp_seq=1 ttl=57 time=6.12 ms
64 bytes from 1.1.1.1: icmp_seq=2 ttl=57 time=5.98 ms
64 bytes from 1.1.1.1: icmp_seq=3 ttl=57 time=6.40 ms
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
curl -s localhost:3000/api/health
{"status":"ok","uptime":4312}
sudo ss -tulpn | grep 3000
tcp LISTEN 0 511 0.0.0.0:3000 0.0.0.0:* users:(("node",pid=4910,fd=21))
dig +short api.example.com
203.0.113.24
wget https://nodejs.org/dist/v22.11.0/node-v22.11.0-linux-x64.tar.xz
HTTP request sent, awaiting response... 200 OK
Length: 29470212 (28M) [application/x-xz]
Saving to: ‘node-v22.11.0-linux-x64.tar.xz’
node-v22.11.0-linux 100%[===================>]  28.10M  9.80MB/s    in 2.9s
2026-10-03 10:05:15 (9.80 MB/s) - ‘node-v22.11.0-linux-x64.tar.xz’ saved [29470212/29470212]

Worth remembering

QuestionCommand
What is my address?ip a
Can I reach the internet?ping -c 3 1.1.1.1
Does the API answer?curl -i localhost:3000/health
What holds port 3000?sudo ss -tulpn | grep 3000
Where does this name point?dig +short name
Where is the hop failing?traceroute host
Download a file herewget URL
16

SSH and transfers

Log in to remote machines with keys instead of passwords, and move files there quickly and safely.

sshssh-keygenscprsyncOps

Make one key pair per laptop, copy the public half to each server, and keep the private half on your machine only. rsync sends just what changed, so it wins over scp for anything you deploy twice.

scpscp file host:/pathCopies everything, every time.
rsyncrsync -avz dir/ host:/pathCopies only what changed.
ssh.shBASH
ssh-keygen -t ed25519 -C "shree@devbox"
ssh-copy-id deploy@203.0.113.24
ssh deploy@203.0.113.24
ssh -L 5432:localhost:5432 deploy@203.0.113.24
scp build.tar.gz deploy@203.0.113.24:/srv/app/
rsync -avz --delete dist/ deploy@203.0.113.24:/srv/app/dist/

Mind the trailing slash in rsync. dist/ copies what is inside dist; dist copies the folder itself into the target.

shree@devbox: ~Ubuntu 24.04
# a recorded session, replayed when you press Run
ssh-keygen -t ed25519 -C "shree@devbox"
Generating public/private ed25519 key pair.
Your identification has been saved in /home/shree/.ssh/id_ed25519
Your public key has been saved in /home/shree/.ssh/id_ed25519.pub
rsync -avz --delete dist/ deploy@203.0.113.24:/srv/app/dist/
sending incremental file list
index.html
assets/app.js
sent 48,312 bytes  received 92 bytes  32,269.33 bytes/sec
total size is 182,440  speedup is 3.77

Worth remembering

CommandUse it to
ssh user@hostOpen a shell on a server
ssh -L 5432:localhost:5432Reach a remote database from your laptop
ssh-copy-id user@hostInstall your public key on a server
~/.ssh/configSave hosts as short names: ssh prod
rsync -avz --dry-runPreview a sync before it runs
17

Archives

Pack folders into one file for backups and transfers, then unpack them anywhere.

targzipzipDaily

tar bundles files and z squeezes the bundle with gzip. Remember the letters as create, extract and list, plus z for zip and f for the file name that follows.

tar -czf out.tar.gz dir

Create a compressed archive.

See the example
Pack
tar -tzf file

List what is inside without unpacking.

See the example
Peek
tar -xzf file -C dir

Extract into a chosen folder.

See the example
Unpack
zip -r out.zip dir

For people on Windows or macOS.

See the example
Share
archives.shBASH
tar -czf backup.tar.gz projects/
tar -tzf backup.tar.gz | head -3
mkdir -p /tmp/restore
tar -xzf backup.tar.gz -C /tmp/restore
gzip big.log            # becomes big.log.gz
zip -r site.zip public/
unzip site.zip -d site

Peek before you extract. An archive made from . spills every file into your current folder. tar -tzf shows its layout first.

shree@devbox: ~Ubuntu 24.04
# a recorded session, replayed when you press Run
tar -czf backup.tar.gz projects/
ls -lh backup.tar.gz
-rw-rw-r-- 1 shree shree 214M Oct  3 10:02 backup.tar.gz
tar -tzf backup.tar.gz | head -3
projects/
projects/api/
projects/api/package.json
tar -xzf backup.tar.gz -C /tmp/restore && ls /tmp/restore
projects

Worth remembering

LetterMeans
cCreate an archive
xExtract an archive
tList its contents
zgzip compression (.tar.gz)
Jxz compression (.tar.xz)
vPrint each file as it goes
fThe archive file name comes next
-C dirWork inside dir
18

Packages

Install, update and remove software through the package manager, so every tool arrives with its dependencies and security updates.

aptdnfdpkgOps

Ubuntu and Debian use apt. Run apt update first: it refreshes the list of what exists, and upgrade then installs the newer versions.

apt update

Refreshes the package lists.

See the example
Refresh
apt upgrade

Installs available updates.

See the example
Update
apt install pkg

Installs software and its dependencies.

See the example
Add
apt remove pkg

Uninstalls it; autoremove tidies leftovers.

See the example
Remove
packages.shBASH
sudo apt update
sudo apt upgrade -y
apt search ripgrep
sudo apt install -y ripgrep htop
apt show htop
sudo apt remove htop
sudo apt autoremove
dpkg -l | grep nginx

update is not upgrade. update only downloads the catalogue. Nothing on your system changes until upgrade or install.

shree@devbox: ~Ubuntu 24.04
# a recorded session, replayed when you press Run
sudo apt update
Hit:1 http://archive.ubuntu.com/ubuntu noble InRelease
Reading package lists... Done
12 packages can be upgraded. Run 'apt list --upgradable' to see them.
sudo apt install -y ripgrep
Setting up ripgrep (14.1.0-1) ...
rg --version | head -1
ripgrep 14.1.0

Worth remembering

TaskDebian, UbuntuFedora, RHELArch
Refresh listsapt updatednf check-updatepacman -Sy
Update allapt upgradednf upgradepacman -Syu
Installapt install pkgdnf install pkgpacman -S pkg
Removeapt remove pkgdnf remove pkgpacman -R pkg
Searchapt search worddnf search wordpacman -Ss word
19

Services and logs

systemd starts your app at boot, restarts it when it crashes, and keeps its logs. systemctl drives it and journalctl reads it.

systemctljournalctlProduction

Describe your app once in a unit file, then manage it like any other service. enable --now starts it immediately and on every boot.

systemctl status svc

Running or not, since when, and the last log lines.

See the example
Check
systemctl restart svc

Stops and starts it again.

See the example
Restart
systemctl enable --now

Starts it now and at every boot.

See the example
Boot
journalctl -u svc -f

Follows that service's logs live.

See the example
Logs
Debug system errors$ journalctl -xeBuilt in
In simple words

Opens the system log at the very end, where the newest messages are, and adds plain language notes explaining what went wrong. It is the first place to look after a service fails to start.

Under the hood

-e jumps to the end of the journal in the pager and -x adds explanation text from the message catalog (the lines starting with ░░). Narrow it with -u unit, -b for this boot, or -p err for errors only. Run with sudo to see every unit's messages.

Try it$ sudo journalctl -xeu myappThe latest log of one service, explained, right after systemctl reports it failed.

services.shBASH
systemctl status nginx
sudo systemctl restart nginx
sudo systemctl daemon-reload
sudo systemctl enable --now myapp
systemctl --failed
journalctl -u myapp -f
journalctl -u nginx --since "1 hour ago"
journalctl -p err -b
sudo journalctl -xe                       # latest logs, explained

daemon-reload after editing a unit. systemd caches unit files. Reload it, then restart the service, or your change is ignored.

shree@devbox: ~Ubuntu 24.04
# a recorded session, replayed when you press Run
systemctl status nginx
● nginx.service - A high performance web server and a reverse proxy server
     Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; preset: enabled)
     Active: active (running) since Sat 2026-10-03 08:01:12 IST; 1h 52min ago
journalctl -u myapp --since "10 min ago"
Oct 03 09:51:20 devbox node[4910]: listening on :3000
Oct 03 09:52:04 devbox node[4910]: GET /api/health 200 3ms
sudo journalctl -xeu myapp
Oct 03 09:58:12 devbox systemd[1]: myapp.service: Main process exited, code=exited, status=1/FAILURE
░░ Subject: Unit process exited
░░ Defined-By: systemd
░░ An ExecStart= process belonging to unit myapp.service has exited.
░░ The process' exit code is 'exited' and its exit status is 1.
Oct 03 09:58:12 devbox systemd[1]: myapp.service: Failed with result 'exit-code'.

The unit file behind myapp

/etc/systemd/system/myapp.serviceINI
[Unit]
Description=My Node API
After=network.target

[Service]
User=deploy
WorkingDirectory=/srv/app
ExecStart=/usr/bin/node server.js
Restart=on-failure
Environment=NODE_ENV=production

[Install]
WantedBy=multi-user.target

Restart=on-failure is the safety net. If the app crashes, systemd starts it again and logs why.

Worth remembering

CommandDoes
start / stop svcNow, this boot only
enable / disable svcAt boot, from now on
reload svcRereads config without a restart
is-active svcPrints active or inactive, good in scripts
journalctl -u svc -n 50The last 50 log lines
journalctl -p err -bOnly errors since this boot
journalctl -xeNewest messages, with explanations
20

Cron and your shell

Schedule jobs with cron, set environment variables, and tune your shell with aliases and history tricks.

crontabexportaliashistoryProduction

A cron line is five time fields, then the command. Cron runs with a bare environment, so use full paths and send output to a log, or failures vanish silently.

Reading 0 2 * * *

02***min · hour · day · month · weekday→2amA star means every value of that field.
shell.shBASH
crontab -e                 # edit your schedule
crontab -l
export NODE_ENV=production
env | grep NODE
echo "alias gs='git status'" >> ~/.bashrc
source ~/.bashrc
history | tail -3
sudo !!                    # rerun the last command with sudo

Test the command outside cron first. If it works in your shell but not in cron, the cause is almost always PATH or a relative path.

shree@devbox: ~Ubuntu 24.04
# a recorded session, replayed when you press Run
crontab -l
# m h dom mon dow command
0 2 * * * /home/shree/backup.sh >> /home/shree/backup.log 2>&1
*/15 * * * * /home/shree/cleanup.sh
export NODE_ENV=production
env | grep NODE
NODE_ENV=production
type gs
gs is aliased to `git status'

Worth remembering

ScheduleRuns
0 2 * * *Every day at 02:00
*/15 * * * *Every 15 minutes
0 9 * * 1-5Weekdays at 09:00
0 0 1 * *The first of every month
@rebootOnce, at boot

Which one do I need?

Start from the job you have, find the row, copy the command. Every one of them is covered in a module above.

I want toRun
Where am I?pwd
What is in here?ls -lah
Make nested foldersmkdir -p a/b/c
Copy a foldercp -r src dest
Rename a filemv old new
Watch a log livetail -f app.log
Find big filesfind / -type f -size +100M
Search codegrep -rn "text" src/
Save output and errorscmd > out.log 2>&1
Count the top valuessort | uniq -c | sort -rn
Replace text in a filesed -i 's/a/b/g' file
Make a script runnablechmod +x script.sh
Run one command as rootsudo cmd
Stop a processkill PID, then kill -9 PID
Keep work alive after logouttmux new -s name
Is the disk full?df -h
Which folder is big?du -sh * | sort -h
What is on port 3000?sudo ss -tulpn | grep 3000
Copy files to a serverrsync -avz dir/ user@host:/path
Back up a foldertar -czf backup.tar.gz dir/
Install softwaresudo apt install pkg
Restart a servicesudo systemctl restart svc
Read a service's logsjournalctl -u svc -f
Run something nightlycrontab -e
Browse disk usage interactivelyncdu ~
Watch CPU and memory livehtop
Check CPU temperaturesensors
Full hardware reportinxi -Fxxxz
Download a filewget URL
Why did a service fail?sudo journalctl -xe