Linux cheat sheet

A scannable Linux reference: 39 short snippets across 14 topics, each linking back to the lesson it came from.

At a glance

TopicWhat it covers
Shell basics and navigationThe shell expands globs before the command runs, and expands variables inside double quotes but not single quotes. Getlesson
Files and permissionsPermissions are evaluated in order: owner, then group, then others. The first category you match decides what you get —lesson
Working with text| sends one program's output into the next. File descriptor 1 is stdout, 2 is stderr — that is why 2>&1 meanslesson
Processes, ports and jobs'Address already in use' means something holds the port. Find it with lsof -i :PORT rather than blindly restartinglesson
SSH, packages and servicesKey-based login without passwords, copying files safely, installing software, and managing serviceslesson
Users, groups and privilegeEvery process runs as a numeric user id. Names are only a convenience: the kernel checks the UID and GID, and root islesson
Installing software across distributionsLinux distributions ship a package manager that resolves dependencies against signed repositories. The commands differlesson
Disks, filesystems and mountsBlock devices, partitions, mkfs, fstab entries by UUID, and extending a volume without losing datalesson
Networking: addresses, DNS and firewallsInterfaces and routes, DNS resolution, the tools that prove where a connection dies, and firewall rules that do notlesson
Archiving, syncing and backupstar and compression formats, rsync options and semantics, incremental snapshots, and a backup you have actuallylesson
Scheduling work: cron and systemd timersCron runs your command through /bin/sh with a nearly empty environment and a working directory of $HOME. Aliases, nodelesson
systemd units and reading logsUnit types and dependencies, a service unit you can trust, restart policies, and getting real answers out of journalctllesson
Environment, dotfiles and shell configurationVariables and PATH precedence, which startup file runs when, and keeping dotfiles reproducible across machineslesson
Performance triage and troubleshootingA repeatable method for a slow server: load, memory, CPU, disk and I/O, then syscall-level evidencelesson

Quick snippets

Shell basics and navigation

Paths

pwd                 # print working directory
ls -la              # long list, including hidden files
cd /var/log
cd ~/projects
cd -

# tab completion prevents typos
cat /etc/ho<TAB>

The daily dozen

mkdir -p src/components      # -p creates parents, no error if exists
touch notes.txt
cp file.txt backup.txt
cp -r src/ src.bak/
mv old.txt new.txt
rm -i important.txt          # ask before deleting

Wildcards and quoting

