Cheat sheet

Everything from the lessons on one page. Rows marked “same on both” work everywhere. Use the Show switch at the top to hide the family you don't use.

Where am I & what’s here

TaskRocky / RHELUbuntu / Debian
Print current folderpwd same on both
List filesls same on both
List with details / hidden filesls -l ls -a ls -la same on both
Shortcut “ll”ls -lls -alF
Which distro is this?cat /etc/os-release same on both
Who am I? / my groupswhoami id same on both
Machine name / IP addresshostname hostname -I same on both

Moving around

TaskRocky / RHELUbuntu / Debian
Go into a foldercd Documents same on both
Up one level / home / backcd .. cd cd - same on both
Absolute pathcd /var/log same on both

Files & folders

TaskRocky / RHELUbuntu / Debian
Make folder / nested foldersmkdir NAME mkdir -p a/b/c same on both
Make empty filetouch FILE same on both
Write / append textecho "hi" > FILE echo "hi" >> FILE same on both
Read a filecat FILE less FILE head FILE tail FILE same on both
Copy / copy foldercp A B cp -r DIR1 DIR2 same on both
Move / renamemv OLD NEW same on both
Delete file / folderrm FILE rm -r DIR rmdir EMPTYDIR same on both
Friendly text editorsudo dnf install nano → nano FILEnano FILE (pre-installed)
Write to a root-owned fileecho "hi" | sudo tee /etc/FILE same on both
Search inside textgrep WORD FILE COMMAND | grep WORD same on both
File details / typestat FILE file FILE same on both

The filesystem

TaskRocky / RHELUbuntu / Debian
Settings / logs & data / programs/etc /var /usr/bin same on both
Your own scripts (everyone)/usr/local/bin (never /usr/bin) same on both
Old folders merged into /usr/bin → usr/bin /sbin → usr/sbin /lib → usr/lib same on both
Kernel & bootloader/boot/vmlinuz-* /boot/grub2//boot/vmlinuz-* /boot/grub/
Extra service settings/etc/sysconfig//etc/default/
Installed-package database/var/lib/rpm//var/lib/dpkg/
Repo list/etc/yum.repos.d//etc/apt/sources.list.d/
Kernel info (virtual)cat /proc/cpuinfo /proc/meminfo /proc/1/comm /sys/class/net/ same on both
Devicesls -l /dev (b = block/disk, c = character) /dev/null same on both
sudo finds /usr/local/bin?No: use the full pathYes
The official mapman hier same on both

Installing software

TaskRocky / RHELUbuntu / Debian
Refresh package list(automatic) dnf check-updatesudo apt update
Installsudo dnf install NAMEsudo apt install NAME
Removesudo dnf remove NAMEsudo apt remove NAME
Update everythingsudo dnf upgradesudo apt update && sudo apt upgrade
Searchdnf search WORDapt search WORD
Package detailsdnf info NAMEapt show NAME
List installeddnf list installed rpm -qaapt list --installed dpkg -l
Extra repositorysudo dnf install epel-release(universe is on by default)
Which package gives a command?dnf provides '*/bin/NAME'apt-file search bin/NAME
Which package owns a file?rpm -qf /usr/bin/lsdpkg -S /usr/bin/ls
Version & repo / dependenciesdnf info NAME dnf deplist NAMEapt-cache policy NAME apt-cache depends NAME
Package file type.rpm.deb
Old command nameyumapt-get

Users & sudo

TaskRocky / RHELUbuntu / Debian
Run one command as rootsudo COMMAND same on both
Admin groupwheelsudo
Add a usersudo useradd NAME → sudo passwd NAMEsudo adduser NAME
Make a user an adminsudo usermod -aG wheel NAMEsudo usermod -aG sudo NAME
Change your passwordpasswd same on both
Root shell (then exit!)sudo -i sudo su - same on both
Switch usersu - NAME (the dash = full login) same on both
Is root locked?sudo passwd -S root → LKsudo passwd -S root → L (always)
Who is logged in?who w last same on both

vim

TaskRocky / RHELUbuntu / Debian
Get itsudo dnf install vim-enhanced (vi is always there)vim is pre-installed
Modesi a o A → insert Esc → normal v V → visual : → command line same on both
Save & quit:w :q :wq (ZZ) :q! (discard) same on both
Moveh j k l w b e 0 ^ $ gg G :42 Ctrl+D / Ctrl+U same on both
Editx dd dw cw ciw D yy p P u Ctrl+R . 3dd same on both
Search & replace/word n N * :%s/old/new/g :g/DEBUG/d :v/ERROR/d same on both
Settings:set number ~/.vimrc vimtutor same on both
Default editorecho 'export EDITOR=vim' >> ~/.bashrc sudoedit /etc/FILE same on both

Wildcards & braces

TaskRocky / RHELUbuntu / Debian
Match* anything ? one character [abc] [0-9] [!x] one of / not same on both
Make listsmkdir -p proj/{src,docs} touch f{1..5}.txt cp a.conf{,.bak} same on both
Look firstecho rm *.log → rm *.log same on both
Quote to stop itfind . -name '*.png' echo '*' same on both

Jobs

TaskRocky / RHELUbuntu / Debian
Stop / pauseCtrl+C stop Ctrl+Z pause same on both
Backgroundcmd & bg jobs [-l] same on both
Foregroundfg fg %2 same on both
End a jobkill %1 kill PID same on both
Survive logoutnohup cmd & disown %1 (forever? use a systemd service) same on both

Your hardware

