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.
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.
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.
Prints the folder you are standing in.
See the exampleLists what is here, with hidden files, sizes and owners.
See the exampleMoves you. cd ~ goes home, cd - jumps back.
See the examplepwd
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.
# 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
| Flag | What it adds | Try |
|---|---|---|
-l | Long view: permissions, owner, size, date | ls -l |
-a | Hidden dot files | ls -a |
-h | Sizes as K, M, G | ls -lh |
-t | Newest first | ls -lt |
-R | Every subfolder too | ls -R src |
Files and folders
Make, copy, move, rename and delete. Five verbs cover most of the work you do on files.
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.
Makes folders, with every missing parent when you add -p.
See the exampleMakes an empty file, or bumps the date on one that exists.
See the exampleCopies a file. Add -r to copy a whole folder.
See the exampleMoves or renames in one step.
See the exampleDeletes for good. Add -i to be asked first.
See the exampleMakes a shortcut that points at another path.
See the examplemkdir -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.
# 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
| Command | Does | Safer with |
|---|---|---|
cp | Copies, overwriting quietly | -i asks before overwriting |
mv | Moves or renames, overwriting quietly | -i or -n never overwrites |
rm | Deletes with no undo | -i asks each time, -I once |
mkdir | Makes one folder | -p makes parents, no error if it exists |
ln | Links a name to a path | -s for a symbolic link |
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.
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.
Prints the whole file at once. Best under a screen long.
See the exampleScrolls a page at a time and searches with /.
See the exampleShows the first lines.
See the exampleShows the last lines and keeps following new ones.
See the examplecat 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.
# 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 less | Does |
|---|---|
Space / b | Next page / back a page |
/word | Search forward, n for the next match |
g / G | Jump to the start / the end |
F | Follow new lines like tail -f |
q | Quit |
Find files
find walks a folder tree and matches files by name, type, size or age, then can act on every match.
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.
Matches by name. -iname ignores case.
See the exampleMatches by size, handy for a full disk.
See the exampleMatches by age in days.
See the exampleShows which program runs when you type a name.
See the example$ find . -name "filename"Built inLooks 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".
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 . -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.
# 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
| Test | Matches |
|---|---|
-name "*.js" | Names by pattern, case sensitive |
-type f / -type d | Files only / folders only |
-size +100M | Bigger than 100 MB |
-mtime -1 | Changed in the last day |
-exec cmd {} + | Runs cmd on the matches |
-maxdepth 2 | Stops two levels down |
Search text
grep prints every line that matches a pattern. It is the fastest way into an unfamiliar codebase or a noisy log.
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.
Line numbers, so you can jump straight there.
See the exampleSearches every file under a folder.
See the exampleKeeps lines that do not match.
See the exampleCounts matching lines.
See the examplegrep "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.
# 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
| Flag | Means |
|---|---|
-i | Ignore case |
-n | Show line numbers |
-r | Search folders recursively |
-v | Invert: lines that do not match |
-w | Whole words only |
-l | Only the names of files that match |
-E | Extended regex: |, +, {n} |
-A / -B / -C n | n lines after / before / around |
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.
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 >.
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.
# 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
| Symbol | Sends |
|---|---|
a | b | Output of a into b |
> file | Output into file, replacing it |
>> file | Output onto the end of file |
< file | File into the command as input |
2> file | Errors into file |
> file 2>&1 | Output and errors into file |
| tee file | To the screen and a file at once |
| xargs cmd | Each line as arguments to cmd |
Slice text
Small tools that each do one thing to lines of text. Chained with pipes, they answer real questions about your logs.
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.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.
# 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
| Tool | Flags 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 |
sed and awk
sed edits lines as they stream past. awk treats every line as columns and can count, filter and add up.
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.
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.
# 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
| Piece | Means |
|---|---|
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 |
Permissions
Every file has an owner, a group and three sets of rights. Read them once and Permission denied stops being a mystery.
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.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.
# 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
| Number | Rights | Use it for |
|---|---|---|
755 | rwxr-xr-x | Scripts and public folders |
644 | rw-r--r-- | Ordinary files |
640 | rw-r----- | Config the group may read |
600 | rw------- | Private keys and secrets |
700 | rwx------ | Your own private folder |
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.
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.
Your user name.
See the exampleYour user id and every group you belong to.
See the exampleRuns one command as root.
See the exampleAdds a user to a group.
See the examplewhoami
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.
# 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
| Command | Does |
|---|---|
sudo cmd | Run one command as root |
sudo -u name cmd | Run as another user |
sudo -i | Open a root shell; exit to leave |
su - name | Switch to another user's login |
adduser name | Create a user with a home folder |
usermod -aG g name | Add name to group g |
groups name | List a user's groups |
Processes
Every running program is a process with a number. Find it, watch it, and stop it politely before you stop it hard.
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.
A snapshot of every process.
See the exampleA live view sorted by CPU. htop is friendlier if installed.
See the exampleFinds process ids by name.
See the exampleAsks a process to stop. -9 forces it.
See the example$ htopInstall: sudo apt install htopA 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.
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.
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.
# 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
| Signal | Number | Means |
|---|---|---|
TERM | 15 | Please finish up; the default |
INT | 2 | What Ctrl+C sends |
HUP | 1 | Reload config, for many daemons |
KILL | 9 | Stop now; cannot be caught |
STOP / CONT | 19 / 18 | Pause and resume |
Jobs and sessions
Run things in the background, bring them back, and keep them alive after you close the laptop.
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.
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.
# 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 command | Does |
|---|---|
Ctrl+C | Stop the foreground job |
Ctrl+Z | Pause it |
jobs | List this shell's jobs |
fg / bg | Bring to front / resume behind |
disown | Let a job outlive the shell |
tmux attach | Return to a detached session |
Disk and memory
Answer the two questions behind most outages: is the disk full, and where did the memory go.
df reports whole drives; du measures folders. Start with df to find the full drive, then du to find what filled it.
Free space per mounted drive.
See the exampleSize of each item in this folder.
See the exampleMemory and swap in use.
See the exampleDisks and their partitions as a tree.
See the example$ du -sh ~/* | sort -hBuilt inMeasures every file and folder in your home folder and lists them smallest to largest, so the biggest space users end up at the bottom.
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.
$ ncdu ~Install: sudo apt install ncduScans 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.
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.
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.
# 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
| Command | Answers |
|---|---|
df -h | Which drive is full? |
du -sh * | Which folder here is big? |
du -h --max-depth=1 / | Which top folder is big? |
free -h | How much memory is left? |
lsblk | Which disks are attached? |
ncdu ~ | Where exactly did my space go? |
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.
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.
The kernel version.
See the exampleThe distribution and its release.
See the exampleTime since boot and the load averages.
See the exampleHow many CPUs you can use.
See the example$ sensorsInstall: sudo apt install lm-sensors && sudo sensors-detectPrints 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.
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.
$ inxi -FxxxzInstall: sudo apt install inxiOne 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.
-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.
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.
# 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
| Command | Tells you |
|---|---|
uname -a | Kernel, architecture, host name |
/etc/os-release | Distribution name and version |
hostnamectl | Host, OS, kernel and hardware in one view |
uptime | Time up and 1, 5, 15 minute load |
lscpu | CPU model, cores and threads |
timedatectl | Time zone and clock sync |
sensors | CPU, core and drive temperatures |
inxi -Fxxxz | A full hardware and driver report, private data hidden |
Networking
Is the network up, is my service listening, and does the name resolve? Six commands narrow down most connection problems.
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.
Your interfaces and addresses.
See the exampleWhether a host answers at all.
See the exampleWhat is listening on which port.
See the exampleTalks HTTP and shows the response.
See the exampleWhat a name resolves to.
See the example$ wget URLBuilt inDownloads a file from a web address straight into the current folder, no browser needed. Handy on servers that have no screen at all.
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.
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.
# 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
| Question | Command |
|---|---|
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 here | wget URL |
SSH and transfers
Log in to remote machines with keys instead of passwords, and move files there quickly and safely.
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.
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.
# 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
| Command | Use it to |
|---|---|
ssh user@host | Open a shell on a server |
ssh -L 5432:localhost:5432 | Reach a remote database from your laptop |
ssh-copy-id user@host | Install your public key on a server |
~/.ssh/config | Save hosts as short names: ssh prod |
rsync -avz --dry-run | Preview a sync before it runs |
Archives
Pack folders into one file for backups and transfers, then unpack them anywhere.
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.
Create a compressed archive.
See the exampleList what is inside without unpacking.
See the exampleExtract into a chosen folder.
See the exampleFor people on Windows or macOS.
See the exampletar -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.
# 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
| Letter | Means |
|---|---|
c | Create an archive |
x | Extract an archive |
t | List its contents |
z | gzip compression (.tar.gz) |
J | xz compression (.tar.xz) |
v | Print each file as it goes |
f | The archive file name comes next |
-C dir | Work inside dir |
Packages
Install, update and remove software through the package manager, so every tool arrives with its dependencies and security updates.
Ubuntu and Debian use apt. Run apt update first: it refreshes the list of what exists, and upgrade then installs the newer versions.
Refreshes the package lists.
See the exampleInstalls available updates.
See the exampleInstalls software and its dependencies.
See the exampleUninstalls it; autoremove tidies leftovers.
See the examplesudo 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.
# 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
| Task | Debian, Ubuntu | Fedora, RHEL | Arch |
|---|---|---|---|
Refresh lists | apt update | dnf check-update | pacman -Sy |
Update all | apt upgrade | dnf upgrade | pacman -Syu |
Install | apt install pkg | dnf install pkg | pacman -S pkg |
Remove | apt remove pkg | dnf remove pkg | pacman -R pkg |
Search | apt search word | dnf search word | pacman -Ss word |
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.
Describe your app once in a unit file, then manage it like any other service. enable --now starts it immediately and on every boot.
Running or not, since when, and the last log lines.
See the exampleStops and starts it again.
See the exampleStarts it now and at every boot.
See the exampleFollows that service's logs live.
See the example$ journalctl -xeBuilt inOpens 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.
-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.
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.
# 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
[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
| Command | Does |
|---|---|
start / stop svc | Now, this boot only |
enable / disable svc | At boot, from now on |
reload svc | Rereads config without a restart |
is-active svc | Prints active or inactive, good in scripts |
journalctl -u svc -n 50 | The last 50 log lines |
journalctl -p err -b | Only errors since this boot |
journalctl -xe | Newest messages, with explanations |
Cron and your shell
Schedule jobs with cron, set environment variables, and tune your shell with aliases and history tricks.
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.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.
# 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
| Schedule | Runs |
|---|---|
0 2 * * * | Every day at 02:00 |
*/15 * * * * | Every 15 minutes |
0 9 * * 1-5 | Weekdays at 09:00 |
0 0 1 * * | The first of every month |
@reboot | Once, 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 to | Run |
|---|---|
| Where am I? | pwd |
| What is in here? | ls -lah |
| Make nested folders | mkdir -p a/b/c |
| Copy a folder | cp -r src dest |
| Rename a file | mv old new |
| Watch a log live | tail -f app.log |
| Find big files | find / -type f -size +100M |
| Search code | grep -rn "text" src/ |
| Save output and errors | cmd > out.log 2>&1 |
| Count the top values | sort | uniq -c | sort -rn |
| Replace text in a file | sed -i 's/a/b/g' file |
| Make a script runnable | chmod +x script.sh |
| Run one command as root | sudo cmd |
| Stop a process | kill PID, then kill -9 PID |
| Keep work alive after logout | tmux 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 server | rsync -avz dir/ user@host:/path |
| Back up a folder | tar -czf backup.tar.gz dir/ |
| Install software | sudo apt install pkg |
| Restart a service | sudo systemctl restart svc |
| Read a service's logs | journalctl -u svc -f |
| Run something nightly | crontab -e |
| Browse disk usage interactively | ncdu ~ |
| Watch CPU and memory live | htop |
| Check CPU temperature | sensors |
| Full hardware report | inxi -Fxxxz |
| Download a file | wget URL |
| Why did a service fail? | sudo journalctl -xe |