ls *.js            # zero or more characters
ls file?.txt       # exactly one character
cp src/*.js dist/

grep 'error 500' app.log      # quote anything with spaces
grep "$PATTERN" app.log       # expand the variable
grep '$100' prices.txt        # single quotes keep the $ literal

Full lesson: Shell basics and navigation →

Files and permissions

Reading ls -l

-rwxr-xr--  1 ada dev  4096 Sep 17 10:00 deploy.sh
│└┬┘└┬┘└┬┘
│ │  │  └── others:  r--  (read only)
│ │  └───── group:   r-x  (read + execute)
│ └──────── owner:   rwx  (full)
└────────── file type: - file, d directory, l symlink

Changing permissions

chmod +x deploy.sh          # add execute for everyone
chmod u+x,g-w file          # symbolic: user +x, group -w
chmod 755 script.sh         # numeric: owner 7, group 5, others 5
chmod 600 id_rsa            # private key: owner read/write only
chmod -R 644 public/        # recursive (careful!)

Ownership

chown ada:dev file.txt      # owner:group
chown -R www-data:www-data /srv/app

id                          # who am I, which groups
groups
usermod -aG docker ada      # append to a group (needs logout)

Full lesson: Files and permissions →

Working with text

Looking at files

less app.log       # space to page, / to search, q to quit
head -20 app.log
tail -50 app.log
tail -f app.log    # follow - watch new lines arrive

nl file.txt        # numbered lines
wc -l file.txt     # count lines

Searching with grep

grep 'ERROR' app.log
grep -i 'error' app.log          # case-insensitive
grep -n 'timeout' app.log        # include line numbers
grep -r 'TODO' src/              # recursive
grep -v 'DEBUG' app.log          # invert: lines NOT matching
grep -E '4[0-9]{2}|5[0-9]{2}' access.log   # extended regex
grep -c 'ERROR' app.log          # count matching lines

Pipes and redirection

cat app.log | grep ERROR | wc -l      # count errors
ps aux | grep node | grep -v grep
history | awk '{print $2}' | sort | uniq -c | sort -rn | head

grep ERROR app.log > errors.txt       # overwrite
grep ERROR app.log >> errors.txt      # append
command 2> errors.txt                 # stderr only
command > out.txt 2>&1                # both together

Full lesson: Working with text →

Processes, ports and jobs

What is running

ps aux | grep nginx
top                    # live overview, q to quit
htop                   # friendlier top (install if missing)
pgrep -l node

pstree -p | head       # parent/child relationships

Stopping processes

kill 1234          # SIGTERM - polite request to stop
kill -9 1234       # SIGKILL - cannot be caught, use last
pkill -f 'node app'
killall nginx

Ports and connections

ss -ltnp                 # listening TCP ports + process
lsof -i :3000            # who owns port 3000
ss -tuln

curl -I localhost:3000   # quick health check
curl -s -o /dev/null -w '%{http_code} %{time_total}s\n' https://example.com

Full lesson: Processes, ports and jobs →

SSH, packages and services

Connecting

ssh [email protected]
ssh -p 2222 [email protected]
ssh -i ~/.ssh/id_ed25519 ada@server

# keys beat passwords: immune to brute force, better automation
ssh-keygen -t ed25519 -C 'ada@example'
ssh-copy-id ada@server

Connecting

# ~/.ssh/config - then just: ssh web
Host web
  HostName 203.0.113.10
  User ada
  Port 2222
  IdentityFile ~/.ssh/id_ed25519

Copying files

scp file.txt ada@web:/var/www/
scp -r ./dist ada@web:/var/www/app/
scp ada@web:/var/log/app.log ./      # download

# rsync: only transfers differences, resumable
rsync -avz --progress ./dist/ ada@web:/var/www/app/
rsync -avz --exclude 'node_modules/' ./ ada@web:/srv/app/

Full lesson: SSH, packages and services →

Users, groups and privilege

Accounts, UID and the password files

whoami              # effective user name
id                  # uid, gid and every supplementary group
groups

getent passwd ada   # resolves through NSS, so LDAP/SSSD accounts work too
getent passwd www-data   # a service account: no login shell by design

Delegating privilege with sudo

sudo -l                     # what am I allowed to run?
sudo -u postgres psql       # run one command as another user
sudo -i                     # interactive root login shell
sudo -s                      # root shell that keeps your environment

sudo visudo                 # validates syntax before saving
sudo visudo -f /etc/sudoers.d/deploy

Delegating privilege with sudo

# /etc/sudoers.d/deploy
# %group syntax applies to every member of the group
%developers ALL=(ALL) ALL
deploy     ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart nginx, /usr/bin/systemctl status nginx

Full lesson: Users, groups and privilege →

Installing software across distributions

apt and dpkg in practice

# add a third-party repository the careful way
curl -fsSL https://example.com/key.gpg | sudo gpg --dearmor -o /usr/share/keyrings/example.gpg
echo "deb [signed-by=/usr/share/keyrings/example.gpg] https://example.com/apt stable main" |
  sudo tee /etc/apt/sources.list.d/example.list
sudo apt update

Beyond the system package manager

sudo dnf install nginx
sudo dnf history                     # every transaction, and its id
sudo dnf history undo 42             # roll one back
dnf provides "*/nginx.conf"          # which package ships this path

sudo pacman -Syu                     # refresh and upgrade together (Arch)

# building from source when no package fits
sudo apt install build-essential pkg-config
./configure --prefix=/usr/local && make -j"$(nproc)" && sudo make install

Full lesson: Installing software across distributions →

Disks, filesystems and mounts

Seeing what you have