TaskRocky / RHELUbuntu / Debian
Linux & kernelcat /etc/os-release uname -r hostnamectl same on both
CPU & memorylscpu nproc free -h /proc/cpuinfo /proc/meminfo same on both
Diskslsblk (devices) df -h (space) same on both
Devicessudo dnf install pciutils usbutils → lspci lsusblspci lspci -k lsusb
Model & VM?sudo dmidecode -s system-product-name systemd-detect-virt same on both

Archives & compression

TaskRocky / RHELUbuntu / Debian
Compress one filegzip -k F xz -k F bzip2 -k F → gunzip / unxz / bunzip2 same on both
Read without unpackingzcat xzcat bzcat same on both
tartar -czf a.tar.gz DIR tar -cJf a.tar.xz DIR tar -tf a.tar.gz tar -xf a.tar.gz -C DIR same on both
zipsudo dnf install zip unzip → zip -r a.zip DIR unzip -l a.zip unzip a.zip -d DIRsudo apt install zip unzip → zip -r a.zip DIR unzip -l a.zip unzip a.zip -d DIR
What is it?file NAME same on both

Updates

TaskRocky / RHELUbuntu / Debian
What’s waitingdnf check-update dnf updateinfo list --securitysudo apt update apt list --upgradable
Security onlysudo dnf upgrade --securitysudo unattended-upgrade -v
Reboot needed?dnf needs-restarting -r sudo dnf needs-restarting -sls /var/run/reboot-required cat /var/run/reboot-required.pkgs
Kernelsrpm -q kerneldpkg -l 'linux-image*'
Automaticsudo dnf install dnf-automatic /etc/dnf/automatic.conf sudo systemctl enable --now dnf-automatic.timer/etc/apt/apt.conf.d/20auto-upgrades 50unattended-upgrades

More ways to install

TaskRocky / RHELUbuntu / Debian
Vendor reposudo dnf config-manager --add-repo URL.repo → sudo dnf install PKGcurl -fsSL KEY | sudo gpg --dearmor -o /usr/share/keyrings/X.gpg deb [signed-by=…] URL noble main → sudo apt update
SnapEPEL: sudo dnf install snapd sudo systemctl enable --now snapd.socketsnap find X sudo snap install X snap list sudo snap remove X
Flatpaksudo flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo flatpak install flathub ID flatpak list same on both
AppImagechmod u+x X.AppImage ./X.AppImage (needs fuse-libs)chmod u+x X.AppImage ./X.AppImage (needs libfuse2t64)

SELinux & AppArmor

TaskRocky / RHELUbuntu / Debian
Is it on?getenforce sestatussudo aa-status
Labels / profilesls -Z ps -eZ id -Zls /etc/apparmor.d ps auxZ
What was blockedsudo ausearch -m avc -ts recentsudo journalctl -k | grep DENIED
Test brieflysudo setenforce 0 … sudo setenforce 1sudo aa-complain PROG … sudo aa-enforce PROG
Fix for realsudo semanage fcontext -a -t TYPE "/path(/.*)?" sudo restorecon -Rv /path semanage port -a setsebool -Pedit /etc/apparmor.d/PROFILE sudo apparmor_parser -r PROFILE

Networking

