Core commands, filesystem and permissions reference, and everyday sysadmin tasks for the Linux CLI.
| Category | Environment / Scope | Name / Utility | Syntax / Combo | Common Flags / Options | Real Usage Example | Description & Functionality |
|---|---|---|---|---|---|---|
| 8. Services & Packages | Debian/Ubuntu | apt / dpkg | apt update && apt install |
update, upgrade, install, purge, dpkg -i | sudo apt update && sudo apt install -y htop |
Package management suite for Debian and Ubuntu based distributions. |
| 8. Services & Packages | POSIX / All | at / nohup | at [time] / nohup cmd & |
nohup.out redirection | nohup python3 script.py > app.log 2>&1 & |
Executes one-time delayed jobs (at) or runs tasks detached from terminal SIGHUP disconnects (nohup). |
| 3. Text Processing | POSIX / All | awk | awk 'program' file |
-F [delimiter], print $N, BEGIN/END blocks | awk -F':' '{print $1, $7}' /etc/passwd |
Pattern scanning language optimized for parsing columnated log files and structured text. |
| 5. Process & Memory | POSIX / All | bg / fg / jobs | bg [%job] / fg [%job] / jobs |
-l (show PIDs) | jobs -l && fg %1 |
Lists suspended jobs and manages foreground/background process execution in current shell. |
| 3. Text Processing | POSIX / All | cat | cat [options] file |
-n (line numbers), -A (show non-printing) | cat -n /etc/hosts |
Concatenates file contents and prints directly to standard output. |
| 2. File & Directory | POSIX / All | cd | cd [dir] |
- (previous dir), ~ (home) | cd /etc/nginx && cd - |
Changes active directory location. cd - toggles back to previous path. |
| 4. Permissions & Users | POSIX / All | chgrp | chgrp group file |
-R (recursive) | chgrp -R developers /opt/project |
Changes group ownership of target files and directories. |
| 4. Permissions & Users | POSIX / All | chmod | chmod [mode] file |
755, 644, +x, -R (recursive) | chmod +x deploy.sh && chmod 600 id_rsa |
Modifies file mode bits for Owner, Group, and Others using octal (755) or symbolic (+x) notation. |
| 4. Permissions & Users | POSIX / All | chown | chown owner:group file |
-R (recursive), --reference | chown -R www-data:www-data /var/www |
Changes target file or directory system user ownership and group assignment. |
| 3. Text Processing | POSIX / All | cmp | cmp [options] file1 file2 |
-l (list byte numbers), -s (silent mode) | cmp -s file1.bin file2.bin |
Compares two files byte by byte to check for identical binary data. |
| 2. File & Directory | POSIX / All | cp | cp [options] src dst |
-r (recursive), -p (preserve permissions), -a (archive) | cp -a /etc/nginx /etc/nginx.bak |
Copies files and directories. Option -a preserves ownership, mode, and timestamps. |
| 8. Services & Packages | POSIX / All | crontab | crontab [options] |
-e (edit), -l (list), -r (remove), -u [user] | crontab -e / crontab -l |
Schedules automated recurring background tasks using cron timing syntax. |
| 1. Terminal Keys | Shortcut | Cut Line / Kill Buffer | Ctrl + U / Ctrl + K |
Readline Cut Buffer | Ctrl + U (Cut to start) |
Cuts line from cursor to beginning (Ctrl+U) or cursor to end (Ctrl+K). Paste with Ctrl+Y. |
| 7. Networking | POSIX / All | curl / wget | curl [opts] URL / wget [opts] URL |
-I (headers), -X (POST/PUT), -O, -c (continue download) | curl -Is https://api.github.com |
Downloads web assets and interacts with REST APIs over HTTP/HTTPS/FTP. |
| 3. Text Processing | POSIX / All | cut | cut [options] file |
-d [delimiter], -f [fields], -c [characters] | cut -d',' -f1,3 data.csv |
Extracts specific character ranges or delimited fields from lines in text files. |
| 6. System & Storage | POSIX / All | df | df [options] |
-h (human-readable), -T (fs type), -i (inodes) | df -hT |
Displays total disk space usage and free capacity for all mounted filesystems. |
| 3. Text Processing | POSIX / All | diff | diff [options] file1 file2 |
-u (unified output), -y (side-by-side), -r (recursive) | diff -u config.orig config.new |
Compares two text files line by line and outputs differences (used to generate patch files). |
| 7. Networking | POSIX / All | dig / nslookup | dig [domain] [type] / nslookup |
+short, @server, MX, TXT, AAAA | dig example.com MX +short |
Queries DNS servers for record resolution (A, CNAME, MX, TXT) and troubleshooting. |
| 6. System & Storage | POSIX / All | dmesg | dmesg [options] |
-T (human timestamps), -l (level: err,warn) | dmesg -T -l err,crit |
Prints kernel ring buffer messages to diagnose boot errors, drive failures, and hardware faults. |
| 8. Services & Packages | RHEL/Fedora | dnf / rpm | dnf install [pkg] |
install, check-update, remove, rpm -qa | sudo dnf install -y nginx |
Package management suite for Red Hat Enterprise Linux, Fedora, and Rocky Linux. |
| 2. File & Directory | POSIX / All | du | du [options] [path] |
-s (summary), -h (human readable), -d [depth] | du -sh /var/log/* | sort -hr |
Estimates and prints disk space consumption for specified directory paths. |
| 6. System & Storage | POSIX / All | fdisk / parted | fdisk [device] / parted |
-l (list partitions) | sudo fdisk -l /dev/sda |
Low-level partition manipulation utilities for creating and resizing MBR and GPT disk tables. |
| 2. File & Directory | POSIX / All | file | file [options] filename |
-b (brief mode), -i (mime type string) | file -i archive.tar.gz |
Inspects file magic headers to determine true file type (binary, text, encoding, archive). |
| 2. File & Directory | POSIX / All | find | find [path] [expression] |
-name, -type (f/d), -mtime, -size, -exec | find /var/log -name "*.log" -mtime +30 -delete |
Recursively searches directory trees based on name, age, size, permissions, or executes actions. |
| 5. Process & Memory | POSIX / All | free | free [options] |
-h (human-readable), -m (MB), -g (GB), -s [delay] | free -h |
Displays total, used, free, shared, buffer/cache, and available RAM memory and Swap space. |
| 6. System & Storage | POSIX / All | fsck | fsck [options] device |
-y (auto repair), -f (force check) | sudo fsck -f -y /dev/sda1 |
Checks and repairs corrupted filesystem blocks and inode structure errors. Drive must be unmounted. |
| 3. Text Processing | POSIX / All | grep | grep [options] pattern file |
-i (ignorecase), -r (recursive), -n (line num), -v (invert) | grep -rnw "Failed password" /var/log/auth.log |
Searches files or stdin for lines matching regular expression patterns. |
| 4. Permissions & Users | POSIX / All | groupadd | groupadd [options] groupname |
-g (custom GID), -r (system group) | groupadd -r sysadmins |
Creates new user group definitions inside `/etc/group`. |
| 3. Text Processing | POSIX / All | head | head [options] file |
-n [lines], -c [bytes] | head -n 20 /var/log/auth.log |
Prints top N lines (default 10) of input files or pipeline stream. |
| 6. System & Storage | POSIX / All | hostname / hostnamectl | hostnamectl [cmd] |
status, set-hostname | sudo hostnamectl set-hostname node-01 |
Queries and sets machine network hostname and system metadata. |
| 1. Terminal Keys | Shortcut | Insert Last Argument | Alt + . or Esc + . |
Readline Argument | mkdir my_dir -> cd [Alt+.] |
Cycles and pastes the final argument from previous commands. Saves typing long paths. |
| 1. Terminal Keys | Shortcut | Interrupt Signal | Ctrl + C |
SIGINT Signal | Ctrl + C |
Sends SIGINT to immediately stop and terminate the foreground process. |
| 7. Networking | POSIX / All | ip | ip [object] [command] |
addr, link, route, -c (color) | ip -c addr show eth0 / ip route |
Modern utility to view/configure network interfaces, IP addresses, and routing tables (replaces ifconfig). |
| 8. Services & Packages | Systemd | journalctl | journalctl [options] |
-u [unit], -f (follow), -p [err], --since "1 hour ago" | journalctl -u docker -f --since "10 min ago" |
Queries and filters systemd binary journal log files. |
| 5. Process & Memory | POSIX / All | kill / killall | kill [signal] PID / killall name |
-15 (SIGTERM), -9 (SIGKILL), -1 (SIGHUP) | kill -15 1234 / killall -9 nginx |
Sends specified termination signals to processes by process PID or binary name string. |
| 3. Text Processing | POSIX / All | less | less [options] file |
-N (line numbers), -S (chop long lines), +F (live tail) | less -N /var/log/syslog |
Interactive file pager supporting backward navigation and keyword searching. |
| 1. Terminal Keys | Shortcut | Line Start / End Jump | Ctrl + A / Ctrl + E |
Readline Shortcut | Ctrl + A (Start) / Ctrl + E (End) |
Moves cursor instantly to beginning (Ctrl+A) or end (Ctrl+E) of command line. |
| 2. File & Directory | POSIX / All | ln | ln [options] target link |
-s (symbolic link), -f (force override) | ln -s /etc/nginx/sites-avail/app /etc/nginx/sites-enabled/ |
Creates soft (symbolic) or hard filesystem links to target files/directories. |
| 2. File & Directory | POSIX / All | locate | locate [options] pattern |
-i (ignore case), -r (regex search) | locate nginx.conf |
Fast indexed search using internal mlocate database. Requires running updatedb to refresh. |
| 2. File & Directory | POSIX / All | ls | ls [options] [path] |
-l (long), -a (all), -h (human), -t (time sort) | ls -la --color=auto /var/log |
Lists directory contents including permissions, owner, size, and mod dates. |
| 6. System & Storage | POSIX / All | lsblk / blkid | lsblk [opts] / blkid [dev] |
-f (filesystems), -p (full path) | lsblk -f && sudo blkid |
Displays block devices tree hierarchy, drive partition layout, filesystems, and device UUIDs. |
| 6. System & Storage | POSIX / All | lscpu / lsusb / lspci | lscpu / lsusb / lspci |
-v (verbose), -t (tree mode) | lscpu | grep "Model name" |
Enumerates hardware details for CPU sockets/cores, USB controller buses, and PCI express cards. |
| 7. Networking | POSIX / All | lsof | lsof [options] |
-i:[port], -u [user], -p [PID], +D [path] | sudo lsof -i :80 |
Lists all open files, directory locks, and associated network sockets held by processes. |
| 2. File & Directory | POSIX / All | mkdir | mkdir [options] dir |
-p (parents), -m (mode) | mkdir -p /var/www/site/public |
Creates directories. Option -p creates parent subdirectories recursively if missing. |
| 6. System & Storage | POSIX / All | mkfs | mkfs.[type] [device] |
-t (type: ext4, xfs, btrfs), -L (label) | sudo mkfs.ext4 -L "DATA" /dev/sdb1 |
Formats specified disk partition with a Linux filesystem (ext4, xfs, btrfs, vfat). |
| 6. System & Storage | POSIX / All | mount / umount | mount [dev] [dir] / umount |
-a (all fstab), -t (type), -o (options: ro,rw) | sudo mount -o ro /dev/sdb1 /mnt/data |
Attaches or unmounts storage device filesystems into designated mount directories. |
| 2. File & Directory | POSIX / All | mv | mv [options] src dst |
-i (interactive prompt), -n (no clobber), -v (verbose) | mv config.tmp config.yaml |
Moves or renames files and directories within or across filesystems. |
| 7. Networking | POSIX / All | nc (netcat) | nc [options] host port |
-z (zero-I/O port scan), -v (verbose), -l (listen) | nc -zv 192.168.1.50 22-80 |
Swiss-army knife for TCP/UDP socket testing, banner grabbing, and port connectivity verification. |
| 5. Process & Memory | POSIX / All | nice / renice | nice -n [val] cmd / renice -n PID |
Priority range -20 (highest) to +19 (lowest) | nice -n 19 tar -czf backup.tar.gz /data |
Sets or alters process CPU scheduling priority to balance resource usage against foreground tasks. |
| 7. Networking | POSIX / All | nmap | nmap [options] target |
-sS (SYN scan), -p [ports], -A (aggressive/OS detection) | nmap -sV -p 22,80,443 192.168.1.0/24 |
Network exploration security scanner for host discovery, open port detection, and OS fingerprinting. |
| 4. Permissions & Users | POSIX / All | passwd | passwd [options] [username] |
-l (lock), -u (unlock), -e (expire password) | sudo passwd devuser |
Updates local user account password authentication credentials. |
| 5. Process & Memory | POSIX / All | pgrep / pkill | pgrep [opts] pattern / pkill |
-f (full command line), -u (user), -9 (SIGKILL) | pkill -9 -f "python3 server.py" |
Looks up processes by regex name (pgrep) or sends termination signals matching full command string. |
| 7. Networking | POSIX / All | ping | ping [options] host |
-c [count], -i [interval], -4 (IPv4), -6 (IPv6) | ping -c 4 1.1.1.1 |
Sends ICMP ECHO_REQUEST packets to verify reachability and measure round-trip latency. |
| 5. Process & Memory | POSIX / All | ps | ps [options] |
aux, -ef, --sort=-%cpu, --sort=-%mem | ps aux --sort=-%mem | head -n 10 |
Takes a static snapshot of active system processes, showing PID, CPU/RAM usage, and TTY. |
| 2. File & Directory | POSIX / All | pwd | pwd [options] |
-P (physical), -L (logical) | pwd -P |
Prints current working directory absolute path, resolving symlinks with -P. |
| 1. Terminal Keys | Shortcut | Reverse History Search | Ctrl + R |
Bash / Zsh History | (reverse-i-search)`ssh': ... |
Interactively searches command history backwards as you type characters. |
| 2. File & Directory | POSIX / All | rm | rm [options] path |
-r (recursive), -f (force), -i (interactive) | rm -rf /tmp/cache_* |
Deletes files or directories recursively. Use extreme caution with -rf. |
| 2. File & Directory | POSIX / All | rmdir | rmdir [options] dir |
-p (parents), --ignore-fail-on-non-empty | rmdir -p old_dir/sub_dir |
Removes empty directories from the filesystem hierarchy. |
| 7. Networking | POSIX / All | scp / rsync | rsync [opts] src user@host:dst |
-a (archive), -v (verbose), -z (compress), -P (progress) | rsync -avzP ./build/ user@remote:/var/www/html/ |
Transfers and synchronizes files over SSH, updating only modified block deltas. |
| 3. Text Processing | POSIX / All | sed | sed [options] 'script' file |
-i (in-place write), -e (expression), s/pattern/replace/g | sed -i 's/Port 22/Port 2222/g' /etc/ssh/sshd_config |
Stream editor for inline text parsing, string search and replace, and line filtering. |
| 8. Services & Packages | POSIX / All | shutdown / reboot | shutdown [opts] [time] |
-h (halt now), -r (reboot), +N (delay minutes) | sudo shutdown -h +15 "Server maintenance" |
Safely signals processes and shuts down or reboots the OS kernel. |
| 3. Text Processing | POSIX / All | sort | sort [options] file |
-n (numeric), -r (reverse), -k [column], -u (unique) | sort -t: -k3 -n /etc/passwd |
Sorts lines of text files alphabetically or numerically across designated key columns. |
| 7. Networking | POSIX / All | ss / netstat | ss [options] / netstat [options] |
-t (tcp), -u (udp), -l (listen), -p (process), -n (numeric) | ss -tulpn | grep :80 |
Displays active listening sockets, open network connections, and associated PIDs. |
| 7. Networking | POSIX / All | ssh | ssh [options] user@host |
-i [key], -p [port], -L (local tunnel), -D (SOCKS proxy) | ssh -i ~/.ssh/id_ed25519 user@10.0.0.5 -p 2222 |
Connects securely to remote host command lines over encrypted channels. |
| 2. File & Directory | POSIX / All | stat | stat [options] file |
-f (filesystem), -c (format specification) | stat /etc/passwd |
Displays detailed inode metadata, file size, access permissions, owner UID, and timestamps. |
| 4. Permissions & Users | POSIX / All | su / sudo | su - [user] / sudo [cmd] |
-i (login shell), -u (user target), -v (validate) | sudo -i / su - postgres |
Switches target user shell environment or elevates command privileges using sudo policies. |
| 1. Terminal Keys | Shortcut | Suspend Job | Ctrl + Z |
SIGTSTP Signal | Ctrl + Z (Then run 'bg') |
Suspends active job and pushes it to background. Resume with fg or bg. |
| 8. Services & Packages | Systemd | systemctl | systemctl [cmd] [service] |
status, start, stop, restart, enable, disable, reload | sudo systemctl restart nginx |
Controls systemd init services, daemons, and target runlevels. |
| 3. Text Processing | POSIX / All | tail | tail [options] file |
-f (follow live stream), -n [lines], --retry | tail -100f /var/log/nginx/error.log |
Outputs trailing lines of files. Option -f monitors file growth in real-time. |
| 8. Services & Packages | POSIX / All | tar | tar [options] archive.tar.gz |
-c (create), -x (extract), -z (gzip), -v, -f (file) | tar -czvf backup.tar.gz /var/www |
Bundles files into tape archives and compresses/extracts using gzip or bzip2. |
| 7. Networking | POSIX / All | tcpdump | tcpdump [options] [filter] |
-i [interface], -nn (no resolve), -w [file.pcap], port [N] | sudo tcpdump -i eth0 -nn port 80 -w http.pcap |
Command-line packet analyzer that captures network traffic for Wireshark inspection. |
| 3. Text Processing | POSIX / All | tee | tee [options] file |
-a (append mode), -i (ignore signals) | echo "data" | sudo tee -a /etc/config |
Reads standard input and writes simultaneously to standard output and disk files. |
| 5. Process & Memory | POSIX / All | top / htop | top / htop |
-u [user], -p [PID], -d [delay] | htop -u www-data |
Dynamic real-time monitoring interface for CPU utilization, RAM consumption, and process trees. |
| 2. File & Directory | POSIX / All | touch | touch [options] file |
-a (access time), -m (mod time), -c (no create) | touch /var/www/app/index.php |
Creates empty file if missing or updates file access and modification timestamps. |
| 3. Text Processing | POSIX / All | tr | tr [options] set1 [set2] |
-d (delete), -s (squeeze repeats) | cat file.txt | tr 'a-z' 'A-Z' |
Translates, squeezes, or deletes characters from standard input stream. |
| 7. Networking | POSIX / All | traceroute / mtr | traceroute host / mtr host |
-n (numeric IP), -T (TCP packets) | mtr --report 8.8.8.8 |
Traces network packet hop paths and pinpoints latency bottlenecks or packet loss. |
| 2. File & Directory | POSIX / All | tree | tree [options] [path] |
-L [depth], -d (dirs only), -a (all files) | tree -L 2 /var/www |
Displays directory tree structure visually in depth-indented format. |
| 7. Networking | POSIX / All | ufw / iptables | ufw status / iptables -L |
allow [port], deny, -t nat, -A INPUT | sudo ufw allow 22/tcp && sudo ufw enable |
Manages Linux kernel netfilter packet filtering rules and firewall policies. |
| 4. Permissions & Users | POSIX / All | umask | umask [mask] |
-S (symbolic output) | umask 022 |
Sets default access permission masking mode for newly created files and directories. |
| 6. System & Storage | POSIX / All | uname | uname [options] |
-a (all details), -r (kernel release), -m (arch) | uname -a |
Prints kernel name, hostname, OS version, architecture (x86_64/aarch64), and build details. |
| 3. Text Processing | POSIX / All | uniq | uniq [options] [input] |
-c (count), -d (duplicates only), -u (unique only) | sort access.log | uniq -c | sort -nr |
Filters adjacent duplicate lines from sorted text streams and reports occurrence counts. |
| 2. File & Directory | POSIX / All | updatedb | updatedb [options] |
--output, --prunepaths | sudo updatedb |
Rebuilds mlocate search index database across mounted local filesystems. |
| 5. Process & Memory | POSIX / All | uptime | uptime [options] |
-p (pretty format), -s (since timestamp) | uptime -p |
Shows running duration, active users, and 1, 5, 15 minute system load averages. |
| 4. Permissions & Users | POSIX / All | useradd | useradd [options] username |
-m (create home), -s (shell), -g (group), -G (groups) | useradd -m -s /bin/bash -G sudo devuser |
Creates a new user account, setting up home directories and initial group memberships. |
| 4. Permissions & Users | POSIX / All | userdel | userdel [options] username |
-r (remove home directory and mail spool) | userdel -r olduser |
Deletes user account and optionally wipes home directory files with -r. |
| 4. Permissions & Users | POSIX / All | usermod | usermod [options] username |
-aG (append groups), -s (change shell), -L (lock) | usermod -aG docker devuser |
Modifies user account parameters, secondary group affiliations, or login shells. |
| 3. Text Processing | POSIX / All | vim / nano | vim [file] / nano [file] |
+line, -R (read-only mode) | sudo vim /etc/fstab |
Terminal text editors for modifying configuration files directly on headless servers. |
| 4. Permissions & Users | POSIX / All | visudo | visudo [options] |
-c (check syntax), -f [file] | sudo visudo |
Safely opens `/etc/sudoers` in an editor with syntax validation checks to prevent lockouts. |
| 5. Process & Memory | POSIX / All | vmstat | vmstat [delay] [count] |
-s (event counters), -d (disk stats) | vmstat 2 5 |
Reports virtual memory, swap-in/swap-out paging, process queues, I/O blocks, and CPU states. |
| 4. Permissions & Users | POSIX / All | w / who / last | w / who / last |
-n [lines], -f [file] | w && last -n 10 |
Inspects active SSH sessions, logged-in terminal users, system load, and recent boot history. |
| 2. File & Directory | POSIX / All | wc | wc [options] [file] |
-l (lines), -w (words), -c (bytes) | wc -l /etc/passwd |
Prints line, word, character, and byte count totals from input files or pipes. |
| 4. Permissions & Users | POSIX / All | whoami / id | whoami / id [user] |
-u (UID), -g (GID), -G (all groups) | id -u root |
Prints effective username (whoami) or full numeric UID/GID and group assignments (id). |
| 8. Services & Packages | POSIX / All | zip / unzip | zip archive.zip file / unzip |
-r (recursive), -q (quiet), -d (destination) | unzip archive.zip -d /tmp/extracted |
Compresses and extracts standard zip archive files. |
Command Structure & Syntax Anatomy
1. Binary / Alias
The executable file located in $PATH (e.g. /usr/bin/rsync) or custom shell alias.
2. Short / Long Flags
Short options use single dash (-a -v or combined -av). Long options use double dashes (--archive).
3. Arguments
Operands passed to the program, such as file targets, string regex patterns, or network host IP addresses.
4. Redirection / Pipe
Chains stdout output into stdin of the next process using | or redirects streams to disk with >, >>.
Standard I/O Streams
File Mode Bits (Octal)
Process Signals
System Administrator Pro-Tips
sudo to a privileged system command? Use history substitution:
sudo !!
Executes the exact last command line with elevated privileges.
cd -
Swaps instantly between your current path and previous $OLDPWD.
watch -n 1 "ss -tulpn"
Refreshes open listening port status output every 1.0 second.
System Administration Golden Rules
- Dry-Run Verification: Always run non-destructive flags first (e.g.,
rsync --dry-runorrm -i) before executing bulk operations. - Avoid Blind chmod 777: Never grant world-writable permissions to fix permission errors; use exact group grants (e.g.,
755or644). - Use Absolute Paths in Scripts: Cron jobs and systemd service units do not inherit your user shell's
$PATHenvironment. Always specify full binary paths (e.g./usr/bin/python3).
High-efficiency pipeline combining process filtering, sorting, and field selection:
htpasswd — Protect a Folder Behind a Username & Password
| Task | Command | Notes |
|---|---|---|
| Install the tool |
sudo apt install apache2-utils
|
Debian/Ubuntu. On RHEL/Fedora it's sudo dnf install httpd-tools. The tool comes from Apache but the file it makes works with nginx too. |
| Create file + first user |
sudo htpasswd -c /etc/nginx/.htpasswd tyler
|
Prompts for the password twice — double entry catches the silent typo that blind single entry invites. -c is for the first user only. |
| Add another user |
sudo htpasswd /etc/nginx/.htpasswd sarah
|
No -c. Re-using -c here wipes the whole file and replaces it with just this user — the classic htpasswd mistake. |
| Change a password |
sudo htpasswd /etc/nginx/.htpasswd tyler
|
Same as adding — an existing user is simply re-prompted and updated. |
| Verify a password |
htpasswd -v /etc/nginx/.htpasswd tyler
|
Checks a typed password against the stored hash — the "did I set what I think I set?" test, worth running before you blame the server. |
| Delete a user |
sudo htpasswd -D /etc/nginx/.htpasswd sarah
|
Removes just that one line from the file. |
| Stronger hashing |
sudo htpasswd -B -C 12 /etc/nginx/.htpasswd tyler
|
-B uses bcrypt instead of the aging MD5-based default. See the nginx caveat below before using it there. |
- HTTPS is not optional. Basic Auth sends the username and password base64-encoded — that is encoding, not encryption — on every single request. Over plain HTTP, anyone on the path reads the credentials. Behind TLS it's fine.
- Keep the file out of the webroot.
/etc/nginx/.htpasswd, never/var/www/html/.htpasswd— a file inside the served directory can end up downloadable. -cdestroys. It means "create the file", and it recreates it happily over an existing one, silently dropping every other user. First user only.- Never use
-b(password on the command line) interactively — it lands in your shell history in plaintext. It exists for scripts that pipe from a secrets store, not for typing. - bcrypt (
-B) + nginx needs one check. nginx delegates hash checking to the system'scrypt(), and older glibc builds don't speak bcrypt — logins then fail with a correct password. Modern distros (libxcrypt) are fine; if valid logins bounce after switching to-B, recreate the entry with the default hash and it will work everywhere. - No lockout, no rate limit. Basic Auth will happily let a bot guess forever. For anything internet-facing, pair it with fail2ban or nginx's
limit_reqon the gated location. - Permissions: the file should be readable by the web server user and nobody else —
sudo chown root:www-data /etc/nginx/.htpasswd && sudo chmod 640 /etc/nginx/.htpasswd. - Browsers cache the credentials for the session. "Logging out" isn't really a thing — closing the browser or using a private window is how you test with a second account.
scp & rsync — Moving Files Over SSH
The two tools a web designer or developer actually uses to get a site from a laptop onto a client's server and back. scp is the quick copy; rsync is the deploy tool. Both ride on the SSH login you already have, so if ssh user@host works, everything below works too.
Make a key pair (once per laptop)
Ed25519 is the modern default: short, fast, and supported by every server you'll meet. Give it a passphrase; the agent in step 4 means you type it once per login, not per transfer.
Put the public key on the server
ssh-copy-id does the whole dance (creates ~/.ssh, appends to authorized_keys, fixes permissions). It asks for the account password one last time.
No ssh-copy-id (older macOS)? Same result by hand:
Give the server a short name in ~/.ssh/config
This is the step most people skip and then regret. Every tool that uses SSH — ssh, scp, rsync, git, VS Code Remote — reads this file, so the port, user, and key live in one place.
From now on: ssh acme, scp file.css acme:/var/www/site/css/, rsync -avz dist/ acme:/var/www/site/.
Load the key into the agent (macOS remembers it)
With --apple-use-keychain the passphrase goes in the Keychain, and UseKeychain yes + AddKeysToAgent yes in the config make new terminals pick it up automatically.
Test it, then test a transfer
If the first command logs in without a password prompt, keys work. The second proves the path on the server is writable by that user — the number-one deploy failure.
| ~/.ssh/config directive | Example | Why a web dev wants it |
|---|---|---|
| Host | Host acme acme.* | The alias you'll type. Patterns work: Host *.acme-client.com applies to every subdomain. |
| HostName | HostName 203.0.113.10 | The real IP or DNS name. Change it here when a client migrates hosts and nothing else has to change. |
| User | User deploy | Stops the user@ prefix. Use a non-root deploy user where you can; see --rsync-path in the rsync tab for the sudo trick. |
| Port | Port 2222 | Many hosts move SSH off 22. Set once here and scp's -P / rsync's -e "ssh -p" become unnecessary. |
| IdentityFile | IdentityFile ~/.ssh/acme_ed25519 | Per-client keys. Lets you revoke one client's access without touching the others. |
| IdentitiesOnly | IdentitiesOnly yes | Only offer the key named above. Cures "Too many authentication failures" when the agent holds five keys and the server gives up after three. |
| ProxyJump | ProxyJump bastion.acme.com | Tunnels through a jump/bastion host transparently. scp and rsync then work as if the target were directly reachable. |
| ServerAliveInterval | ServerAliveInterval 60 | Keeps long transfers alive through NAT/firewall idle timeouts. Pair with ServerAliveCountMax 3. |
| AddKeysToAgent / UseKeychain | AddKeysToAgent yes | macOS: passphrase typed once, stored in Keychain, auto-loaded in every new terminal. Put these under Host *. |
| ControlMaster / ControlPath | ControlMaster auto | Reuses one SSH connection for a burst of commands — a deploy script that runs rsync then three ssh calls feels instant instead of paying the handshake four times. |
| Compression | Compression yes | Compress the SSH stream. Helps text/CSS/JS on slow links; pointless for already-compressed images and zips. |
Upload
Local path first, host:path last. A relative remote path is relative to that user's home.
Download
host:path first, local path last. . means "right here".
Capital -P
Port is -P in scp (lowercase -p means preserve times). ssh uses lowercase. Everyone gets this wrong once.
Quote the remote side
Spaces and globs on the remote path need quoting twice: acme:"'/var/www/My Site/'".
| Task | Command | Notes |
|---|---|---|
| Upload one file | scp style.css acme:/var/www/site/css/ | Trailing slash on the destination folder = "put it inside". Without it, if the folder doesn't exist you've just created a file named css. |
| Upload and rename | scp index.local.html acme:/var/www/site/index.html | Destination is a file path, so it lands under that name. |
| Download one file | scp acme:/var/www/site/wp-config.php ./backup/ | Remote first, local last. The local folder must already exist. |
| Upload a whole folder | scp -r ./dist acme:/var/www/site/ | Creates /var/www/site/dist/. To copy the contents into site/ use scp -r ./dist/. acme:/var/www/site/ — or just use rsync. |
| Download a folder | scp -r acme:/var/www/site/wp-content/themes/acme ./themes/ | Pulls the theme folder down as ./themes/acme. |
| Non-standard port | scp -P 2222 logo.svg deploy@203.0.113.10:/var/www/site/img/ | Capital P. Unnecessary once the port is in ~/.ssh/config. |
| Specific key | scp -i ~/.ssh/acme_ed25519 file.zip deploy@203.0.113.10:~/ | ~/ or a bare host: both mean the remote home directory. |
| Several files at once | scp index.html style.css app.js acme:/var/www/site/ | Any number of sources; the last argument is always the destination. |
| Local glob | scp img/*.webp acme:/var/www/site/img/ | Your shell expands the * before scp sees it, so this just works locally. |
| Remote glob | scp 'acme:/var/log/nginx/*.log' ./logs/ | Quote it so your shell doesn't try to expand it (zsh otherwise errors with "no matches found"); the remote shell expands it instead. |
| Spaces in a path | scp "Brand Guide.pdf" acme:"'/var/www/site/Client Files/'" | Local side: normal quotes. Remote side: quotes inside quotes, because the path is parsed twice. Or rename the folder and never think about it again. |
| Preserve timestamps & modes | scp -rp ./assets acme:/var/www/site/ | Lowercase -p. Keeps mtimes so "what changed?" comparisons on the server still mean something. |
| Compress in flight | scp -C site-dump.sql acme:~/ | Great for SQL, HTML, CSS, JS. Useless for images/zips/videos (already compressed) — it just burns CPU. |
| Cap bandwidth | scp -l 8000 video.mp4 acme:/var/www/site/media/ | Kilobits per second (8000 ≈ 1 MB/s). Keeps a big upload from killing your Zoom call or the client's live site. |
| Server-to-server | scp -3 old-host:/var/www/site/uploads.tar.gz acme:/var/www/site/ | -3 routes through your laptop so the two servers don't need keys for each other. Slower, but zero setup. For a real migration, run rsync on one of the servers instead. |
| Through a bastion | scp -J bastion.acme.com file.zip deploy@10.0.1.20:~/ | Same as ProxyJump in the config. Private-network app servers behind a jump box. |
| Quiet (for scripts) | scp -q build.zip acme:~/ && echo "uploaded" | No progress meter. Exit code is still non-zero on failure, so && chains stay safe. |
| Debug a failing copy | scp -v file.txt acme:~/ 2>&1 | grep -i -E "auth|denied|offer" | Shows which keys were offered and why they were rejected. Add -vvv for the full firehose. |
| Use a different config file | scp -F ~/clients/acme/ssh_config file.txt acme:~/ | Handy when a client's SSH details live in their project repo rather than your global config. |
| Legacy scp protocol | scp -O file.txt acme:~/ | Forces the old protocol if a very old server has no SFTP subsystem ("subsystem request failed"). |
| Copy a folder fast without scp | tar czf - dist | ssh acme "tar xzf - -C /var/www/site" | Thousands of tiny files (node_modules, image sprites) copy far faster as one stream than as thousands of scp round-trips. |
| Need resume? | rsync -avP big-file.zip acme:/var/www/site/ | scp can't. rsync's -P (--partial --progress) picks up where the last attempt died. |
- It overwrites without asking. There is no
-i/interactive mode. Uploadingwp-config.phpover the live one is instant and unrecoverable. Download the server's copy first if in doubt. -rfollows symlinks and copies the target. A theme folder with a symlink to../shared/arrives as a full copy ofshared/. rsync's-akeeps it as a link.- Destination folder missing = wrong result, not an error.
scp file.css acme:/var/www/site/csswith nocss/folder creates a file namedcss.ssh acme "mkdir -p /var/www/site/css"first. - Ownership is whoever you logged in as. Files uploaded as
deployare owned bydeploy; PHP running aswww-datamay not be able to write next to them (uploads, caches). Fix withchownafter, or rsync's--chown. - Port is
-P, preserve is-p. Lowercase when you meant uppercase silently connects to port 22 and hangs. - Every run sends everything. A 300 MB site re-uploaded for a one-line CSS fix. That's the whole reason rsync exists.
-a archive
= -rlptgoD: recursive, keep symlinks, permissions, times, group, owner, devices. "Copy it faithfully." Always.
-v verbose
Print each file transferred. Add -h for human sizes, --stats for a summary.
-z compress
Compress in transit. Big win for HTML/CSS/JS/SQL over the internet; skip it on a LAN or for image-heavy folders.
--delete
Remove files on the destination that no longer exist in the source. What turns "copy" into "mirror". Always dry-run first.
The trailing-slash rule
How "changed" is decided
Filter order
| Flag / Task | Example | What it does & when you want it |
|---|---|---|
| -a, --archive | rsync -a ./dist/ acme:/var/www/site/ | The baseline. Recursive + preserves symlinks, perms, times, owner, group. Everything below assumes it. |
| -n, --dry-run | rsync -avzn --delete ./dist/ acme:/var/www/site/ | Show what would happen, touch nothing. Non-negotiable before any --delete, and the fastest way to check your trailing slash. |
| -v / -h / --stats | rsync -avh --stats ./dist/ acme:/var/www/site/ | List files, human-readable sizes, totals at the end ("Number of regular files transferred: 3"). |
| -z, --compress | rsync -avz ./src/ acme:/var/www/site/ | Compress in transit. Add --skip-compress=jpg/png/webp/mp4/zip/gz if a folder mixes code and media (recent versions do this by default). |
| -P (= --partial --progress) | rsync -avP big-export.zip acme:/var/www/site/dl/ | Per-file progress bar and keep partial files so a dropped connection resumes instead of restarting. Use for anything over ~50 MB. |
| --info=progress2 | rsync -a --info=progress2 ./media/ acme:/var/www/site/media/ | One overall progress line for the whole job instead of one per file. Nicer for folders with thousands of images. |
| --delete | rsync -avz --delete ./dist/ acme:/var/www/site/ | Mirror: remove files on the server that aren't in dist/. Renamed about.html → about-us.html locally? Old one is gone on the server too. Never use it on a folder the client writes to (uploads) without excludes. |
| --delete-excluded | rsync -av --delete --delete-excluded --exclude 'cache/' ./site/ acme:/var/www/site/ | Also delete files on the destination that match an exclude. Dangerous with --exclude uploads/ — it would wipe uploads. Know the difference. |
| --exclude | rsync -avz --exclude '.git' --exclude 'node_modules' --exclude '.env' --exclude '.DS_Store' ./ acme:/var/www/site/ | Repeatable. A bare name matches at any depth; /node_modules only at the source root; *.map globs; cache/ only directories. Quote patterns so the shell doesn't expand them. |
| --exclude-from | rsync -avz --delete --exclude-from '.rsyncignore' ./ acme:/var/www/site/ | One pattern per line, # comments allowed. Commit .rsyncignore to the project so every deploy uses the same list. (See Deploy Workflows for a starter file.) |
| --include + --exclude | rsync -avm --include '*/' --include '*.css' --exclude '*' ./src/ acme:/var/www/site/ | "Only CSS files, keep the folder structure." */ lets rsync descend into directories, -m prunes empty ones. First match wins, so includes go first. |
| -e "ssh …" | rsync -avz -e "ssh -p 2222 -i ~/.ssh/acme_ed25519" ./dist/ deploy@203.0.113.10:/var/www/site/ | Custom port / key / jump host when you haven't set up ~/.ssh/config. Note ssh's port is lowercase -p here. |
| --chown | rsync -avz --chown=www-data:www-data ./dist/ acme:/var/www/site/ | Set owner:group on arrival so the web server can read/write. Needs root (or the sudo trick below) on the receiving side; requires rsync ≥ 3.1. |
| --chmod | rsync -avz --chmod=D755,F644 ./dist/ acme:/var/www/site/ | Directories 755, files 644 regardless of what they were on your Mac. Fixes the "everything is 777 / 600" mess from a bad export. |
| --rsync-path="sudo rsync" | rsync -avz --rsync-path="sudo rsync" --chown=www-data:www-data ./dist/ acme:/var/www/site/ | Log in as a normal user but run the receiving rsync as root — writes into root-owned /var/www and applies --chown. Needs passwordless sudo for rsync on the server (deploy ALL=(root) NOPASSWD: /usr/bin/rsync). |
| --no-o --no-g --no-p | rsync -rlvz --no-o --no-g --no-p ./dist/ shared-host:public_html/ | Shared hosting where you can't change ownership: drop owner/group/perms from -a (equivalently -rltvz) and the "failed to set permissions/times" noise disappears. |
| -u, --update | rsync -avzu acme:/var/www/site/wp-content/uploads/ ./uploads/ | Skip files that are newer on the destination. Safe two-way-ish pulls of a folder the client edits. |
| --ignore-existing | rsync -avz --ignore-existing ./uploads/ acme:/var/www/site/wp-content/uploads/ | Add new files only, never touch what's already there. The polite way to top up a media library. |
| --existing | rsync -avz --existing ./dist/ acme:/var/www/site/ | Opposite: update files that already exist, create nothing new. Push fixes to a site without adding your local junk. |
| -c, --checksum | rsync -avzc ./dist/ acme:/var/www/site/ | Compare by content hash, not size+time. Slower (reads every file on both ends) but right after a build tool that rewrites timestamps on every run. |
| --size-only | rsync -avz --size-only ./media/ acme:/var/www/site/media/ | Ignore timestamps entirely. Useful after a copy that lost mtimes (FAT drives, some FTP clients) so you don't re-send 4 GB of unchanged images. |
| --bwlimit | rsync -avP --bwlimit=2m ./media/ acme:/var/www/site/media/ | Kilobytes/s (or 2m). Keep a bulk media push from starving the client's live traffic — or your own connection. |
| -i, --itemize-changes | rsync -ain --delete ./dist/ acme:/var/www/site/ | A change-code per file (>f.st.... = file, size & time differ; *deleting). The most precise dry-run readout. |
| --list-only | rsync --list-only acme:/var/www/site/ | A remote ls -l through rsync. Handy to confirm a path before you point --delete at it. |
| -L / -l / --safe-links | rsync -avL ./dist/ acme:/var/www/site/ | -l (in -a) copies symlinks as symlinks; -L replaces them with the real files (what you want when the server won't have the link target); --safe-links drops links pointing outside the tree. |
| --files-from | git diff --name-only main | rsync -avz --files-from=- ./ acme:/var/www/site/ | Send exactly the listed paths (relative to the source root). Feeding it git diff gives a "deploy only what I changed" one-liner. |
| --backup --backup-dir | rsync -avz --delete --backup --backup-dir=/var/backups/site/$(date +%F) ./dist/ acme:/var/www/site/ | Anything overwritten or deleted on the server is moved into a dated folder first. Cheap undo for a bad deploy. (Backup dir must be an absolute path on the destination.) |
| --link-dest | rsync -avz --delete --link-dest=../latest acme:/var/www/site/ ~/Backups/acme/$(date +%F)/ | Time Machine-style snapshots: unchanged files are hard-linked to the previous snapshot, so 30 daily backups cost about one copy of disk. Repoint a latest symlink after each run. |
| --remove-source-files | rsync -av --remove-source-files acme:/var/www/site/exports/ ./exports/ | A "move" — delete each source file after it's safely copied. Folders are left behind (empty). Pull-and-clear job for a client's export drop. |
| --max-size / --min-size | rsync -avz --max-size=20m acme:/var/www/site/wp-content/uploads/ ./uploads/ | Pull the uploads folder for local dev but skip the 500 MB videos. |
| -x, --one-file-system | rsync -avx / /mnt/backup/root/ | Don't cross mount points. Full-server backups that would otherwise happily recurse into /proc, /mnt, and the backup drive itself. |
| --inplace / --append-verify | rsync -avP --append-verify huge-backup.tar acme:/backups/ | For one giant file that grows (a log, a tar being written): append the new tail and verify. --inplace writes directly instead of via a temp file (saves disk, breaks atomicity — don't use it for live web assets). |
| --timeout / --contimeout | rsync -avz --timeout=60 --contimeout=15 ./dist/ acme:/var/www/site/ | Fail fast in a cron job rather than hanging forever on a dead connection. |
| --log-file | rsync -avz --log-file=~/deploys/acme-$(date +%F).log ./dist/ acme:/var/www/site/ | A written record of what each deploy changed. Worth its weight the day a client asks "what changed on Tuesday?" |
| -H, --hard-links | rsync -avH /srv/snapshots/ /mnt/backup/snapshots/ | Preserve hard links (needed when copying a --link-dest backup set elsewhere, or they balloon to full size). |
| Local → local | rsync -avh --progress ~/Sites/acme/ /Volumes/Archive/acme/ | No host at all: rsync is also the best cp -r — resumable, skips unchanged, shows progress. Copying to an external drive, this is what you want. |
| Remote → remote | ssh old-host "rsync -avz /var/www/site/ deploy@new-host:/var/www/site/" | rsync itself refuses two remote paths. Run it on one server (needs a key from old → new). Site migrations go server-to-server, not through your laptop. |
| macOS: get a modern rsync | brew install rsync && rsync --version | head -1 | Older macOS shipped rsync 2.6.9 (2006): no --chown, no --info=progress2, protocol warnings against modern Linux. Newer macOS bundles openrsync, which also lacks some flags. Homebrew fixes both. |
Deploy a static / built site (the standard deploy)
Build locally, mirror the output folder, never touch anything that isn't yours. Dry-run first, then the real thing.
Push a whole project but keep secrets and junk at home
Commit a .rsyncignore so every deploy — yours, a teammate's, the CI job — uses the same list.
WordPress: push the theme, never the uploads
Only the theme folder is yours. --delete is safe here because it's scoped to that folder; the uploads/plugins the client manages are never in the path.
Pull a client site down to work on it locally
Code first, then the media (which is big — cap file size), then the database. No --delete on a pull: you don't want rsync deleting your local work.
Deploy as a normal user, land files owned by the web server
You log in as deploy; files should belong to www-data with sane modes. One-time on the server: echo 'deploy ALL=(root) NOPASSWD: /usr/bin/rsync' | sudo tee /etc/sudoers.d/deploy-rsync.
One-file hotfix
Fix a typo in production without a full deploy. scp is fine for this; the second line is the same thing with rsync (resumable, skips if identical).
Deploy only what changed in git
List the files that differ from main (or between two tags) and send exactly those. Deleted files still need a manual ssh acme rm — this can't delete.
A deploy.sh you can hand to the next developer
Build, dry-run with a confirmation, deploy with a dated backup of anything overwritten, reload the server. set -e stops at the first failure so a broken build never half-deploys.
Nightly server backup to your laptop / NAS, with snapshots
Each night is a full-looking folder that only stores the changed files. Restore = copy a folder back. Run from cron or a launchd job on the machine that stores the backups.
Ghost theme deploy (this site's own routine)
Mirror the theme folder from the repo into Ghost's content/themes/, then restart so Handlebars templates re-load. Excludes keep dev-only files out.
Migrate a site between two servers
Run rsync on the old server pointing at the new one (after putting a key from old → new in place). Then a final --delete pass at cutover to catch the last hour of uploads.
- Missing trailing slash +
--delete.rsync -av --delete dist acme:/var/www/site/createssite/dist/and deletes everything else in site/ — because from rsync's point of view, onlydistshould exist there. Dry-run shows this instantly as a wall ofdeleting …lines. - Pointing
--deleteat a folder the client writes to. Uploads, exports, user avatars. Your local copy is a week old; rsync faithfully deletes the week. Scope the deploy to the theme/build folder, or--exclude 'uploads/'(and never add--delete-excluded). - Deploying
.env/wp-config.phpfrom local. Overwrites production database credentials with your local ones. Site down. Exclude them, always, in a committed.rsyncignore. - Source and destination swapped. A pull written as a push. rsync happily makes the server look like your stale laptop. Say it out loud: source, then destination. Dry-run.
- Running as root with
-aonto a shared host. Ownership arrives as your local UID (501) and PHP can no longer write. Use--chownor--no-o --no-g.
| Error / symptom | What it means | Fix |
|---|---|---|
| Permission denied (publickey) | The server only accepts keys and none of yours matched (wrong key, wrong user, key not in authorized_keys, or bad permissions on the server's ~/.ssh). | ssh -v acme and read which key was offered. Check User. On the server: chmod 700 ~/.ssh; chmod 600 ~/.ssh/authorized_keys. |
| Too many authentication failures | Your agent offered 5+ keys and the server cut you off before reaching the right one. | IdentitiesOnly yes + IdentityFile in the config for that host. |
| REMOTE HOST IDENTIFICATION HAS CHANGED! | The server's host key differs from what's saved — usually because the client re-provisioned the server (or moved the IP to a new box). Rarely, an actual attack. | Confirm with the client the server was rebuilt, then ssh-keygen -R 203.0.113.10 (and -R hostname) and reconnect. |
| Connection timed out / refused | Wrong port, firewall, or SSH moved off 22. | Ask for the port. nc -zv host 2222 to test. Remember scp is -P, rsync is -e "ssh -p 2222", config is Port. |
| bash: rsync: command not found (remote) | rsync isn't installed on the server (minimal Docker images, some fresh VPSes). | ssh acme "sudo apt install -y rsync" (or dnf). Until then, the tar | ssh trick from the scp tab. |
| rsync: --chown: unknown option / protocol version mismatch | One side is running an ancient rsync (macOS 2.6.9, or openrsync). | brew install rsync on the Mac; make sure /opt/homebrew/bin is first in PATH. rsync --version on both ends. |
| rsync error: some files/attrs were not transferred (code 23) | Partial success: most files went, a few failed — typically permission denied on a file, or "failed to set times/permissions" on a host you don't own. | Scroll up for the actual per-file error. Shared host: add --no-o --no-g --no-p. Root-owned folder: --rsync-path="sudo rsync". |
| rsync: failed to set times on … Operation not permitted | You don't own the destination file/dir, so its mtime can't be changed. Data still copied. | --no-times / -O (omit dir times), or fix ownership on the server once. |
| skipping non-regular file / symlink has no referent | A symlink was copied as a link but its target doesn't exist on the server. | Use -L to copy the real files instead of links, or exclude the link. |
| Broken pipe / connection unexpectedly closed (large transfers) | Idle NAT/firewall timeout, flaky Wi-Fi, or the server's SSH was restarted mid-copy. | ServerAliveInterval 60 in the config; re-run the same rsync with -P and it resumes. |
| zsh: no matches found: acme:/var/log/*.log | zsh tried to expand the remote glob locally and found nothing. | Quote the remote path: 'acme:/var/log/*.log'. |
| My excludes aren't excluding | Pattern anchoring. --exclude 'src' matches any src at any depth; --exclude '/src' only the top-level one; --exclude 'src/' only directories. Patterns are relative to the source root, not your cwd. | Test with -n -i and look for the files. Order matters: the first matching include/exclude wins. |
| Everything re-uploads every deploy | Your build tool rewrites every file's mtime, so size+time says "changed" even when content didn't. Or a FAT/exFAT drive with 2-second timestamps. | -c (checksum) for exactness, --modify-window=2 for FAT, or --size-only for media. |
| Site shows old content after deploy | The files are there — a cache in front of them isn't (nginx/Varnish/Cloudflare/WordPress plugin/browser). | ssh acme "ls -l /var/www/site/css/" to prove the file landed, then purge the cache. Ghost, WordPress, and Cloudflare all have a purge command/button. |
| subsystem request failed on channel 0 (scp) | New scp speaks SFTP; this old server has no SFTP subsystem enabled. | scp -O for the legacy protocol, or just use rsync. |
| Windows: where do I run these? | PowerShell has ssh/scp built in (OpenSSH) but no rsync. | Use WSL (Ubuntu) for rsync; your Windows files are at /mnt/c/Users/…. Or Git Bash + a rsync package. Keys live in ~/.ssh inside WSL. |
--delete, anything big. sftp (or a GUI like Cyberduck/FileZilla in SFTP mode): browsing around when you don't know the path yet.
If you're typing -r after scp, you probably want rsync.
-n added, read the list, then press ↑ and delete the n. It takes four seconds and it has saved more client sites than any backup ever has.
rsync -avzn --delete ./dist/ acme:/var/www/site/
--backup --backup-dir, restore by rsyncing that dated folder back over the site. If you didn't, this is why the nightly --link-dest snapshots exist.
rsync -av acme:/var/backups/acme/2026-09-05_1420/ acme:/var/www/acme.com/
Two remote paths won't work from your laptop — run that one on the server over ssh.