Cheat sheet
Everything from the lessons on one page. Rows marked “same on both” work everywhere. Use the Both · Rocky · Ubuntu switch at the top to hide the family you don't use. For whole jobs step by step (a new server, a website, a break-in), see the recipes.
Printing gives you what's on screen: pick Rocky or Ubuntu at the top for a shorter sheet, or filter first to print just a few rows.
Computer Hacking Tools · Linux cheat sheet · computerhackingtools.com/cheatsheet
Where am I & what’s here
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Print current folder | pwd same on both | |
| List files | ls same on both | |
| List with details / hidden files | ls -l ls -a ls -la same on both | |
| Shortcut “ll” | ls -l | ls -alF |
| Which distro is this? | cat /etc/os-release same on both | |
| Who am I? / my groups | whoami id same on both | |
| Machine name / IP address | hostname hostname -I same on both | |
Moving around
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Go into a folder | cd Documents same on both | |
| Up one level / home / back | cd .. cd cd - same on both | |
| Absolute path | cd /var/log same on both | |
Files & folders
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Make folder / nested folders | mkdir NAME mkdir -p a/b/c same on both | |
| Make empty file | touch FILE same on both | |
| Write / append text | echo "hi" > FILE echo "hi" >> FILE same on both | |
| Read a file | cat FILE less FILE head FILE tail FILE same on both | |
| Copy / copy folder | cp A B cp -r DIR1 DIR2 same on both | |
| Move / rename | mv OLD NEW same on both | |
| Delete file / folder | rm FILE rm -r DIR rmdir EMPTYDIR same on both | |
| Friendly text editor | sudo dnf install nano → nano FILE | nano FILE (pre-installed) |
| Write to a root-owned file | echo "hi" | sudo tee /etc/FILE same on both | |
| Search inside text | grep WORD FILE COMMAND | grep WORD same on both | |
| File details / type | stat FILE file FILE same on both | |
The filesystem
| Task | Rocky / RHEL | Ubuntu / 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 | |
| Devices | ls -l /dev (b = block/disk, c = character) /dev/null same on both | |
| sudo finds /usr/local/bin? | No: use the full path | Yes |
| The official map | man hier same on both | |
Finding files & links
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Find by name / type / size | find ~ -name "*.txt" find / -type d -name NAME find ~ -size +100M same on both | |
| Changed recently | find ~ -mtime -1 (last day) find ~ -mmin -30 same on both | |
| Act on results | find … -exec COMMAND {} \; find … -delete (look first!) same on both | |
| Instant search | sudo dnf install mlocate → sudo updatedb → locate NAME | sudo apt install plocate → locate NAME |
| Refresh locate | sudo updatedb same on both | |
| Where is a command? | which CMD whereis CMD type CMD same on both | |
| Shortcut (symlink) | ln -s TARGET LINKNAME readlink LINK same on both | |
| Hard link / inode numbers | ln FILE NEWNAME ls -li same on both | |
| Find broken links | find ~ -xtype l same on both | |
Installing software
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Refresh package list | (automatic) dnf check-update | sudo apt update |
| Install | sudo dnf install NAME | sudo apt install NAME |
| Remove | sudo dnf remove NAME | sudo apt remove NAME |
| Update everything | sudo dnf upgrade | sudo apt update && sudo apt upgrade |
| Search | dnf search WORD | apt search WORD |
| Package details | dnf info NAME | apt show NAME |
| List installed | dnf list installed rpm -qa | apt list --installed dpkg -l |
| Extra repository | sudo 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/ls | dpkg -S /usr/bin/ls |
| Version & repo / dependencies | dnf info NAME dnf deplist NAME | apt-cache policy NAME apt-cache depends NAME |
| Package file type | .rpm | .deb |
| Old command name | yum | apt-get |
Users & sudo
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Run one command as root | sudo COMMAND same on both | |
| Admin group | wheel | sudo |
| Add a user | sudo useradd NAME → sudo passwd NAME | sudo adduser NAME |
| Make a user an admin | sudo usermod -aG wheel NAME | sudo usermod -aG sudo NAME |
| Change your password | passwd same on both | |
| Root shell (then exit!) | sudo -i sudo su - same on both | |
| Switch user | su - NAME (the dash = full login) same on both | |
| Is root locked? | sudo passwd -S root → LK | sudo passwd -S root → L (always) |
| Who is logged in? | who w last same on both | |
vim
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Get it | sudo dnf install vim-enhanced (vi is always there) | vim is pre-installed |
| Modes | i 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 | |
| Move | h j k l w b e 0 ^ $ gg G :42 Ctrl+D / Ctrl+U same on both | |
| Edit | x 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 editor | echo 'export EDITOR=vim' >> ~/.bashrc sudoedit /etc/FILE same on both | |
Wildcards & braces
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Match | * anything ? one character [abc] [0-9] [!x] one of / not same on both | |
| Make lists | mkdir -p proj/{src,docs} touch f{1..5}.txt cp a.conf{,.bak} same on both | |
| Look first | echo rm *.log → rm *.log same on both | |
| Quote to stop it | find . -name '*.png' echo '*' same on both | |
Jobs
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Stop / pause | Ctrl+C stop Ctrl+Z pause same on both | |
| Background | cmd & bg jobs [-l] same on both | |
| Foreground | fg fg %2 same on both | |
| End a job | kill %1 kill PID same on both | |
| Survive logout | nohup cmd & disown %1 (forever? use a systemd service) same on both | |
Your hardware
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Linux & kernel | cat /etc/os-release uname -r hostnamectl same on both | |
| CPU & memory | lscpu nproc free -h /proc/cpuinfo /proc/meminfo same on both | |
| Disks | lsblk (devices) df -h (space) same on both | |
| Devices | sudo dnf install pciutils usbutils → lspci lsusb | lspci lspci -k lsusb |
| Model & VM? | sudo dmidecode -s system-product-name systemd-detect-virt same on both | |
Archives & compression
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Compress one file | gzip -k F xz -k F bzip2 -k F → gunzip / unxz / bunzip2 same on both | |
| Read without unpacking | zcat xzcat bzcat same on both | |
| tar | tar -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 | |
| zip | sudo dnf install zip unzip → zip -r a.zip DIR unzip -l a.zip unzip a.zip -d DIR | sudo 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
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| What’s waiting | dnf check-update dnf updateinfo list --security | sudo apt update apt list --upgradable |
| Security only | sudo dnf upgrade --security | sudo unattended-upgrade -v |
| Reboot needed? | dnf needs-restarting -r sudo dnf needs-restarting -s | ls /var/run/reboot-required cat /var/run/reboot-required.pkgs |
| Kernels | rpm -q kernel | dpkg -l 'linux-image*' |
| Automatic | sudo 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
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Vendor repo | sudo dnf config-manager --add-repo URL.repo → sudo dnf install PKG | curl -fsSL KEY | sudo gpg --dearmor -o /usr/share/keyrings/X.gpg deb [signed-by=…] URL noble main → sudo apt update |
| Snap | EPEL: sudo dnf install snapd sudo systemctl enable --now snapd.socket | snap find X sudo snap install X snap list sudo snap remove X |
| Flatpak | sudo flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo flatpak install flathub ID flatpak list same on both | |
| AppImage | chmod u+x X.AppImage ./X.AppImage (needs fuse-libs) | chmod u+x X.AppImage ./X.AppImage (needs libfuse2t64) |
SELinux & AppArmor
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Is it on? | getenforce sestatus | sudo aa-status |
| Labels / profiles | ls -Z ps -eZ id -Z | ls /etc/apparmor.d ps auxZ |
| What was blocked | sudo ausearch -m avc -ts recent | sudo journalctl -k | grep DENIED |
| Test briefly | sudo setenforce 0 … sudo setenforce 1 | sudo aa-complain PROG … sudo aa-enforce PROG |
| Fix for real | sudo semanage fcontext -a -t TYPE "/path(/.*)?" sudo restorecon -Rv /path semanage port -a setsebool -P | edit /etc/apparmor.d/PROFILE sudo apparmor_parser -r PROFILE |
Networking
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| My IP addresses | ip a ip -br a (old: ifconfig · Windows: ipconfig) same on both | |
| Gateway / routes | ip route (old: route -n) same on both | |
| Neighbours on the LAN | ip neigh (old: arp -a) same on both | |
| Listening ports | ss -tlnp (old: netstat -tlnp) same on both | |
| Get ifconfig/route/netstat | sudo dnf install net-tools | sudo apt install net-tools |
| Test the path | ping -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 use | cat /etc/resolv.conf | resolvectl status (resolv.conf says 127.0.0.53) |
| DNS lookup tools | sudo dnf install bind-utils → dig NAME | dig NAME (pre-installed) |
| Local name overrides | /etc/hosts getent hosts NAME same on both | |
| Permanent config | NetworkManager: 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 changes | sudo ip addr add IP/24 dev CARD sudo ip route add default via GW same on both | |
| Rename the machine | sudo hostnamectl set-hostname NAME same on both | |
Links, neighbours & MTU
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Interfaces, MACs, up or down | ip -br link same on both | |
| Cable, speed, duplex | ethtool enp0s3 same on both | |
| Neighbour (ARP) table | ip neigh same on both | |
| See Ethernet headers too | sudo tcpdump -e -nn -c 10 same on both | |
| Test the MTU (1500 = 1472 + 28) | ping -c 1 -M do -s 1472 HOST same on both | |
| Change the MTU for good | sudo nmcli con mod enp0s3 802-3-ethernet.mtu 1400 | mtu: 1400 (netplan) |
IP addresses & routes
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| My addresses and prefixes | ip -br a ip -6 addr same on both | |
| Network, broadcast, hosts | ipcalc 192.168.1.50/24 same on both | |
| Split a network into /26s | ipcalc -S 26 10.20.0.0/24 | ipcalc 10.20.0.0/24 /26 |
| Install ipcalc | (installed) | sudo apt install ipcalc |
| Routing table | ip route same on both | |
| Which way will a packet go? | ip route get 10.20.0.15 same on both | |
| Follow the path hop by hop | tracepath -n 8.8.8.8 traceroute -n 8.8.8.8 same on both | |
| Add a route (until reboot) | sudo ip route add 10.20.0.0/24 via 192.168.1.60 same on both | |
| Save a route | sudo nmcli con mod enp0s3 +ipv4.routes "10.20.0.0/24 192.168.1.60" sudo nmcli con up enp0s3 | routes: - to: 10.20.0.0/24 via: 192.168.1.60 (netplan) sudo netplan apply |
| My public address (after NAT) | curl ifconfig.me same on both | |
DNS
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Look up a name | dig NAME dig NAME +short same on both | |
| Other record types | dig NAME MX dig NAME TXT dig NAME AAAA dig NAME NS same on both | |
| Ask a particular DNS server | dig @1.1.1.1 NAME same on both | |
| The truth, skipping caches | dig +trace NAME same on both | |
| Address → name | dig -x ADDRESS same on both | |
| What apps get (hosts file first) | getent hosts NAME same on both | |
| Install dig | sudo dnf install bind-utils | (installed: bind9-dnsutils) |
| Which DNS servers this machine uses | cat /etc/resolv.conf nmcli dev show | grep DNS | resolvectl status |
| Empty the local DNS cache | (no local cache) | sudo resolvectl flush-caches |
Ports & connections
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Who listens (TCP / UDP) | sudo ss -tlnp sudo ss -ulnp same on both | |
| Open connections | ss -tn sudo ss -tnp same on both | |
| Name of a port | getent services ssh grep -w 443 /etc/services same on both | |
| Can I connect? (refused vs timed out) | nc -zv HOST PORT nc -zv -w 3 HOST PORT same on both | |
| Install nc / tcpdump | sudo dnf install nmap-ncat tcpdump | (both preinstalled) |
| Watch packets | sudo tcpdump -nn -c 10 tcp port 80 sudo tcpdump -nn udp port 53 same on both | |
HTTP with curl
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| The whole conversation | curl -v URL same on both | |
| Headers only / headers + body | curl -I URL curl -i URL same on both | |
| Follow redirects | curl -L URL same on both | |
| Just the status code | curl -s -o /dev/null -w '%{http_code}\n' URL same on both | |
| Another site on the same server | curl -H 'Host: shop.example.org' http://SERVER/ same on both | |
| Send data / log in | curl -X POST -d 'name=maria' URL curl -u USER:PASS URL same on both | |
| The web server's logs | /var/log/httpd/access_log /var/log/httpd/error_log | /var/log/apache2/access.log /var/log/apache2/error.log |
TLS & certificates
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Watch the handshake | curl -v https://SITE same on both | |
| See the chain a server sends | openssl s_client -connect SITE:443 -servername SITE -showcerts </dev/null same on both | |
| Read a certificate | openssl x509 -in FILE -noout -subject -issuer -dates -ext subjectAltName same on both | |
| Expires within 30 days? | … | openssl x509 -noout -checkend 2592000 same on both | |
| Trust your own CA | sudo cp CA.pem /etc/pki/ca-trust/source/anchors/ sudo update-ca-trust | sudo cp CA.pem /usr/local/share/ca-certificates/CA.crt sudo update-ca-certificates |
| The CA bundle apps read | /etc/pki/tls/certs/ca-bundle.crt | /etc/ssl/certs/ca-certificates.crt |
Load balancing & caching
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Install HAProxy | sudo dnf install haproxy | sudo apt install haproxy |
| Config and check | /etc/haproxy/haproxy.cfg sudo haproxy -c -f /etc/haproxy/haproxy.cfg same on both | |
| A backend with health checks | balance roundrobin server web1 127.0.0.1:8081 check same on both | |
| Apply a new config | sudo systemctl reload haproxy same on both | |
| Servers going UP / DOWN | journalctl -u haproxy same on both | |
| Is it from the CDN cache? | curl -I URL (look at x-cache: HIT/MISS and age) same on both | |
Remote access (SSH)
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Connect / disconnect | ssh USER@HOST exit same on both | |
| Make a key pair | ssh-keygen -t ed25519 same on both | |
| Copy key to server | ssh-copy-id USER@HOST same on both | |
| Copy key without ssh-copy-id | cat ~/.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 permissions | refused (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 | |
| Apps | Windows Terminal · PuTTY (+PuTTYgen, .ppk keys) · MobaXterm · Mac Terminal · iTerm2 · Termius same on both | |
| PuTTY copy / paste | select = copy right-click = paste same on both | |
| Web console | Cockpit: sudo systemctl enable --now cockpit.socket → https://IP:9090 | sudo apt install cockpit → https://IP:9090 |
| Install SSH server | sudo dnf install openssh-server | sudo apt install openssh-server |
| SSH service name | sshd | ssh |
Services
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Status / start / stop / restart | systemctl status NAME sudo systemctl start|stop|restart NAME same on both | |
| Start now + at every boot | sudo systemctl enable --now NAME same on both | |
| Auto-starts after install? | No | Yes |
| Everything in the logs | journalctl -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 commands | service NAME restart (“Redirecting to /bin/systemctl…”) | service NAME restart /etc/init.d/NAME restart |
| /etc/init.d | just a README | compatibility scripts (“via systemctl”) |
| Old boot on/off tool | chkconfig NAME on | update-rc.d NAME enable |
| Read / edit a unit | systemctl 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 file | sudo systemctl daemon-reload same on both | |
| Block / unblock a service | sudo systemctl mask NAME sudo systemctl unmask NAME same on both | |
| Installed units & state | systemctl list-unit-files systemctl --failed same on both | |
| Runlevel → target | runlevel 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)
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Package & service | httpd | apache2 |
| Install | sudo dnf install httpd | sudo apt install apache2 |
| Config folder | /etc/httpd/ | /etc/apache2/ |
| Logs | /var/log/httpd/ | /var/log/apache2/ |
| Runs as user | apache | www-data |
| Web pages go in | /var/www/html/ same on both | |
| Test it | curl localhost same on both | |
Firewall
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Tool | firewalld (firewall-cmd) | ufw |
| On by default? | Yes (SSH allowed) | No |
| Show rules | sudo firewall-cmd --list-services | sudo ufw status |
| Allow web traffic | sudo firewall-cmd --permanent --add-service=http → sudo firewall-cmd --reload | sudo ufw allow 'Apache' |
| Allow SSH | (already allowed) | sudo ufw allow OpenSSH ← before enabling! |
| Turn on | sudo systemctl enable --now firewalld | sudo ufw enable |
| Security guard | SELinux | AppArmor |
| Low-level rules | sudo nft list ruleset | sudo iptables -L -n |
| iptables command | sudo dnf install iptables-nft | (pre-installed) |
Network & security tools
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Fetch a page / headers | curl URL curl -I URL curl -O URL same on both | |
| Download a file | sudo dnf install wget → wget URL | wget URL |
| Is a port open? | sudo dnf install nmap-ncat → nc -zv HOST PORT | nc -zv HOST PORT |
| What is listening? | ss -tlnp sudo lsof -i :PORT same on both | |
| Watch packets | sudo dnf install tcpdump → sudo tcpdump -i enp0s3 -c 5 icmp | sudo tcpdump -i enp0s3 -c 5 icmp |
| Random secret | openssl rand -base64 24 same on both | |
| Self-signed certificate | openssl req -x509 -newkey rsa:2048 -nodes -keyout key.pem -out cert.pem -days 30 -subj "/CN=NAME" same on both | |
| Check a certificate | openssl x509 -in cert.pem -noout -dates openssl s_client -connect HOST:443 same on both | |
| Checksum | sha256sum FILE sha256sum -c SHA256SUMS same on both | |
| My public IP | curl ifconfig.me same on both | |
Logs
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Read the journal | journalctl -u NAME -n 50 journalctl -p err journalctl -xeu NAME same on both | |
| Who can read log files | root (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 live | sudo tail -f FILE (Ctrl+C to stop) same on both | |
| Kernel messages | dmesg -T journalctl -k | sudo dmesg -T journalctl -k |
Text power tools
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Find lines | grep -i -n -c -v -r -E "a|b" same on both | |
| Count | wc -l FILE same on both | |
| Sort / unique / count | sort sort -n sort -rn sort | uniq -c same on both | |
| Columns | cut -d, -f1,3 FILE awk -F, '{print $1}' FILE same on both | |
| Filter by column | awk -F, '$3 > 85 {print $1}' FILE same on both | |
| awk flavor | gawk (awk --version) | mawk (awk -W version) |
| Find & replace | sed 's/old/new/g' FILE (then -i to save) same on both | |
| Swap characters | tr a-z A-Z tr , '\n' tr -d CHARS same on both | |
| Show and save | COMMAND | tee FILE same on both | |
| List → arguments | find … | xargs COMMAND same on both | |
| Compare files | diff OLD NEW same on both | |
Permissions
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Read permissions | ls -l stat FILE same on both | |
| Make a script runnable | chmod +x script.sh same on both | |
| Private file / normal file / folder | chmod 600 FILE chmod 644 FILE chmod 755 DIR same on both | |
| Change owner / group | sudo chown USER:GROUP FILE sudo chgrp GROUP FILE same on both | |
| Default permissions | umask same on both | |
| Home folder default | 700 (drwx------) | 750 (drwxr-x---) |
| /etc/shadow | 000, root only | 640, root:shadow |
| Extra security layer | SELinux (ls -Z) | AppArmor |
Users & groups
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Create a user | sudo useradd NAME → sudo passwd NAME | sudo adduser NAME |
| useradd makes a home folder? | Yes | Only with -m |
| Add to a group (keep others!) | sudo usermod -aG GROUP USER sudo gpasswd -a USER GROUP same on both | |
| New group | sudo groupadd NAME same on both | |
| Lock / delete user | sudo usermod -L USER sudo userdel -r USER same on both | |
| Look up | id USER getent passwd USER who last same on both | |
Cron (scheduling)
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Package / service | cronie / crond | cron / cron |
| Edit / list / remove your jobs | crontab -e crontab -l crontab -r (no undo!) same on both | |
| Time fields | minute 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 editor | vi (EDITOR=nano crontab -e) | asks: 1 = nano |
| cron.d file names with a dot | work | IGNORED |
| Cron log | /var/log/cron | grep CRON /var/log/syslog |
| In a crontab, write % as | \% (or use a script) same on both | |
Processes & disk
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| List processes | ps aux pgrep -l NAME top htop same on both | |
| htop | EPEL: sudo dnf install htop | pre-installed |
| Stop a process | kill PID kill -9 PID pkill NAME same on both | |
| Load / cores / memory | uptime 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 type | xfs | ext4 |
| Empty a big log safely | sudo truncate -s 0 FILE same on both | |
| Deleted but still full? | sudo lsof +L1 → restart / stop that program same on both | |
| Stop by exact name | killall NAME same on both | |
Disks & mounting
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| See disks | lsblk sudo fdisk -l df -hT same on both | |
| Partition a disk | sudo fdisk /dev/sdb (n … w) same on both | |
| Format | sudo mkfs.xfs /dev/sdb1 | sudo mkfs.ext4 /dev/sdb1 |
| Mount / unmount | sudo mount /dev/sdb1 /data sudo umount /data same on both | |
| “target is busy” | cd ~ lsof /data same on both | |
| Disk ID for fstab | sudo blkid /dev/sdb1 same on both | |
| Mount at boot | UUID=… /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
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Create / list / extract archive | tar -czf A.tar.gz DIR tar -tzf A.tar.gz tar -xzf A.tar.gz -C DEST same on both | |
| Date in a file name | backup-$(date +%F).tar.gz same on both | |
| Mirror a folder | rsync -av SRC/ DEST/ (dry run: -n) same on both | |
| Copy to a server | rsync -av DIR user@host:path/ scp FILE user@host: same on both | |
| Install rsync | sudo dnf install rsync (if missing) | (pre-installed) |
| Compress one file | gzip FILE gunzip FILE.gz zcat FILE.gz same on both | |
| Zip files | sudo dnf install zip unzip | sudo apt install zip unzip |
| Make / list / extract zip | zip -r A.zip DIR unzip -l A.zip unzip A.zip -d DEST same on both | |
| Verify backups | sha256sum *.tar.gz > SHA256SUMS sha256sum -c SHA256SUMS same on both | |
Your company’s app
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Program / settings / data | /opt/cht/ourapp /etc/opt/cht/ourapp /var/opt/cht/ourapp same on both | |
| Service account | sudo useradd --system --shell /sbin/nologin ourapp | sudo adduser --system --group ourapp |
| Owners | program root:root 755 config root:ourapp 640 data ourapp:ourapp 750 same on both | |
| Switch versions | sudo ln -sfn /opt/cht/ourapp/releases/ourapp-X /opt/cht/ourapp/current → sudo systemctl restart ourapp same on both | |
| Test as the app user | sudo -u ourapp /opt/cht/ourapp/current/bin/ourapp --check-config | sudo -u ourapp ourapp --check-config |
| SELinux labels | ls -Z sudo ausearch -m avc -ts recent sudo restorecon -Rv /opt/cht | (AppArmor: no file labels, ls -Z shows ?) |
| Open the port | sudo firewall-cmd --permanent --add-port=8080/tcp → --reload | sudo ufw allow 8080/tcp |
Shell skills
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Repeat last command (as root) | !! sudo !! same on both | |
| Search history | history | grep WORD Ctrl+R same on both | |
| Variables | NAME=value echo "$NAME" export NAME env same on both | |
| Shortcuts | alias NAME='COMMAND' (keep it: add to ~/.bashrc) same on both | |
| Rerun every 2 s | watch -n 2 COMMAND same on both | |
| Session that survives disconnects | sudo dnf install tmux → tmux new -s NAME | tmux new -s NAME |
| Detach / reattach | Ctrl+B then D tmux ls tmux attach -t NAME same on both | |
| screen | EPEL only: sudo dnf install screen | screen -S NAME Ctrl+A then D screen -r |
| Time zone | timedatectl sudo timedatectl set-timezone America/Chicago same on both | |
| Clock sync service | chronyd | systemd-timesyncd |
| Rename the server | sudo hostnamectl set-hostname NAME | sudo hostnamectl set-hostname NAME (+ fix 127.0.1.1 in /etc/hosts) |
Capturing output
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Save results / add to a file | cmd > file cmd >> file same on both | |
| Errors only / both / silence | cmd 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 it | cmd | tee file cmd 2>&1 | tee -a log same on both | |
| Write a whole file | cat > file <<EOF … EOF <<'EOF' = no $ expansion same on both | |
| Error message from a script | echo "oops" >&2 same on both | |
| Record a session | script session.log … exit same on both | |
| To the system log | logger -t TAG "msg" → /var/log/messages | logger -t TAG "msg" → /var/log/syslog |
| Log a whole script | exec >> /path/script.log 2>&1 (near the top) same on both | |
Substitution & subshells
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Insert a command’s output | echo "Today: $(date +%A)" old style: `date +%A` same on both | |
| Save output in a variable | n=$(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 file | diff <(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
| Task | Rocky / RHEL | Ubuntu / 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 | |
| Decide | if …; then …; elif …; else …; fi cmd && ok || fail same on both | |
| Choose | case "$x" in a|b) … ;; *) … ;; esac same on both | |
| Loops | for x in a b c; do …; done while [ … ]; do …; done same on both | |
| File line by line | while IFS=: read -r a b rest; do …; done < file same on both | |
| Functions | name() { local x="$1"; …; return 0; } same on both | |
| Debug / strict | bash -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
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| /bin/sh is… | bash (lenient) | dash (strict) |
| Instead of [[ ]] / == / source | [ ] · = · . same on both | |
| Instead of echo -e / -n | printf '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 script | sudo dnf install ShellCheck (EPEL) → shellcheck s.sh | sudo apt install shellcheck → shellcheck s.sh checkbashisms s.sh |
| POSIX version / limits | getconf _POSIX_VERSION getconf ARG_MAX same on both | |
Distro cousins
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Rocky’s twin | AlmaLinux: same commands, ID="almalinux" same on both | |
| Amazon Linux 2023 | Red 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 images | rockylinux/rockylinux:9 · almalinux:9 · amazonlinux:2023 · ubuntu:24.04 · debian:13 · alpine same on both | |
| Alpine | apk add PKG no bash (BusyBox sh) same on both | |
Alpine & containers
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Get podman | sudo dnf install podman | sudo apt install podman (or docker.io, with sudo) |
| Run Alpine | podman run -it docker.io/library/alpine:3.22 (exit to leave) same on both | |
| One command, then clean up | podman run --rm IMAGE COMMAND same on both | |
| Containers / images | podman ps -a podman images podman rm NAME same on both | |
| Alpine packages | apk update apk add PKG apk del PKG apk search WORD apk add --no-cache PKG same on both | |
| Alpine basics | shell = 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)
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Build | podman build -t NAME:TAG . (the dot = build context) sudo docker build -t NAME . same on both | |
| Containerfile | FROM image:tag RUN cmd COPY src dest WORKDIR dir ENV K=V EXPOSE port CMD ["prog", "arg"] same on both | |
| Run with a port | podman run -d --name web -p HOST:CONTAINER IMAGE (rootless: HOST ≥ 1024) same on both | |
| Look after it | podman ps -a podman logs NAME podman exec -it NAME sh podman port NAME same on both | |
| Replace it | podman stop NAME podman rm NAME run the new tag podman rmi IMAGE same on both | |
| Open the port | sudo firewall-cmd --add-port=8080/tcp --permanent ; sudo firewall-cmd --reload | sudo ufw allow 8080/tcp |
| Share it | podman login REGISTRY podman tag IMG REGISTRY/you/IMG:TAG podman push REGISTRY/you/IMG:TAG same on both | |
Reliability numbers (SRE)
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| The words | SLI = what you measure SLO = your target SLA = a promise with a penalty KPI = a business number same on both | |
| Nines per 30 days | 99% = 7h12m 99.5% = 3h36m 99.9% = 43m 99.99% = 4m19s 99.999% = 26s same on both | |
| Availability from a log | awk '$9 < 500 {g++} END {printf "%.2f%%\n", 100*g/NR}' access.log same on both | |
| Percentiles | awk '{print $NF}' access.log | sort -n | awk '{t[NR]=$1} END {print t[int(NR*0.95)]}' same on both | |
| Error budget | budget = 1 − SLO burn rate 1 = spends it in exactly 30 days same on both | |
Monitoring (Prometheus)
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Ports | node_exporter :9100/metrics Prometheus :9090 Alertmanager :9093 same on both | |
| Check before (re)loading | promtool check config /etc/prometheus/prometheus.yml promtool check rules FILE sudo systemctl reload prometheus same on both | |
| Query | curl -s 'localhost:9090/api/v1/query?query=up' | jq promtool query instant http://localhost:9090 'EXPR' same on both | |
| PromQL | up rate(node_cpu_seconds_total{mode="idle"}[5m]) avg by (instance) (…) predict_linear(x[6h], 86400) same on both | |
| Alerts | curl -s localhost:9090/api/v1/alerts | jq pending → firing after for: same on both | |
| See the UI safely | ssh -L 9090:localhost:9090 you@server → http://localhost:9090 same on both | |
Incidents
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Order | acknowledge → 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.d | sudo journalctl -t sudo --since "1 hour ago" ls -lt /etc/apache2/sites-available |
| Config test | sudo apachectl configtest | sudo apache2ctl configtest |
| Update format | time (UTC) · what users see · what we know · what we’re doing · next update at same on both | |
Limits & OOM
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| OOM evidence | dmesg | grep -i -E "oom|killed process" | sudo dmesg | grep -i -E "oom|killed process" |
| Per-service usage | systemd-cgtop -m systemctl status NAME (Memory:) systemctl show NAME -p MemoryCurrent same on both | |
| Set limits now + saved | sudo systemctl set-property NAME MemoryMax=300M CPUQuota=50% same on both | |
| Other settings | sudo systemctl edit NAME → [Service] OOMScoreAdjust=-500 / LimitNOFILE=65536 same on both | |
| cgroup files | cat /sys/fs/cgroup/system.slice/NAME.service/memory.max same on both | |
Capacity & load testing
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Get ab | sudo dnf install httpd-tools | sudo 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 Law | in flight = throughput × latency same on both | |
| Growth | awk 'BEGIN { print log(target/now) / log(1 + rate) }' → months same on both | |
Performance triage (SRE)
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Get the tools | sudo dnf install sysstat | sudo apt install sysstat |
| The checklist | uptime 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 messages | dmesg | tail | sudo dmesg | tail |
| Disk hogs | pidstat -d 1 3 iostat -xz 1 3 (w_await, aqu-sz, %util) same on both | |
| Signs of trouble | load > 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 | |
| Timers | systemctl list-timers systemctl cat NAME.timer OnCalendar=*-*-* 02:00:00 sudo systemctl daemon-reload same on both | |
Compose
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Get it | sudo dnf install epel-release ; sudo dnf install podman-compose | sudo apt install podman-compose (or docker-compose-v2 → docker compose) |
| Run | podman-compose up -d ps logs [SERVICE] exec SERVICE sh restart SERVICE same on both | |
| Change | up -d (recreates what changed) up -d --build (new code) same on both | |
| Stop | down (keeps volumes) down -v (deletes volumes = data!) same on both | |
| Networking | containers reach each other by SERVICE NAME localhost = the container itself same on both | |
CI (GitHub Actions)
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Where | .github/workflows/NAME.yml → on: [push] jobs: runs-on: steps: uses / run same on both | |
| Pass or fail | exit code 0 = pass anything else = fail same on both | |
| From the terminal | gh run list gh run view gh run view ID --log-failed same on both | |
| Lint scripts | sudo dnf install ShellCheck (EPEL) ; shellcheck *.sh | sudo apt install shellcheck ; shellcheck *.sh |
Ansible
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Install | sudo dnf install epel-release ; sudo dnf install ansible | sudo apt install ansible |
| Inventory | [web] web1 ansible_host=192.168.1.61 ansible-inventory --graph same on both | |
| Ad-hoc | ansible all -m ping ansible web -a 'uptime' ansible web -b -m package -a 'name=tree' same on both | |
| Playbooks | ansible-playbook site.yml --check --diff ansible-playbook site.yml -l web1 same on both | |
| Both families | when: ansible_facts['os_family'] == "RedHat" / "Debian" or [rocky]/[ubuntu] groups + group_vars same on both | |
| Variables | group_vars/GROUP.yml host_vars/HOST.yml ansible-inventory --host HOST -e var=value (wins) same on both | |
| Loops & handlers | loop: "{{ list }}" + {{ item }} notify: Name → handlers: - name: Name same on both | |
| Roles | ansible-galaxy role init --init-path roles NAME roles: [NAME] defaults/ (low) vs vars/ (high) same on both | |
| Vault | ansible-vault encrypt|view|edit|decrypt FILE --ask-vault-pass vault_password_file = ~/.vault_pass same on both | |
| Rolling updates | serial: 1 max_fail_percentage: 0 uri + assert health check -l web1 -i inventories/production.ini same on both | |
| Checks | ansible-playbook X --syntax-check ansible-lint run twice → changed=0 --list-tasks --tags TAG same on both | |
Secrets & config
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| .env | echo "DB_PASSWORD=$(openssl rand -hex 16)" > .env ; chmod 600 .env ; echo .env >> .gitignore same on both | |
| Compose | REDIS_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 leaks | git log -p | grep -i pass gitleaks trufflehog same on both | |
| After a leak | ROTATE: new secret everywhere, old one must stop working same on both | |
Deploys
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Proxy switch | edit ProxyPass port ; sudo apachectl configtest ; sudo systemctl reload httpd | edit ProxyPass port ; sudo apache2ctl configtest ; sudo systemctl reload apache2 |
| Smoke test green first | curl localhost:8082/health curl localhost:8082/api/… same on both | |
| Strategies | recreate · rolling · blue-green (instant rollback) · canary (small slice first) same on both | |
| Rocky + proxy | sudo setsebool -P httpd_can_network_connect 1 same on both | |
AWS: basics
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Install CLI v2 | sudo dnf install unzip ; curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o awscliv2.zip ; unzip awscliv2.zip ; sudo ./aws/install | sudo 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 I | aws 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 | |
| IAM | aws iam create-user / create-group / add-user-to-group / attach-group-policy --policy-arn … create-access-key same on both | |
| Test a policy | aws iam simulate-principal-policy --policy-source-arn USER-ARN --action-names s3:PutObject same on both | |
AWS: network & servers
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| VPC | aws 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 subnet | create-internet-gateway + attach-internet-gateway create-route-table + create-route 0.0.0.0/0 → igw + associate-route-table same on both | |
| Security group | create-security-group authorize-security-group-ingress --protocol tcp --port 22 --cidr MY-IP/32 --source-group SG same on both | |
| Latest AMI | aws ssm get-parameter --name /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64 | aws ssm get-parameter --name /aws/service/canonical/ubuntu/server/24.04/stable/current/amd64/hvm/ebs-gp3/ami-id |
| Launch | aws 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 & find | aws ec2 wait instance-running --instance-ids $ID describe-instances --query 'Reservations[].Instances[].PublicIpAddress' same on both | |
| Log in | ssh -i key.pem ec2-user@IP (Amazon Linux) | ssh -i key.pem ubuntu@IP |
| First-boot log | tail /var/log/cloud-init-output.log same on both | |
| Lifecycle | stop-instances (new IP on start) · start-instances · terminate-instances (gone) same on both | |
AWS: S3
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Buckets & files | aws 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 | |
| Delete | aws s3 rm s3://B/KEY aws s3 rb s3://B --force same on both | |
| Website | aws 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 file | aws s3 presign s3://B/KEY --expires-in 300 same on both | |
| Versions | aws s3api put-bucket-versioning --bucket B --versioning-configuration Status=Enabled list-object-versions same on both | |
AWS: scale, DNS, alarms
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Load balancer | elbv2 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 Scaling | create-launch-template (UserData = base64 -w0) · autoscaling create-auto-scaling-group --min-size --max-size --desired-capacity same on both | |
| Scale on CPU | put-scaling-policy --policy-type TargetTrackingScaling … 'TargetValue':50 describe-scaling-activities same on both | |
| DNS records | aws route53 change-resource-record-sets --hosted-zone-id Z --change-batch file://c.json (CREATE · UPSERT · DELETE) same on both | |
| Check DNS | dig +short NAME dig NS DOMAIN dig @ns-X.awsdns-Y.com NAME same on both | |
| Alarm → email | sns create-topic · sns subscribe --protocol email · cloudwatch put-metric-alarm … --alarm-actions TOPIC-ARN same on both | |
| Guard the bill | billing alarm in us-east-1 (AWS/Billing EstimatedCharges) aws budgets create-budget same on both | |
AWS: costs & clean-up
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| This month | aws ce get-cost-and-usage --time-period Start=…,End=… --granularity MONTHLY --metrics UnblendedCost --group-by Type=DIMENSION,Key=SERVICE same on both | |
| Find by tag | aws resourcegroupstaggingapi get-resources --tag-filters Key=Project,Values=NAME same on both | |
| Every region | for r in $(aws ec2 describe-regions --query 'Regions[].RegionName' --output text); do aws ec2 describe-instances --region $r …; done same on both | |
| Usual leftovers | describe-addresses 'Addresses[?AssociationId==null]' · describe-volumes status=available · load balancers · hosted zones · buckets same on both | |
| Delete order | ASGs → instances → load balancers → target groups → IPs/volumes → buckets → DNS → network same on both | |
Git
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Install | sudo dnf install git | (preinstalled) sudo apt install git |
| Who am I | git config --global user.name "Name" user.email you@example.com same on both | |
| Start / copy a repo | git init git clone URL same on both | |
| Save a snapshot | git status git add FILE git commit -m "message" git commit -am "…" same on both | |
| Look back | git log --oneline git diff git diff --staged git show HASH same on both | |
| Undo changes | git restore FILE git restore --staged FILE git restore --source=HASH FILE same on both | |
| Fix the last commit (not pushed) | git add FILE → git commit --amend -m "…" --no-edit same on both | |
| Undo a pushed commit | git revert HASH → git push same on both | |
| Uncommit (not pushed) | git reset --soft HEAD~1 (keep staged) git reset HEAD~1 (keep files) --hard (throw away) same on both | |
| Lost commits | git reflog → git reset --hard HEAD@{N} git switch -c rescue HEAD@{N} same on both | |
| Branches | git branch git switch -c NAME git switch main git merge NAME same on both | |
| Conflict | edit the file (remove <<< === >>>) → git add FILE → git commit (rebase: git rebase --continue) same on both | |
| Park unfinished work | git stash git stash -u git stash list git stash pop same on both | |
| Copy one commit here | git cherry-pick HASH same on both | |
| Catch up with main (own branch) | git rebase main --continue --abort git pull --rebase same on both | |
| Share | git remote add origin URL git push -u origin main git pull same on both | |
| Install gh | sudo dnf install epel-release → sudo dnf install gh | sudo apt install gh |
| Pull request | git push -u origin BRANCH → gh pr create --title "…" --body "…" gh pr view --comments gh pr diff same on both | |
| Update a PR branch | git fetch → git rebase origin/main → git push --force-with-lease same on both | |
| Merge & tidy up | gh pr merge --squash --delete-branch → git switch main → git pull → git fetch --prune same on both | |
| Tag a release | git tag -a v1.2 -m "…" → git push origin v1.2 git describe gh release create v1.2 --generate-notes same on both | |
| Visit an old version | git switch --detach v1.0 → git switch - same on both | |
| Search history | git log -S TEXT --grep=WORD --author=NAME -- FILE git blame FILE same on both | |
| Find the bad commit | git bisect start → bisect bad → bisect good v1.1 → bisect run CMD → bisect reset same on both | |
| Track /etc (etckeeper) | sudo dnf install epel-release → sudo dnf install etckeeper etckeeper-dnf → sudo etckeeper init → sudo etckeeper commit "Initial commit" | sudo apt install etckeeper (init + first commit done for you) |
| /etc history & undo | sudo git -C /etc status / diff / log sudo etckeeper commit "why" sudo git -C /etc restore FILE same on both | |
| Never commit | passwords & keys → list them in .gitignore same on both | |
Python & AI
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Python tools | sudo dnf install python3-pip | sudo apt install python3-venv python3-pip |
| Make & use a venv | python3 -m venv ~/ai-env source ~/ai-env/bin/activate deactivate same on both | |
| Install Ollama | curl -fsSL https://ollama.com/install.sh | sh same on both | |
| Chat with a model | ollama run llama3.2 (/bye to quit) same on both | |
| Models: list / remove | ollama list ollama rm NAME same on both | |
Help!
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Manual page | man COMMAND (q to quit) same on both | |
| Quick help | COMMAND --help same on both | |
| Which command does…? | man -k WORD apropos WORD same on both | |
| Cancel / clear screen | Ctrl+C clear (or Ctrl+L) same on both | |
| Previous commands | ↑ history same on both | |
| Auto-complete | Tab same on both | |
Attack surface
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| What's listening (0.0.0.0 = everyone) | sudo ss -tlnp same on both | |
| Which services run | systemctl list-units --type=service --state=running same on both | |
| Accounts that can log in | getent passwd | awk -F: '$7 !~ /(nologin|false)$/ {print $1}' same on both | |
| Who is an admin | getent group wheel | getent group sudo |
| Turn a service off for good | sudo 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 wheel | sudo usermod -L NAME sudo usermod -s /sbin/nologin NAME sudo gpasswd -d NAME sudo |
SSH hardening
| Task | Rocky / RHEL | Ubuntu / 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 session | ssh-add ssh-add -l same on both | |
| Keys only, no root login | printf '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 uses | sudo sshd -T | grep -iE 'passwordauth|permitroot' same on both | |
| Apply it | sudo systemctl reload sshd | sudo 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 in | AllowUsers student maria (in the drop-in) same on both | |
| The drop-in that overrides you | 01-permitrootlogin.conf (PermitRootLogin yes) | 50-cloud-init.conf (PasswordAuthentication yes) |
Firewalls in depth
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| See the rules | sudo firewall-cmd --get-active-zones sudo firewall-cmd --list-all | sudo 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 network | sudo firewall-cmd --permanent --zone=internal --add-source=192.168.1.0/24 sudo firewall-cmd --permanent --zone=public --remove-service=ssh sudo firewall-cmd --reload | sudo ufw allow from 192.168.1.0/24 to any port 22 |
| Block one address | sudo 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 now | sudo firewall-cmd --runtime-to-permanent | (ufw saves every change) |
| Rate-limit SSH | rich rule: rule service name="ssh" limit value="3/m" accept | sudo ufw limit OpenSSH |
| Log what's blocked | sudo firewall-cmd --set-log-denied=all sudo journalctl -k | grep REJECT | sudo ufw logging on sudo journalctl -k | grep 'UFW BLOCK' |
Scanning your own lab
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Install nmap | sudo dnf install nmap | sudo apt install nmap |
| Which machines are up? | nmap -sn 192.168.1.0/24 same on both | |
| Common ports / every port | nmap 192.168.1.50 nmap -p- 192.168.1.50 same on both | |
| What program and version answers | nmap -sV -p 22,80,3306 192.168.1.50 same on both | |
| Inside view: what's listening | sudo ss -tlnp same on both | |
| MariaDB: localhost only | [mysqld] bind-address = 127.0.0.1 in /etc/my.cnf.d/99-bind.cnf | [mysqld] bind-address = 127.0.0.1 in /etc/mysql/mariadb.conf.d/99-bind.cnf |
| What MariaDB will read (last one wins) | my_print_defaults --mysqld same on both | |
Reading the login log
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| The login & sudo log | sudo tail /var/log/secure journalctl -u sshd | sudo tail /var/log/auth.log journalctl -u ssh |
| How many failed passwords? | sudo grep -c "Failed password" /var/log/secure | sudo grep -c "Failed password" /var/log/auth.log |
| Which addresses guess the most | … | grep -o "from [0-9.]*" | sort | uniq -c | sort -rn same on both | |
| Logins that worked | sudo grep Accepted /var/log/secure | sudo grep Accepted /var/log/auth.log |
| Logins / failures / who's on now | last sudo lastb | head w same on both | |
| Lock out and end a user's session | sudo usermod -L NAME sudo usermod -s /sbin/nologin NAME sudo pkill -KILL -u NAME same on both | |
Permission audit
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Every SUID program | sudo find / -perm -4000 -type f 2>/dev/null same on both | |
| Which package owns a file? | rpm -qf FILE | dpkg -S FILE |
| Remove SUID | sudo chmod u-s FILE same on both | |
| Files anyone can write | sudo find / -xdev -type f -perm -0002 2>/dev/null same on both | |
| /etc/shadow as it should be | sudo chmod 000 /etc/shadow (root root) | sudo chmod 640 /etc/shadow (root shadow) |
| What may a user run with sudo? | sudo -l -U NAME same on both | |
| Edit / check sudo rules safely | sudo visudo -f /etc/sudoers.d/FILE sudo visudo -c same on both | |
File integrity
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Save / check checksums | sha256sum FILES > list.sha256 sha256sum -c list.sha256 same on both | |
| Do a package's files still match? | rpm -V PKG sudo rpm -Va | sudo dpkg --verify PKG sudo dpkg --verify |
| Put a package's files back | sudo dnf reinstall PKG | sudo apt install --reinstall PKG |
| First AIDE database | sudo aide --init sudo mv /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz | sudo aideinit |
| What changed since? | sudo aide --check | sudo aide --config /etc/aide/aide.conf --check |
| Accept the changes | sudo aide --update then mv aide.db.new.gz → aide.db.gz | sudo aide --config /etc/aide/aide.conf --update then mv aide.db.new → aide.db |
| Any extra root accounts? | awk -F: '$3 == 0' /etc/passwd same on both | |
Web server hardening
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| What does the site give away? | curl -I http://localhost same on both | |
| Your own settings file | /etc/httpd/conf.d/security.conf | /etc/apache2/conf-available/security.conf |
| Hide the version | ServerTokens Prod ServerSignature Off same on both | |
| No folder listings | Options FollowSymLinks (take Indexes out) same on both | |
| Security headers | Header always set X-Content-Type-Options "nosniff" | sudo a2enmod headers then the same Header lines |
| HTTPS (test certificate) | sudo dnf install mod_ssl | sudo a2enmod ssl sudo a2ensite default-ssl |
| Real certificate | sudo certbot --apache -d your.domain sudo certbot renew --dry-run same on both | |
| Check, then apply | sudo apachectl configtest sudo systemctl reload httpd | sudo apache2ctl configtest sudo systemctl reload apache2 |