TaskRocky / RHELUbuntu / Debian
My IP addressesip a ip -br a (old: ifconfig · Windows: ipconfig) same on both
Gateway / routesip route (old: route -n) same on both
Neighbours on the LANip neigh (old: arp -a) same on both
Listening portsss -tlnp (old: netstat -tlnp) same on both
Get ifconfig/route/netstatsudo dnf install net-toolssudo apt install net-tools
Test the pathping -c 3 GATEWAY ping -c 3 8.8.8.8 ping -c 3 google.com tracepath 8.8.8.8 same on both
DNS servers in usecat /etc/resolv.confresolvectl status (resolv.conf says 127.0.0.53)
DNS lookup toolssudo dnf install bind-utils → dig NAMEdig NAME (pre-installed)
Local name overrides/etc/hosts getent hosts NAME same on both
Permanent configNetworkManager: nmcli con mod … → nmcli con up … (or nmtui)Netplan: /etc/netplan/*.yaml → sudo netplan try
Config files/etc/NetworkManager/system-connections/*.nmconnection/etc/netplan/*.yaml (spaces, not tabs; chmod 600)
Temporary changessudo ip addr add IP/24 dev CARD sudo ip route add default via GW same on both
Rename the machinesudo hostnamectl set-hostname NAME same on both

Remote access (SSH)

TaskRocky / RHELUbuntu / Debian
Connect / disconnectssh USER@HOST exit same on both
Make a key pairssh-keygen -t ed25519 same on both
Copy key to serverssh-copy-id USER@HOST same on both
Copy key without ssh-copy-idcat ~/.ssh/id_ed25519.pub | ssh USER@HOST "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys" same on both
Key permissions (server)chmod 700 ~/.ssh chmod 600 ~/.ssh/authorized_keys same on both
Loose key permissionsrefused (see /var/log/secure)group-write allowed if the group is yours
Nickname a server~/.ssh/config: Host NAME / HostName IP / User USER → ssh NAME same on both
AppsWindows Terminal · PuTTY (+PuTTYgen, .ppk keys) · MobaXterm · Mac Terminal · iTerm2 · Termius same on both
PuTTY copy / pasteselect = copy right-click = paste same on both
Web consoleCockpit: sudo systemctl enable --now cockpit.socket → https://IP:9090sudo apt install cockpit → https://IP:9090
Install SSH serversudo dnf install openssh-serversudo apt install openssh-server
SSH service namesshdssh

Services

TaskRocky / RHELUbuntu / Debian
Status / start / stop / restartsystemctl status NAME sudo systemctl start|stop|restart NAME same on both
Start now + at every bootsudo systemctl enable --now NAME same on both
Auto-starts after install?NoYes
Everything in the logsjournalctl -u NAME journalctl -f same on both
PID 1 (the init system)systemd on both ps -p 1 -o comm same on both
Old-style commandsservice NAME restart (“Redirecting to /bin/systemctl…”)service NAME restart /etc/init.d/NAME restart
/etc/init.djust a READMEcompatibility scripts (“via systemctl”)
Old boot on/off toolchkconfig NAME onupdate-rc.d NAME enable
Read / edit a unitsystemctl cat NAME sudo systemctl edit NAME (drop-in) same on both
Unit file folders/usr/lib/systemd/system (packages) /etc/systemd/system (yours, wins) same on both
After changing a unit filesudo systemctl daemon-reload same on both
Block / unblock a servicesudo systemctl mask NAME sudo systemctl unmask NAME same on both
Installed units & statesystemctl list-unit-files systemctl --failed same on both
Runlevel → targetrunlevel systemctl get-default 3 = multi-user.target 5 = graphical.target same on both
Custom service script location/usr/local/bin (SELinux blocks /home)/usr/local/bin
Kernel settings (not systemctl!)sysctl KEY sudo sysctl -w KEY=VAL /etc/sysctl.d/*.conf sudo sysctl --system same on both

Web server (Apache)

TaskRocky / RHELUbuntu / Debian
Package & servicehttpdapache2
Installsudo dnf install httpdsudo apt install apache2
Config folder/etc/httpd//etc/apache2/
Logs/var/log/httpd//var/log/apache2/
Runs as userapachewww-data
Web pages go in/var/www/html/ same on both
Test itcurl localhost same on both

Firewall

TaskRocky / RHELUbuntu / Debian
Toolfirewalld (firewall-cmd)ufw
On by default?Yes (SSH allowed)No
Show rulessudo firewall-cmd --list-servicessudo ufw status
Allow web trafficsudo firewall-cmd --permanent --add-service=http → sudo firewall-cmd --reloadsudo ufw allow 'Apache'
Allow SSH(already allowed)sudo ufw allow OpenSSH ← before enabling!
Turn onsudo systemctl enable --now firewalldsudo ufw enable
Security guardSELinuxAppArmor
Low-level rulessudo nft list rulesetsudo iptables -L -n
iptables commandsudo dnf install iptables-nft(pre-installed)

Network & security tools

TaskRocky / RHELUbuntu / Debian
Fetch a page / headerscurl URL curl -I URL curl -O URL same on both
Download a filesudo dnf install wget → wget URLwget URL
Is a port open?sudo dnf install nmap-ncat → nc -zv HOST PORTnc -zv HOST PORT
What is listening?ss -tlnp sudo lsof -i :PORT same on both
Watch packetssudo dnf install tcpdump → sudo tcpdump -i enp0s3 -c 5 icmpsudo tcpdump -i enp0s3 -c 5 icmp
Random secretopenssl rand -base64 24 same on both
Self-signed certificateopenssl req -x509 -newkey rsa:2048 -nodes -keyout key.pem -out cert.pem -days 30 -subj "/CN=NAME" same on both
Check a certificateopenssl x509 -in cert.pem -noout -dates openssl s_client -connect HOST:443 same on both
Checksumsha256sum FILE sha256sum -c SHA256SUMS same on both
My public IPcurl ifconfig.me same on both

Logs

TaskRocky / RHELUbuntu / Debian
Read the journaljournalctl -u NAME -n 50 journalctl -p err journalctl -xeu NAME same on both
Who can read log filesroot (use sudo)root + adm group
System messages/var/log/messages/var/log/syslog
Logins & sudo/var/log/secure/var/log/auth.log
Package history/var/log/dnf.log/var/log/apt/history.log
Watch a log livesudo tail -f FILE (Ctrl+C to stop) same on both
Kernel messagesdmesg -T journalctl -ksudo dmesg -T journalctl -k

Text power tools

TaskRocky / RHELUbuntu / Debian
Find linesgrep -i -n -c -v -r -E "a|b" same on both
Countwc -l FILE same on both
Sort / unique / countsort sort -n sort -rn sort | uniq -c same on both
Columnscut -d, -f1,3 FILE awk -F, '{print $1}' FILE same on both
Filter by columnawk -F, '$3 > 85 {print $1}' FILE same on both
awk flavorgawk (awk --version)mawk (awk -W version)
Find & replacesed 's/old/new/g' FILE (then -i to save) same on both
Swap characterstr a-z A-Z tr , '\n' tr -d CHARS same on both
Show and saveCOMMAND | tee FILE same on both
List → argumentsfind … | xargs COMMAND same on both
Compare filesdiff OLD NEW same on both

Permissions

TaskRocky / RHELUbuntu / Debian
Read permissionsls -l stat FILE same on both
Make a script runnablechmod +x script.sh same on both
Private file / normal file / folderchmod 600 FILE chmod 644 FILE chmod 755 DIR same on both
Change owner / groupsudo chown USER:GROUP FILE sudo chgrp GROUP FILE same on both
Default permissionsumask same on both
Home folder default700 (drwx------)750 (drwxr-x---)
/etc/shadow000, root only640, root:shadow
Extra security layerSELinux (ls -Z)AppArmor

Users & groups

TaskRocky / RHELUbuntu / Debian
Create a usersudo useradd NAME → sudo passwd NAMEsudo adduser NAME
useradd makes a home folder?YesOnly with -m
Add to a group (keep others!)sudo usermod -aG GROUP USER sudo gpasswd -a USER GROUP same on both
New groupsudo groupadd NAME same on both
Lock / delete usersudo usermod -L USER sudo userdel -r USER same on both
Look upid USER getent passwd USER who last same on both

Cron (scheduling)

TaskRocky / RHELUbuntu / Debian
Package / servicecronie / crondcron / cron
Edit / list / remove your jobscrontab -e crontab -l crontab -r (no undo!) same on both
Time fieldsminute hour day-of-month month day-of-week command same on both
Every 5 min / 2:30am weekdays*/5 * * * * 30 2 * * 1-5 same on both
System jobs (with a user field)/etc/crontab /etc/cron.d/NAME same on both
Drop-in folders/etc/cron.hourly cron.daily cron.weekly cron.monthly same on both
Default crontab editorvi (EDITOR=nano crontab -e)asks: 1 = nano
cron.d file names with a dotworkIGNORED
Cron log/var/log/crongrep CRON /var/log/syslog
In a crontab, write % as\% (or use a script) same on both

