How to use this page
Find the job you're trying to do, and copy the pattern. Words in capitals, like FILE, NAME or HOST, are yours to replace. Every row links to the lesson that explains it, with real output. Use the print button at the top to keep a copy next to you.
Three habits that make everything here easier:
- Check where you are before you run anything. Your prompt says which machine you're on: your own computer, a container (
root@...) or a server (vagrant@bookshop,sam@bookshop). Most mistakes in this track were the right command on the wrong machine. - Read the last line first. Error messages end with the part that matters.
- Test, then apply.
nginx -t,sshd -tandvisudo -ccheck a settings change before it can break anything;reloadapplies it without dropping anyone.
When Linux says no
Start here when something fails. Match the message, then look where it says.
| The message says | It means | Look at | Lesson |
|---|---|---|---|
No such file or directory | The path doesn't exist from where you are, or has a typo | pwd, then ls the folder above it | 2, 3 |
command not found | No folder on your PATH has that program. Often it's simply not installed | which NAME, then apt search | 1, 8 |
Permission denied | Your user isn't allowed to do that to that file | ls -l FILE and id: owner, group, the three permission sets | 5 |
status=203/EXEC | systemd couldn't run the program in ExecStart | The path, and the x permission on the program | 7 |
status=217/USER | The User= in the unit file doesn't exist | id NAME | 7 |
Unable to locate package | apt doesn't know that name, often because the list is stale | sudo apt update, then apt search | 8 |
Address already in use | Something else is already listening on that port | sudo ss -tlnp | grep :PORT | 8 |
502 Bad Gateway | nginx is fine, but the program behind it didn't answer | nginx's error.log, and ss -tln for where the app really listens | 9, 14 |
Connection refused | The machine answered, but nothing is listening on that port | sudo ss -tlnp, and the app's port setting | 9 |
| Timed out, or curl exit code 28 | Nothing came back at all: often a firewall dropping traffic | Test another port on the same machine, then sudo ufw status | 9, 14 |
No space left on device | The disk is full | df -h, then sudo du -sh /path/* | sort -h | 10, 14 |
| df says full after you deleted the file | A program still has the deleted file open | sudo lsof +L1, then restart that program | 10 |
WARNING: UNPROTECTED PRIVATE KEY FILE! | Your private key is readable by others, so SSH won't use it | chmod 600 ~/.ssh/KEY | 11 |
Permission denied (publickey) | The server didn't accept your key, and won't take a password | On the server: sudo journalctl -u ssh, usually ownership or modes | 11 |
REMOTE HOST IDENTIFICATION HAS CHANGED! | The server's key isn't the one your computer remembers | A rebuilt server: ssh-keygen -R HOST. Otherwise, stop and find out why | 11, 15 |
sudo: a password is required | sudo -n wouldn't ask, and a password is needed | Run sudo -v on its own first | 11 |
unbound variable | set -u caught a variable that was never set | The variable's name, and where it should be set | 13 |
value too great for base | A number with a leading zero, like 09, was read as octal | date +%-S, or $((10#$n)) | 13 |
What am I standing on?
| To | Run | Lesson |
|---|---|---|
| Which kernel | uname -s | 1 |
| The kernel's version | uname -r | 1 |
| Which distribution, and its version | cat /etc/os-release | 1 |
| Which macOS version (Mac only) | sw_vers | 1 |
| Which user you are | whoami | 1 |
| The machine's name | hostname | 1 |
| What process 1 is | cat /proc/1/cmdline; echo | 1 |
| Run one command in a throwaway Linux container | docker run --rm ubuntu:24.04 <command> | 1 |
| Get a shell in one | docker run -it --rm ubuntu:24.04 bash | 1 |
Moving around and handling files
| To | Run | Lesson |
|---|---|---|
| See where you are | pwd | 2 |
| List a folder, including hidden files | ls -a | 2 |
| List with details, sizes readable | ls -lh | 2 |
| Details of a folder itself | ls -ld /tmp | 2 |
| Go somewhere | cd /var/log | 2 |
| Go up one folder | cd .. | 2 |
| Go home | cd ~ | 2 |
| Go back to where you were | cd - | 2 |
| Make folders, including missing parents | mkdir -p a/b/c | 2 |
| Make an empty file | touch file.txt | 2 |
| Copy a file | cp from.txt to.txt | 2 |
| Move or rename | mv old.txt new.txt | 2 |
| Delete a file (no undo) | rm file.txt | 2 |
| Delete an empty folder | rmdir folder | 2 |
| Delete a folder and everything in it (no undo) | rm -r folder | 2 |
| Find where a command lives | which ls | 2 |
Reading and searching
| To | Run | Lesson |
|---|---|---|
| List several folders at once | ls -l logs config | 3 |
| List everything, all the way down | ls -R config | 3 |
| Newest changes last | ls -ltr | 3 |
| Print a whole file | cat notes/todo.txt | 3 |
| First or last lines | head -20 file, tail -20 file | 3 |
| Count lines | wc -l file | 3 |
| Read page by page | less file | 3 |
| Follow a log as it's written | tail -f file | 3 |
| Find lines that match | grep pattern file | 3 |
| Ignore case, count, line numbers | grep -i, grep -c, grep -n | 3 |
| Lines that don't match | grep -v pattern file | 3 |
| Context around matches | grep -B2 -A2 pattern file | 3 |
| Whole words only | grep -w 8080 file | 3 |
| Search a whole folder; just the file names | grep -r pattern dir, grep -rl pattern dir | 3 |
| Which distribution, from its settings file | grep PRETTY_NAME /etc/os-release | 3 |
| Find files by name | find . -name "*.conf" | 3 |
| Folders only, or files only | find . -type d, find . -type f | 3 |
| Bigger than a size | find . -size +100M | 3 |
| Changed more than 30 days ago | find . -mtime +30 | 3 |
| Leave a folder out | find . -not -path './.git*' | 3 |
| Run a command on each file found | find . -size +1M -exec ls -lh {} ; | 3 |
| See the last command's exit code | echo $? | 3 |
Pipes, columns and counts
| To | Run | Lesson |
|---|---|---|
| Send one command's output into another | command1 | command2 | 4 |
| Print one column (any spaces) | awk '{print $1}' file | 4 |
| Print the last column | awk '{print $NF}' file | 4 |
| Only lines that meet a condition | awk '$9 >= 500' file | 4 |
| A condition, then one column | awk '$9 == 404 {print $7}' file | 4 |
| Add up a column | awk '{sum += $10} END {print sum}' file | 4 |
| A different separator | awk -F' = ' '{print $2}' file | 4 |
| One piece, with an exact separator | cut -d: -f2 file | 4 |
| Certain characters of each line | cut -c12-13 file | 4 |
| How many of each, biggest first | sort | uniq -c | sort -rn | 4 |
| Sort by number, or remove duplicates | sort -n, sort -u | 4 |
| Print certain lines | sed -n '10,20p' file | 4 |
| First and last line | sed -n '1p;$p' file | 4 |
| Delete lines that match | sed '/^#/d' file | 4 |
| Replace text (shows the result only) | sed 's/old/new/g' file | 4 |
| Replace text in the file, keeping a backup | sed -i.bak 's/old/new/' file | 4 |
| Save output, replacing the file | command > file | 4 |
| Add output to the end of a file | command >> file | 4 |
| Save errors | command 2> errors.txt | 4 |
| Save output and errors together | command > file 2>&1 | 4 |
| Throw errors away | command 2>/dev/null | 4 |
| Save and keep going | command | tee file | command2 | 4 |
| Sort a file into itself, safely | sort -o file file | 4 |
Users and permissions
| To | Run | Lesson |
|---|---|---|
| Who am I, and my groups | whoami, id | 5 |
| Someone else's ID and groups | id sam | 5 |
| List users or groups | cat /etc/passwd, cat /etc/group | 5 |
| A folder's own permissions | ls -ld folder | 5 |
| Permissions as a number | stat -c '%a %A %U %G %n' file | 5 |
| Add a user who can log in | useradd --create-home --shell /bin/bash sam | 5 |
| Add a user for an app | useradd --system --shell /usr/sbin/nologin bookshop | 5 |
Add someone to a group (keep -a) | usermod -aG bookshop sam | 5 |
| Run one command as another user | su -s /bin/bash bookshop -c 'command' | 5 |
| Log in as another user | su - sam | 5 |
| Change owner and group | chown bookshop:bookshop file | 5 |
| The same, for a folder and everything in it | chown -R bookshop:bookshop folder | 5 |
| Set permissions exactly | chmod 640 file, chmod 750 folder | 5 |
| Change just one thing | chmod +x script, chmod o-r file, chmod g+w file | 5 |
| Settings an app may read but not change | root:bookshop and 640 | 5 |
| Private: only the owner | chmod 600 file | 5 |
| Never | chmod 777 | 5 |
Processes and signals
| To | Run | Lesson |
|---|---|---|
| Every process | ps -ef, or ps aux for memory columns | 6 |
| One user's processes | ps -u bookshop | 6 |
| Choose the columns | ps -o pid,ppid,user,stat,etime,cmd -u bookshop | 6 |
| Biggest users of memory, or processor | ps aux --sort=-%mem | head, --sort=-%cpu | 6 |
| The family tree | ps -ef --forest | 6 |
| Find by name | pgrep -a name, exactly: pgrep -x name | 6 |
| Count matches | pgrep -c name | 6 |
Live view (q quits, M memory, P processor) | top | 6 |
| One snapshot of top | top -b -n 1 | head -15 | 6 |
| A process's details | cat /proc/PID/status | 6 |
| Run in the background | command & | 6 |
| List, front, back | jobs, fg %1, bg %1 | 6 |
| Pause what's in front | Ctrl+Z | 6 |
| Ask a process to stop | kill PID | 6 |
| Force it, as a last resort | kill -9 PID | 6 |
| Stop by exact name, for one user | pkill -x -u bookshop books-api | 6 |
| Pause and carry on | kill -STOP PID, kill -CONT PID | 6 |
| Keep going after logout | nohup command > job.log 2>&1 & | 6 |
| Exit code after a signal | 128 + its number: 143 TERM, 137 KILL, 130 INT | 6 |
Services and the journal
| To | Run | Lesson |
|---|---|---|
| Start, stop, restart | sudo systemctl start NAME, stop, restart | 7 |
| Start at every boot, and now | sudo systemctl enable --now NAME | 7 |
| Don't start at boot | sudo systemctl disable NAME | 7 |
| Everything about it | systemctl status NAME | 7 |
| Yes-or-no checks | systemctl is-active NAME, is-enabled | 7 |
| Its main process number | systemctl show -p MainPID --value NAME | 7 |
| After editing a unit file | sudo systemctl daemon-reload | 7 |
| Show a unit file, with overrides | systemctl cat NAME | 7 |
| Override a packaged unit safely | sudo systemctl edit NAME | 7 |
| What's running, what's failed | systemctl list-units --type=service --state=running, systemctl --failed | 7 |
| A service's logs | journalctl -u NAME -n 50 | 7 |
| Follow them live | journalctl -u NAME -f | 7 |
| Since a time, or this boot | journalctl -u NAME --since "1 hour ago", -b | 7 |
| Anything that went wrong this boot | journalctl -p warning -b | 7 |
| Write a file as root | echo "text" | sudo tee FILE | 7 |
| Start, log in to, restart the server | vagrant up, vagrant ssh, vagrant reload, on your own computer | 7 |
Installing software
| To | Run | Lesson |
|---|---|---|
| Refresh the package list (do this first) | sudo apt update | 8 |
| Find a package | apt search NAME, exactly: apt search --names-only '^NAME$' | 8 |
| Describe it | apt show NAME | 8 |
| Installed and available versions | apt-cache policy NAME | 8 |
| Install | sudo apt install -y NAME | 8 |
| Remove, keeping settings | sudo apt remove NAME | 8 |
| Remove with its settings | sudo apt purge NAME | 8 |
| Clear packages nothing needs | sudo apt autoremove | 8 |
| What can be updated | apt list --upgradable | 8 |
| Update everything | sudo apt upgrade | 8 |
| Which package owns a file | dpkg -S /usr/sbin/nginx | 8 |
| Every file in a package | dpkg -L NAME | 8 |
Installed, or only settings left (rc) | dpkg -l NAME | 8 |
| Check nginx's settings | sudo nginx -t | 8 |
| Apply them without dropping connections | sudo systemctl reload nginx | 8 |
| Switch an nginx site on | sudo ln -s /etc/nginx/sites-available/NAME /etc/nginx/sites-enabled/NAME | 8 |
| nginx's logs | /var/log/nginx/access.log, error.log | 8 |
| Run a PostgreSQL command | sudo -u postgres psql -c 'SELECT version();' | 8 |
| Make a database, list them | sudo -u postgres createdb NAME, sudo -u postgres psql -l | 8 |
Networking
| To | Run | Lesson |
|---|---|---|
| The server's addresses | ip -br addr, or just hostname -I | 9 |
| Where traffic goes, and the gateway | ip route | 9 |
| What's listening, on which port and address | sudo ss -tlnp | 9 |
| Who's on one port | sudo ss -tlnp | grep :8080 | 9 |
| Connections open right now | sudo ss -tnp | 9 |
| Turn a name into an address | getent hosts NAME | 9 |
| Name a machine yourself | echo "ADDRESS NAME" | sudo tee -a /etc/hosts | 9 |
| Is the machine reachable? | ping -c 2 HOST | 9 |
| Is the port open? | nc -zv HOST PORT | 9 |
| Does the web service answer? | curl -si http://HOST/PATH, every step: curl -v | 9 |
| Don't wait forever | curl --max-time 5, then echo $?: 7 refused, 28 timed out, 6 no name | 9 |
| Firewall: allow SSH first, then switch on | sudo ufw allow OpenSSH, then sudo ufw enable | 9 |
| Allow a port | sudo ufw allow 80/tcp | 9 |
| See the rules, numbered | sudo ufw status numbered | 9 |
| Remove a rule | sudo ufw delete allow 80/tcp, or sudo ufw delete NUMBER | 9 |
Disks and memory
| To | Run | Lesson |
|---|---|---|
| How full is every filesystem | df -h | 10 |
| Out of inodes? | df -i | 10 |
| Disks, partitions and mounts | lsblk | 10 |
| Biggest folders, biggest last | sudo du -sh /var/* | sort -h | tail | 10 |
| Big files, on one filesystem | sudo find / -xdev -type f -size +100M -exec ls -lh {} ; | 10 |
| How much the journal uses | journalctl --disk-usage | 10 |
| Make a filesystem (erases the disk) | sudo mkfs.ext4 /dev/sdb | 10 |
| Mount, and unmount | sudo mount /dev/sdb /srv/data, sudo umount /srv/data | 10 |
| Where something is mounted | findmnt /srv/data | 10 |
| A disk's unchanging ID, for fstab | sudo blkid | 10 |
| Check fstab before a reboot does | sudo findmnt --verify, then sudo systemctl daemon-reload | 10 |
| Mount everything in fstab | sudo mount -a | 10 |
| Empty a log that's in use | sudo truncate -s 0 FILE | 10 |
| Deleted files still taking space | sudo lsof +L1 | 10 |
| Memory: read the available column | free -h | 10 |
| Biggest users of memory | ps aux --sort=-%mem | head | 10 |
| Is there swap? | swapon --show | 10 |
| Was anything killed for memory? | sudo journalctl -k | grep -i oom | 10 |
| Clear downloaded packages | sudo apt clean | 10 |
SSH, keys and sudo
| To | Run | Lesson |
|---|---|---|
| Make a key pair, with a passphrase | ssh-keygen -t ed25519 -f ~/.ssh/NAME -C "comment" | 11 |
| A key's fingerprint | ssh-keygen -lf ~/.ssh/NAME.pub | 11 |
| A server's host key fingerprint (on the server) | sudo ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub | 11 |
| Log in with a key | ssh -i ~/.ssh/NAME user@HOST | 11 |
| Run one command, and come back | ssh HOST 'command' | 11 |
| A command that needs a terminal, like sudo | ssh -t HOST 'sudo command' | 11 |
| Never prompt, just fail | ssh -o BatchMode=yes HOST | 11 |
| Copy a file to a server | scp FILE HOST: | 11 |
| A short name for a server | A Host block in ~/.ssh/config | 11 |
| Permissions SSH expects | Private key 600, ~/.ssh 700, authorized_keys 600, home folder not writable by others | 11 |
| Why a login failed (on the server) | sudo journalctl -u ssh -n 50 | 11 |
| What a user may do with sudo | sudo -l -U NAME | 11 |
| Check sudo's rules | sudo visudo -c, a new file first: sudo visudo -cf FILE | 11 |
| Keys only, no root | PasswordAuthentication no, PermitRootLogin no in /etc/ssh/sshd_config.d/10-NAME.conf | 11 |
| Check, apply, and confirm SSH's settings | sudo sshd -t, sudo systemctl reload ssh, sudo sshd -T | 11 |
| Type your sudo password now, so later commands don't stop to ask | sudo -v, on its own line, before pasting several commands | 11 |
| Make sudo forget your password straight away | sudo -k | 11 |
Loops, tests and one-liners
| To | Run | Lesson |
|---|---|---|
| See the last exit status | echo $? | 12 |
| Run B only if A worked, or failed | A && B, A || B | 12 |
| Ask a yes-or-no question | grep -q TEXT FILE, systemctl is-active --quiet NAME | 12 |
| Test a file, folder or number | [ -f PATH ], [ -d PATH ], [ "$n" -ge 10 ] | 12 |
| Choose between two paths | if TEST; then ...; else ...; fi | 12 |
| Once for each file | for f in *.csv; do CMD "$f"; done | 12 |
| Once for each number | for i in {1..5}; do ...; done | 12 |
| Once for each line of a file | while IFS=: read -r a b rest; do ...; done < FILE | 12 |
| Wait until something works | until CMD; do sleep 1; done | 12 |
| Leave a loop early | break | 12 |
| Cut an ending off a name | "${f%.csv}" | 12 |
| Many files, anywhere in a folder | find DIR -name "*.log" -print0 | xargs -0 CMD | 12 |
| See what the loop would do | Put echo in front of the command | 12 |
| A shortcut, and a shortcut with arguments | alias NAME="CMD", NAME() { CMD "$@"; }, in ~/.bashrc or ~/.zshrc | 12 |
| What is this command, really? | type NAME | 12 |
Scripts, cron and timers
| To | Run | Lesson |
|---|---|---|
| Start every script | #!/bin/bash, then set -euo pipefail | 13 |
| Make it runnable | chmod +x FILE, run with ./FILE, or put it in /usr/local/bin | 13 |
| Its arguments | $1, $2, $# (how many), "$@" (all, each whole) | 13 |
| Clean up however it ends | trap 'rm -f "$tmp"' EXIT | 13 |
| A safe temporary file | tmp=$(mktemp) | 13 |
| Private files from a script | umask 077 | 13 |
| Check a script | bash -n FILE, shellcheck FILE | 13 |
| A cron job | MIN HOUR DAY MONTH WEEKDAY USER COMMAND >> LOG 2>&1 in /etc/cron.d/NAME | 13 |
| What cron ran | sudo journalctl -u cron | 13 |
A % in a cron line | %, or move the command into a script | 13 |
| Check a timer schedule | systemd-analyze calendar "--* 02:30:00" | 13 |
| Turn a timer on, and see it | sudo systemctl enable --now NAME.timer, systemctl list-timers | 13 |
| Run a timer's job now | sudo systemctl start NAME.service | 13 |
| Back up, and restore, a PostgreSQL database | pg_dump DB | gzip > FILE.sql.gz, zcat FILE.sql.gz | psql -d NEWDB | 13 |
| Numbers with a leading zero | date +%-S, or $((10#$n)) | 13 |
First checks, by symptom
| To | Run | Lesson |
|---|---|---|
| Anything at all | What changed? systemctl is-active NAME, sudo journalctl -u NAME -n 50 | 14 |
| 502 or 504 from nginx | sudo tail /var/log/nginx/error.log, ss -tln, the app's port setting | 14 |
| Service won't start | systemctl status NAME, the journal from the bottom up, sudo -u USER to test as the service's user | 14 |
| Can't write, job failed | df -h, then sudo du -ah DIR | sort -h | tail | 14 |
| Slow | uptime against nproc, top, free -h, systemctl status PID | 14 |
| Settings change broke it | sudo nginx -t, sudo sshd -t, sudo visudo -c | 14 |
| Works inside, not outside | curl -m 5 from outside, sudo ufw status, sudo journalctl -k | grep "UFW BLOCK" | 14 |
| Applying a settings change | Test, then reload: sudo nginx -t && sudo systemctl reload nginx | 14 |
Setup scripts and audits
| To | Run | Lesson |
|---|---|---|
| Install a file with mode and owner | install -m 640 -o root -g bookshop SRC DEST | 15 |
| Are two files identical? | cmp -s A B | 15 |
| A file's mode and owner | stat -c '%a %U:%G' FILE | 15 |
| Add a line only once | grep -q LINE FILE || echo LINE >> FILE | 15 |
| Create a user only once | id NAME || useradd ... | 15 |
| Create a table only once | CREATE TABLE IF NOT EXISTS ... | 15 |
| Test, then apply, settings | sshd -t then systemctl try-reload-or-restart ssh; nginx -t then systemctl reload nginx, each on its own line | 15 |
| See invisible characters | cat -A FILE; strip \r with sed -i 's/\r$//' FILE | 15 |
In a pipe, with pipefail | grep PATTERN > /dev/null, not grep -q | 15 |
| Build, re-run, rebuild with Vagrant | vagrant up, vagrant provision, vagrant destroy -f | 15 |
| Forget a rebuilt server's old key | ssh-keygen -R ADDRESS | 15 |
On a Mac
Your Mac's terminal looks like Linux, but its tools come from BSD, a different family (Lesson 1). The ones that differ in this track:
| To | On Linux | On a Mac |
|---|---|---|
| Edit a file in place, no backup | sed -i 's/a/b/' FILE | sed -i '' 's/a/b/' FILE |
| A file's permissions as a number | stat -c '%a' FILE | stat -f '%OLp' FILE |
| Which version of a tool | sed --version | Usually no --version; check man sed |
| Services and logs | systemctl, journalctl | Neither exists: macOS uses launchctl and log |
| Install software | apt | Homebrew: brew install NAME |
When a command from this page fails on your Mac, check which machine you meant to run it on first. If it really is meant for the Mac, this table is the usual reason.