lsblk -f                    # tree of disks, partitions, filesystems, UUIDs
lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT
blkid                       # UUID and filesystem type per device
df -h                       # space per mounted filesystem
df -i                       # inode usage - full inodes look like a full disk
du -sh /var/log/* | sort -h | tail

findmnt /                    # where a mount came from and with which options
mount | column -t

Partitioning, formatting and LVM

# LVM: grow a filesystem later without downtime
sudo pvcreate /dev/sdc
sudo vgcreate vgdata /dev/sdc
sudo lvcreate -L 20G -n app vgdata
sudo mkfs.ext4 /dev/vgdata/app

# later, after adding a disk
sudo pvcreate /dev/sdd
sudo vgextend vgdata /dev/sdd
sudo lvextend -r -l +100%FREE /dev/vgdata/app    # -r resizes the filesystem too

Full lesson: Disks, filesystems and mounts →

Networking: addresses, DNS and firewalls

Interfaces, routes and DNS

ip -br a                       # one line per interface: up/down plus address
ip a show eth0
ip -4 a
ip r                           # routing table; look for the default gateway
ip neigh                       # ARP/neighbour cache

nmcli device status
nmcli con show eth0
resolvectl status              # what systemd-resolved actually uses
cat /etc/resolv.conf           # often a stub pointing at 127.0.0.53
cat /etc/hosts                 # checked before DNS

Interfaces, routes and DNS

dig example.com A +short
dig example.com MX
dig -x 203.0.113.10            # reverse lookup

getent hosts example.com       # the same path the application library uses
# getent honours /etc/hosts and NSS; dig does not - compare the two when they disagree

Proving where a connection dies

ping -c 3 1.1.1.1            # is there a path at all?
mtr -rwzc 20 example.com     # per-hop loss, better than a single traceroute
traceroute -T -p 443 example.com

curl -v https://example.com              # DNS, TCP, TLS and HTTP in one trace
curl -sS -o /dev/null -w '%{http_code} %{time_connect}s %{time_total}s\n' https://example.com
curl --resolve example.com:443:203.0.113.10 https://example.com   # test before DNS changes

sudo ss -tnp state established        # who is connected right now
sudo tcpdump -i any -nn port 443 -c 20

Full lesson: Networking: addresses, DNS and firewalls →

Archiving, syncing and backups

tar and compression

tar -czf app-2026-09-18.tar.gz -C /srv app       # archive a directory
tar -tzf app-2026-09-18.tar.gz | head            # list without extracting
tar -xzf app-2026-09-18.tar.gz -C /tmp/restore   # extract somewhere else

tar --exclude='node_modules' --exclude='*.log' -czf backup.tar.gz -C /srv app
tar -cJf archive.tar.xz big-directory            # xz: smallest, slowest
tar -c --zstd -f archive.tar.zst dir             # zstd: fast and small
tar -cf - dir | zstd -T0 > dir.tar.zst           # compress in parallel

id                                        # note the numeric uid/gid of the owner
tar --numeric-owner -xvzf backup.tar.gz   # restore ownership as recorded

rsync and what the trailing slash means

rsync -av --progress src/ dest/          # contents of src into dest
rsync -av  src  dest/                    # the directory src itself, inside dest

rsync -av --delete --dry-run ./public/ deploy@web:/var/www/public/
rsync -av --delete ./public/ deploy@web:/var/www/public/

rsync -avz --partial --append-verify big.iso deploy@web:/srv/
rsync -av -e "ssh -p 2222" ./data/ deploy@web:/srv/data/
rsync -av --exclude-from=.rsyncignore ./ ./backup/

# incremental snapshots: unchanged files become hard links, storage stays flat
rsync -av --link-dest=../2026-09-17 ./ /backups/2026-09-18/

Full lesson: Archiving, syncing and backups →

Scheduling work: cron and systemd timers

Crontab syntax

crontab -e                # edit your own crontab
crontab -l                # list it
sudo crontab -u deploy -l # someone else's
sudo crontab -u deploy -e

Crontab syntax

# /etc/cron.d/nightly-backup (root, note the user column)
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
[email protected]
30 2 * * *  deploy  /opt/app/backup.sh >> /var/log/backup.log 2>&1

# in a personal crontab there is no user column
15 3 * * 0  /usr/bin/find /srv/tmp -type f -mtime +14 -delete

The environment trap

#!/usr/bin/env bash
# a cron-friendly script: no assumptions about the caller
set -euo pipefail

export PATH="/usr/local/bin:/usr/bin:/bin"
cd /opt/app || exit 1
. /opt/app/venv/bin/activate

echo "$(date -Is) starting"
/opt/app/bin/run.sh
echo "$(date -Is) finished with status $?"

Full lesson: Scheduling work: cron and systemd timers →

systemd units and reading logs

Units and dependencies

systemctl list-units --type=service --state=running
systemctl list-unit-files 'app*'
systemctl cat app.service            # every file that contributes to the unit
systemctl show app -p ExecStart -p Restart -p User
systemctl status app --no-pager

systemctl daemon-reload              # after ANY unit file change
journalctl -u app -n 100 --no-pager

journalctl without guessing

journalctl -u app -f                       # follow one unit
journalctl -u app --since "1 hour ago" --no-pager
journalctl -u app --since "2026-09-18 08:00" --until "2026-09-18 09:00"
journalctl -b -p err                       # errors since the last boot
journalctl -b -1 -p warning                # the previous boot
journalctl _PID=1234                       # everything one process logged
journalctl -o json-pretty -n 1             # all structured fields
journalctl --disk-usage
sudo journalctl --vacuum-time=14d
sudo journalctl --vacuum-size=500M

journalctl without guessing

# /etc/systemd/journald.conf
Storage=persistent      # keep logs across reboots
SystemMaxUse=1G
MaxRetentionSec=1month
ForwardToSyslog=yes     # also feed rsyslog if you collect logs centrally

Full lesson: systemd units and reading logs →

Environment, dotfiles and shell configuration

Variables, export and PATH

NAME=ada                   # shell variable: not visible to child processes
export NAME                # now it is in the environment of every child
export EDITOR=vim LOG_LEVEL=info   # multiple at once

env | sort | head          # the whole environment
printenv PATH
printenv LANG || echo unset

MYVAR=local bash -c 'echo $MYVAR'    # scoped to one command only
unset MYVAR

Variables, export and PATH

echo "$PATH" | tr ':' '\n'     # one entry per line, in search order
export PATH="$HOME/.local/bin:$PATH"   # prepend: your version wins
export PATH="$PATH:/opt/tool/bin"      # append: fallback

type -a python3              # every definition: alias, function, file
command -v python3           # the file that would run
hash -r                      # forget the shell's command cache after a move

Keeping dotfiles reproducible

bash -n ~/.bashrc            # syntax check without running it
bash -x ~/.bashrc 2>&1 | head -30    # trace what actually executes
echo "$PROMPT_COMMAND"

Full lesson: Environment, dotfiles and shell configuration →

Performance triage and troubleshooting

A method before a tool

uptime                 # load average over 1, 5 and 15 minutes
free -h                # memory, buffers/cache and swap
df -h /                # space; df -i for inodes
dmesg -T | tail -30    # kernel messages: OOM kills, I/O errors, link flaps
systemctl --failed

The main tools and what each answers

sudo apt install sysstat          # provides iostat, pidstat and sar
vmstat 1 5
iostat -xz 1 3
sudo iotop -oPa
pidstat -u -r -d 1 5

# read the columns that matter
# vmstat: r = runnable, b = blocked, si/so = swap in/out,
#         us/sy/id = cpu split, wa = waiting on I/O
# iostat: %util near 100 and high await = the device is the bottleneck

Going deeper

# raise the limit for a service, not for the whole machine
# /etc/systemd/system/app.service.d/limits.conf
[Service]
LimitNOFILE=65535

sudo systemctl daemon-reload && sudo systemctl restart app

# a container needs it too - it inherits the daemon's limit
docker run --ulimit nofile=65535:65535 app:1.0

Full lesson: Performance triage and troubleshooting →

FAQ

Is this Linux cheat sheet free to use?
Yes. No sign-up and no tracking: the page is static, every example is on the page itself, and you can print it or save it as a one-page reference.
Where do the examples come from?
Every snippet is taken from the 14 lessons of the Linux course on this site, and each section links back to the lesson it was pulled from.
How do I go deeper than a cheat sheet?
Open the full Linux course — it carries the worked explanations, the edge cases and the exercises behind every line here.

Git Docker Kubernetes Nginx CI / CD Bash Scripting

Last refreshed 2026-09-27.