Processes & disk

TaskRocky / RHELUbuntu / Debian
List processesps aux pgrep -l NAME top htop same on both
htopEPEL: sudo dnf install htoppre-installed
Stop a processkill PID kill -9 PID pkill NAME same on both
Load / cores / memoryuptime nproc free -h same on both
Disk full?df -h sudo du -h -d 1 / | sort -h sudo find / -size +1G same on both
Filesystem typexfsext4
Empty a big log safelysudo truncate -s 0 FILE same on both
Deleted but still full?sudo lsof +L1 → restart / stop that program same on both
Stop by exact namekillall NAME same on both

Disks & mounting

TaskRocky / RHELUbuntu / Debian
See diskslsblk sudo fdisk -l df -hT same on both
Partition a disksudo fdisk /dev/sdb (n … w) same on both
Formatsudo mkfs.xfs /dev/sdb1sudo mkfs.ext4 /dev/sdb1
Mount / unmountsudo mount /dev/sdb1 /data sudo umount /data same on both
“target is busy”cd ~ lsof /data same on both
Disk ID for fstabsudo blkid /dev/sdb1 same on both
Mount at bootUUID=… /data TYPE defaults 0 0 in /etc/fstab same on both
Test fstab (always!)sudo systemctl daemon-reload sudo mount -a findmnt /data same on both

Backups

TaskRocky / RHELUbuntu / Debian
Create / list / extract archivetar -czf A.tar.gz DIR tar -tzf A.tar.gz tar -xzf A.tar.gz -C DEST same on both
Date in a file namebackup-$(date +%F).tar.gz same on both
Mirror a folderrsync -av SRC/ DEST/ (dry run: -n) same on both
Copy to a serverrsync -av DIR user@host:path/ scp FILE user@host: same on both
Install rsyncsudo dnf install rsync (if missing)(pre-installed)
Compress one filegzip FILE gunzip FILE.gz zcat FILE.gz same on both
Zip filessudo dnf install zip unzipsudo apt install zip unzip
Make / list / extract zipzip -r A.zip DIR unzip -l A.zip unzip A.zip -d DEST same on both
Verify backupssha256sum *.tar.gz > SHA256SUMS sha256sum -c SHA256SUMS same on both

Your company’s app

TaskRocky / RHELUbuntu / Debian
Program / settings / data/opt/cht/ourapp /etc/opt/cht/ourapp /var/opt/cht/ourapp same on both
Service accountsudo useradd --system --shell /sbin/nologin ourappsudo adduser --system --group ourapp
Ownersprogram root:root 755 config root:ourapp 640 data ourapp:ourapp 750 same on both
Switch versionssudo ln -sfn /opt/cht/ourapp/releases/ourapp-X /opt/cht/ourapp/current → sudo systemctl restart ourapp same on both
Test as the app usersudo -u ourapp /opt/cht/ourapp/current/bin/ourapp --check-configsudo -u ourapp ourapp --check-config
SELinux labelsls -Z sudo ausearch -m avc -ts recent sudo restorecon -Rv /opt/cht(AppArmor: no file labels, ls -Z shows ?)
Open the portsudo firewall-cmd --permanent --add-port=8080/tcp → --reloadsudo ufw allow 8080/tcp

Shell skills

TaskRocky / RHELUbuntu / Debian
Repeat last command (as root)!! sudo !! same on both
Search historyhistory | grep WORD Ctrl+R same on both
VariablesNAME=value echo "$NAME" export NAME env same on both
Shortcutsalias NAME='COMMAND' (keep it: add to ~/.bashrc) same on both
Rerun every 2 swatch -n 2 COMMAND same on both
Session that survives disconnectssudo dnf install tmux → tmux new -s NAMEtmux new -s NAME
Detach / reattachCtrl+B then D tmux ls tmux attach -t NAME same on both
screenEPEL only: sudo dnf install screenscreen -S NAME Ctrl+A then D screen -r
Time zonetimedatectl sudo timedatectl set-timezone America/Chicago same on both
Clock sync servicechronydsystemd-timesyncd
Rename the serversudo hostnamectl set-hostname NAMEsudo hostnamectl set-hostname NAME (+ fix 127.0.1.1 in /etc/hosts)

Capturing output

TaskRocky / RHELUbuntu / Debian
Save results / add to a filecmd > file cmd >> file same on both
Errors only / both / silencecmd 2> err.txt cmd > all.txt 2>&1 cmd >/dev/null 2>&1 same on both
Order matters> file 2>&1 ✔ 2>&1 > file ✘ (errors still on screen) same on both
See it and save itcmd | tee file cmd 2>&1 | tee -a log same on both
Write a whole filecat > file <<EOF … EOF <<'EOF' = no $ expansion same on both
Error message from a scriptecho "oops" >&2 same on both
Record a sessionscript session.log … exit same on both
To the system loglogger -t TAG "msg" → /var/log/messageslogger -t TAG "msg" → /var/log/syslog
Log a whole scriptexec >> /path/script.log 2>&1 (near the top) same on both

Substitution & subshells

TaskRocky / RHELUbuntu / Debian
Insert a command’s outputecho "Today: $(date +%A)" old style: `date +%A` same on both
Save output in a variablen=$(ls | wc -l) x=$(cmd 2>&1) (include errors) same on both
Keep lines & spaces"$(cmd)" (quote it!) same on both
Math$(( 6 * 7 )) $(( n + 1 )) $(( 7 % 2 )) same on both
Variable tricks${f##*/} ${f%/*} ${f%.txt} ${#f} ${X:-default} same on both
Do it “over there”(cd /var/log && ls) (subshell: you don’t move) same on both
Group outputs{ date; uptime; } > report.txt same on both
Output as a filediff <(ls dir1) <(ls dir2) same on both
/bin/sh is…bash (everything works)dash (no <( ), [[ ]], ${x//a/b})
Force bash#!/bin/bash bash -c '…' SHELL=/bin/bash in crontab same on both

Shell scripting

TaskRocky / RHELUbuntu / Debian
First line#!/bin/bash (then chmod +x script.sh) same on both
Arguments$1 $2 … $# "$@" shift ${1:-default} same on both
Tests[ -f f ] [ -d d ] [ -z "$s" ] [ "$a" = "$b" ] [ "$n" -gt 5 ] same on both
Decideif …; then …; elif …; else …; fi cmd && ok || fail same on both
Choosecase "$x" in a|b) … ;; *) … ;; esac same on both
Loopsfor x in a b c; do …; done while [ … ]; do …; done same on both
File line by linewhile IFS=: read -r a b rest; do …; done < file same on both
Functionsname() { local x="$1"; …; return 0; } same on both
Debug / strictbash -x script.sh set -euo pipefail same on both
Both families in one script. /etc/os-release; case "$ID" in rocky|rhel) dnf … ;; ubuntu|debian) apt … ;; esac same on both

POSIX & portability

TaskRocky / RHELUbuntu / Debian
/bin/sh is…bash (lenient)dash (strict)
Instead of [[ ]] / == / source[ ] · = · . same on both
Instead of echo -e / -nprintf 'text\n' same on both
Instead of {1..5}for i in 1 2 3 4 5 $(seq 5) same on both
Instead of &>> file 2>&1 same on both
Check a scriptsudo dnf install ShellCheck (EPEL) → shellcheck s.shsudo apt install shellcheck → shellcheck s.sh checkbashisms s.sh
POSIX version / limitsgetconf _POSIX_VERSION getconf ARG_MAX same on both

Distro cousins

TaskRocky / RHELUbuntu / Debian
Rocky’s twinAlmaLinux: same commands, ID="almalinux" same on both
Amazon Linux 2023Red Hat family (from Fedora): dnf, ec2-user, no EPEL, no firewalld, no cron by default same on both
Which family?grep -E '^(ID|ID_LIKE)=' /etc/os-release (check ID_LIKE) same on both
Container imagesrockylinux/rockylinux:9 · almalinux:9 · amazonlinux:2023 · ubuntu:24.04 · debian:13 · alpine same on both
Alpineapk add PKG no bash (BusyBox sh) same on both

Alpine & containers

TaskRocky / RHELUbuntu / Debian
Get podmansudo dnf install podmansudo apt install podman (or docker.io, with sudo)
Run Alpinepodman run -it docker.io/library/alpine:3.22 (exit to leave) same on both
One command, then clean uppodman run --rm IMAGE COMMAND same on both
Containers / imagespodman ps -a podman images podman rm NAME same on both
Alpine packagesapk update apk add PKG apk del PKG apk search WORD apk add --no-cache PKG same on both
Alpine basicsshell = ash (apk add bash) tools = BusyBox libc = musl services = OpenRC same on both
OpenRC (real Alpine)rc-service NAME start rc-update add NAME default rc-status same on both

Build & ship images (DevOps)

TaskRocky / RHELUbuntu / Debian
Buildpodman build -t NAME:TAG . (the dot = build context) sudo docker build -t NAME . same on both
ContainerfileFROM image:tag RUN cmd COPY src dest WORKDIR dir ENV K=V EXPOSE port CMD ["prog", "arg"] same on both
Run with a portpodman run -d --name web -p HOST:CONTAINER IMAGE (rootless: HOST ≥ 1024) same on both
Look after itpodman ps -a podman logs NAME podman exec -it NAME sh podman port NAME same on both
Replace itpodman stop NAME podman rm NAME run the new tag podman rmi IMAGE same on both
Open the portsudo firewall-cmd --add-port=8080/tcp --permanent ; sudo firewall-cmd --reloadsudo ufw allow 8080/tcp
Share itpodman login REGISTRY podman tag IMG REGISTRY/you/IMG:TAG podman push REGISTRY/you/IMG:TAG same on both

Reliability numbers (SRE)

TaskRocky / RHELUbuntu / Debian
The wordsSLI = what you measure SLO = your target SLA = a promise with a penalty KPI = a business number same on both
Nines per 30 days99% = 7h12m 99.5% = 3h36m 99.9% = 43m 99.99% = 4m19s 99.999% = 26s same on both
Availability from a logawk '$9 < 500 {g++} END {printf "%.2f%%\n", 100*g/NR}' access.log same on both
Percentilesawk '{print $NF}' access.log | sort -n | awk '{t[NR]=$1} END {print t[int(NR*0.95)]}' same on both
Error budgetbudget = 1 − SLO burn rate 1 = spends it in exactly 30 days same on both

Monitoring (Prometheus)

TaskRocky / RHELUbuntu / Debian
Portsnode_exporter :9100/metrics Prometheus :9090 Alertmanager :9093 same on both
Check before (re)loadingpromtool check config /etc/prometheus/prometheus.yml promtool check rules FILE sudo systemctl reload prometheus same on both
Querycurl -s 'localhost:9090/api/v1/query?query=up' | jq promtool query instant http://localhost:9090 'EXPR' same on both
PromQLup rate(node_cpu_seconds_total{mode="idle"}[5m]) avg by (instance) (…) predict_linear(x[6h], 86400) same on both
Alertscurl -s localhost:9090/api/v1/alerts | jq pending → firing after for: same on both
See the UI safelyssh -L 9090:localhost:9090 you@server → http://localhost:9090 same on both

Incidents

TaskRocky / RHELUbuntu / Debian
Orderacknowledge → confirm impact → mitigate (roll back!) → communicate → resolve → postmortem same on both
What changed?sudo journalctl -t sudo --since "1 hour ago" ls -lt /etc/httpd/conf.dsudo journalctl -t sudo --since "1 hour ago" ls -lt /etc/apache2/sites-available
Config testsudo apachectl configtestsudo apache2ctl configtest
Update formattime (UTC) · what users see · what we know · what we’re doing · next update at same on both

Limits & OOM

TaskRocky / RHELUbuntu / Debian
OOM evidencedmesg | grep -i -E "oom|killed process"sudo dmesg | grep -i -E "oom|killed process"
Per-service usagesystemd-cgtop -m systemctl status NAME (Memory:) systemctl show NAME -p MemoryCurrent same on both
Set limits now + savedsudo systemctl set-property NAME MemoryMax=300M CPUQuota=50% same on both
Other settingssudo systemctl edit NAME → [Service] OOMScoreAdjust=-500 / LimitNOFILE=65536 same on both
cgroup filescat /sys/fs/cgroup/system.slice/NAME.service/memory.max same on both

Capacity & load testing

TaskRocky / RHELUbuntu / Debian
Get absudo dnf install httpd-toolssudo apt install apache2-utils
Load test (your own server only!)ab -n 2000 -c 50 http://localhost/ (note the trailing /) | grep -E 'Requests per second|Failed| 95%' same on both
Little’s Lawin flight = throughput × latency same on both
Growthawk 'BEGIN { print log(target/now) / log(1 + rate) }' → months same on both

Performance triage (SRE)

TaskRocky / RHELUbuntu / Debian
Get the toolssudo dnf install sysstatsudo apt install sysstat
The checklistuptime dmesg | tail vmstat 1 5 mpstat -P ALL 1 3 pidstat 1 3 iostat -xz 1 3 free -m sar -n DEV 1 3 sar -n TCP,ETCP 1 3 top same on both
Kernel messagesdmesg | tailsudo dmesg | tail
Disk hogspidstat -d 1 3 iostat -xz 1 3 (w_await, aqu-sz, %util) same on both
Signs of troubleload > CPUs vmstat b > 0 or wa high si/so ≠ 0 %util ≈ 100 state D in top same on both
Which service owns a PID?systemctl status PID ps -o pid,ppid,stat,cmd -p PID same on both
Timerssystemctl list-timers systemctl cat NAME.timer OnCalendar=*-*-* 02:00:00 sudo systemctl daemon-reload same on both

Compose

TaskRocky / RHELUbuntu / Debian
Get itsudo dnf install epel-release ; sudo dnf install podman-composesudo apt install podman-compose (or docker-compose-v2 → docker compose)
Runpodman-compose up -d ps logs [SERVICE] exec SERVICE sh restart SERVICE same on both
Changeup -d (recreates what changed) up -d --build (new code) same on both
Stopdown (keeps volumes) down -v (deletes volumes = data!) same on both
Networkingcontainers reach each other by SERVICE NAME localhost = the container itself same on both

CI (GitHub Actions)

TaskRocky / RHELUbuntu / Debian
Where.github/workflows/NAME.yml → on: [push] jobs: runs-on: steps: uses / run same on both
Pass or failexit code 0 = pass anything else = fail same on both
From the terminalgh run list gh run view gh run view ID --log-failed same on both
Lint scriptssudo dnf install ShellCheck (EPEL) ; shellcheck *.shsudo apt install shellcheck ; shellcheck *.sh

Ansible

TaskRocky / RHELUbuntu / Debian
Installsudo dnf install epel-release ; sudo dnf install ansiblesudo apt install ansible
Inventory[web] web1 ansible_host=192.168.1.61 ansible-inventory --graph same on both
Ad-hocansible all -m ping ansible web -a 'uptime' ansible web -b -m package -a 'name=tree' same on both
Playbooksansible-playbook site.yml --check --diff ansible-playbook site.yml -l web1 same on both
Both familieswhen: ansible_facts['os_family'] == "RedHat" / "Debian" or [rocky]/[ubuntu] groups + group_vars same on both
Variablesgroup_vars/GROUP.yml host_vars/HOST.yml ansible-inventory --host HOST -e var=value (wins) same on both
Loops & handlersloop: "{{ list }}" + {{ item }} notify: Name → handlers: - name: Name same on both
Rolesansible-galaxy role init --init-path roles NAME roles: [NAME] defaults/ (low) vs vars/ (high) same on both
Vaultansible-vault encrypt|view|edit|decrypt FILE --ask-vault-pass vault_password_file = ~/.vault_pass same on both
Rolling updatesserial: 1 max_fail_percentage: 0 uri + assert health check -l web1 -i inventories/production.ini same on both
Checksansible-playbook X --syntax-check ansible-lint run twice → changed=0 --list-tasks --tags TAG same on both

Secrets & config

TaskRocky / RHELUbuntu / Debian
.envecho "DB_PASSWORD=$(openssl rand -hex 16)" > .env ; chmod 600 .env ; echo .env >> .gitignore same on both
ComposeREDIS_PASSWORD: ${DB_PASSWORD:?set it in .env} same on both
Services/etc/sysconfig/NAME (600) + EnvironmentFile=/etc/sysconfig/NAME/etc/default/NAME (600) + EnvironmentFile=/etc/default/NAME
Find leaksgit log -p | grep -i pass gitleaks trufflehog same on both
After a leakROTATE: new secret everywhere, old one must stop working same on both

Deploys

TaskRocky / RHELUbuntu / Debian
Proxy switchedit ProxyPass port ; sudo apachectl configtest ; sudo systemctl reload httpdedit ProxyPass port ; sudo apache2ctl configtest ; sudo systemctl reload apache2
Smoke test green firstcurl localhost:8082/health curl localhost:8082/api/… same on both
Strategiesrecreate · rolling · blue-green (instant rollback) · canary (small slice first) same on both
Rocky + proxysudo setsebool -P httpd_can_network_connect 1 same on both

AWS: basics

TaskRocky / RHELUbuntu / Debian
Install CLI v2sudo dnf install unzip ; curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o awscliv2.zip ; unzip awscliv2.zip ; sudo ./aws/installsudo apt install unzip ; curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o awscliv2.zip ; unzip awscliv2.zip ; sudo ./aws/install
Sign in / who am Iaws configure aws configure --profile NAME aws sts get-caller-identity same on both
Pick fields--query 'Items[].Field' --output text|table|json X=$(aws … --query Id --output text) same on both
Region--region us-east-1 export AWS_REGION=… aws configure get region same on both
IAMaws iam create-user / create-group / add-user-to-group / attach-group-policy --policy-arn … create-access-key same on both
Test a policyaws iam simulate-principal-policy --policy-source-arn USER-ARN --action-names s3:PutObject same on both

AWS: network & servers

TaskRocky / RHELUbuntu / Debian
VPCaws ec2 create-vpc --cidr-block 10.0.0.0/16 create-subnet --vpc-id … --cidr-block 10.0.1.0/24 --availability-zone us-east-1a same on both
Public subnetcreate-internet-gateway + attach-internet-gateway create-route-table + create-route 0.0.0.0/0 → igw + associate-route-table same on both
Security groupcreate-security-group authorize-security-group-ingress --protocol tcp --port 22 --cidr MY-IP/32 --source-group SG same on both
Latest AMIaws ssm get-parameter --name /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64aws ssm get-parameter --name /aws/service/canonical/ubuntu/server/24.04/stable/current/amd64/hvm/ebs-gp3/ami-id
Launchaws ec2 run-instances --image-id $AMI --instance-type t3.micro --key-name K --subnet-id … --security-group-ids … --user-data file://web.sh same on both
Wait & findaws ec2 wait instance-running --instance-ids $ID describe-instances --query 'Reservations[].Instances[].PublicIpAddress' same on both
Log inssh -i key.pem ec2-user@IP (Amazon Linux)ssh -i key.pem ubuntu@IP
First-boot logtail /var/log/cloud-init-output.log same on both
Lifecyclestop-instances (new IP on start) · start-instances · terminate-instances (gone) same on both

AWS: S3

TaskRocky / RHELUbuntu / Debian
Buckets & filesaws s3 mb s3://UNIQUE-NAME aws s3 cp FILE s3://B/ aws s3 ls s3://B --recursive aws s3 sync DIR s3://B same on both
Deleteaws s3 rm s3://B/KEY aws s3 rb s3://B --force same on both
Websiteaws s3 website s3://B --index-document index.html --error-document error.html → http://B.s3-website-REGION.amazonaws.com same on both
Go public (careful!)aws s3api put-public-access-block … BlockPublicPolicy=false,RestrictPublicBuckets=false put-bucket-policy --policy file://p.json same on both
Share one fileaws s3 presign s3://B/KEY --expires-in 300 same on both
Versionsaws s3api put-bucket-versioning --bucket B --versioning-configuration Status=Enabled list-object-versions same on both

AWS: scale, DNS, alarms

TaskRocky / RHELUbuntu / Debian
Load balancerelbv2 create-target-group · register-targets · create-load-balancer --subnets A B · create-listener --default-actions Type=forward,TargetGroupArn=… same on both
Why unhealthy?aws elbv2 describe-target-health --target-group-arn $TG (Timeout = security group) same on both
Auto Scalingcreate-launch-template (UserData = base64 -w0) · autoscaling create-auto-scaling-group --min-size --max-size --desired-capacity same on both
Scale on CPUput-scaling-policy --policy-type TargetTrackingScaling … 'TargetValue':50 describe-scaling-activities same on both
DNS recordsaws route53 change-resource-record-sets --hosted-zone-id Z --change-batch file://c.json (CREATE · UPSERT · DELETE) same on both
Check DNSdig +short NAME dig NS DOMAIN dig @ns-X.awsdns-Y.com NAME same on both
Alarm → emailsns create-topic · sns subscribe --protocol email · cloudwatch put-metric-alarm … --alarm-actions TOPIC-ARN same on both
Guard the billbilling alarm in us-east-1 (AWS/Billing EstimatedCharges) aws budgets create-budget same on both

AWS: costs & clean-up

TaskRocky / RHELUbuntu / Debian
This monthaws ce get-cost-and-usage --time-period Start=…,End=… --granularity MONTHLY --metrics UnblendedCost --group-by Type=DIMENSION,Key=SERVICE same on both
Find by tagaws resourcegroupstaggingapi get-resources --tag-filters Key=Project,Values=NAME same on both
Every regionfor r in $(aws ec2 describe-regions --query 'Regions[].RegionName' --output text); do aws ec2 describe-instances --region $r …; done same on both
Usual leftoversdescribe-addresses 'Addresses[?AssociationId==null]' · describe-volumes status=available · load balancers · hosted zones · buckets same on both
Delete orderASGs → instances → load balancers → target groups → IPs/volumes → buckets → DNS → network same on both

Git

TaskRocky / RHELUbuntu / Debian
Installsudo dnf install git(preinstalled) sudo apt install git
Who am Igit config --global user.name "Name" user.email you@example.com same on both
Start / copy a repogit init git clone URL same on both
Save a snapshotgit status git add FILE git commit -m "message" git commit -am "…" same on both
Look backgit log --oneline git diff git diff --staged git show HASH same on both
Undogit restore FILE git restore --staged FILE git restore --source=HASH FILE same on both
Branchesgit branch git switch -c NAME git switch main git merge NAME same on both
Conflictedit the file (remove <<< === >>>) → git add FILE → git commit same on both
Sharegit remote add origin URL git push -u origin main git pull same on both
Never commitpasswords & keys → list them in .gitignore same on both

Python & AI

TaskRocky / RHELUbuntu / Debian
Python toolssudo dnf install python3-pipsudo apt install python3-venv python3-pip
Make & use a venvpython3 -m venv ~/ai-env source ~/ai-env/bin/activate deactivate same on both
Install Ollamacurl -fsSL https://ollama.com/install.sh | sh same on both
Chat with a modelollama run llama3.2 (/bye to quit) same on both
Models: list / removeollama list ollama rm NAME same on both

Help!

TaskRocky / RHELUbuntu / Debian
Manual pageman COMMAND (q to quit) same on both
Quick helpCOMMAND --help same on both
Which command does…?man -k WORD apropos WORD same on both
Cancel / clear screenCtrl+C clear (or Ctrl+L) same on both
Previous commands↑ history same on both
Auto-completeTab same on both

Attack surface

TaskRocky / RHELUbuntu / Debian
What's listening (0.0.0.0 = everyone)sudo ss -tlnp same on both
Which services runsystemctl list-units --type=service --state=running same on both
Accounts that can log ingetent passwd | awk -F: '$7 !~ /(nologin|false)$/ {print $1}' same on both
Who is an admingetent group wheelgetent group sudo
Turn a service off for goodsudo systemctl disable --now NAME same on both
Lock an account (keep it for now)sudo usermod -L NAME sudo usermod -s /sbin/nologin NAME sudo gpasswd -d NAME wheelsudo usermod -L NAME sudo usermod -s /sbin/nologin NAME sudo gpasswd -d NAME sudo

SSH hardening

TaskRocky / RHELUbuntu / Debian
Where the SSH server settings live/etc/ssh/sshd_config /etc/ssh/sshd_config.d/*.conf (read first; first value wins) same on both
Give a key a passphrase (new or existing)ssh-keygen -t ed25519 ssh-keygen -p -f ~/.ssh/id_ed25519 same on both
Unlock the key once per sessionssh-add ssh-add -l same on both
Keys only, no root loginprintf 'PasswordAuthentication no\nPermitRootLogin no\n' | sudo tee /etc/ssh/sshd_config.d/00-hardening.conf same on both
Test the config (silence = OK)sudo sshd -t same on both
What sshd really usessudo sshd -T | grep -iE 'passwordauth|permitroot' same on both
Apply itsudo systemctl reload sshdsudo systemctl reload ssh
Prove passwords are off (from the laptop)ssh -o PubkeyAuthentication=no user@server (must be refused) same on both
Only these users may log inAllowUsers student maria (in the drop-in) same on both
The drop-in that overrides you01-permitrootlogin.conf (PermitRootLogin yes)50-cloud-init.conf (PasswordAuthentication yes)

Firewalls in depth

TaskRocky / RHELUbuntu / Debian
See the rulessudo firewall-cmd --get-active-zones sudo firewall-cmd --list-allsudo ufw status verbose sudo ufw status numbered
Refuse everything by default(the public zone already does)sudo ufw default deny incoming
SSH only from your networksudo firewall-cmd --permanent --zone=internal --add-source=192.168.1.0/24 sudo firewall-cmd --permanent --zone=public --remove-service=ssh sudo firewall-cmd --reloadsudo ufw allow from 192.168.1.0/24 to any port 22
Block one addresssudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="203.0.113.45" drop'sudo ufw insert 1 deny from 203.0.113.45
Remove a rule--remove-service=NAME --remove-rich-rule='…'sudo ufw delete NUMBER
Save what's running nowsudo firewall-cmd --runtime-to-permanent(ufw saves every change)
Rate-limit SSHrich rule: rule service name="ssh" limit value="3/m" acceptsudo ufw limit OpenSSH
Log what's blockedsudo firewall-cmd --set-log-denied=all sudo journalctl -k | grep REJECTsudo ufw logging on sudo journalctl -k | grep 'UFW BLOCK'