Linux CLI
Table of Contents
- Linux CLI Cheatsheet
- 1. File & Directory Operations
- 2. Text Processing
- 3. File Permissions & Ownership
- 4. Process Management
- 5. Disk & Storage
- 6. Networking
- 7. User & Group Management
- 8. Package Management
- 9. Archiving & Compression
- 10. System Information & Monitoring
- 11. Shell & Scripting Essentials
- 12. SSH & Remote
- 13. Cron & Scheduling
- 14. Log Management
- Quick Reference: One-Liners for Production
Linux CLI Cheatsheet
Deep-reference for DevOps engineers. Every section covers the mechanics behind the command — not just syntax. Knowing why a flag behaves a certain way saves hours of debugging at 2 AM.
1. File & Directory Operations
Core Navigation
pwd # print working directory (absolute path)
cd - # go back to previous directory (uses $OLDPWD)
cd ~username # jump to another user's home
ls -lahF --color=auto # long, all files, human-readable sizes, classify
ls -lrt # sort by modification time, oldest first — great for logs
ls -li # show inode numbers — use when debugging hard links
Senior tip:
ls -lrt /var/log/is your first move when something broke and you don’t know which log changed recently. Newest files appear at the bottom.
mkdir / rmdir
mkdir -p /opt/app/{config,logs,data} # create nested dirs + brace expansion
mkdir -m 750 /opt/secrets # create with specific permissions
rmdir dir/ # fails if dir is not empty (safe)
rm -rf dir/ # recursive force delete — no confirmation
Gotcha:
rm -rf /path/to/dir /— that trailing space before/deletes root. Always double-check paths with a space. Some distros protect/but don’t count on it.
cp / mv
cp -a /src /dst # archive mode: preserves permissions, timestamps, symlinks
cp -u src dst # copy only if src is newer than dst (rsync-lite)
cp --reflink=auto src dst # CoW copy on btrfs/xfs — near-instant, no extra disk
mv -n src dst # no-clobber: don't overwrite dst if it exists
mv -i src dst # interactive: prompt before overwrite
Why -a over -r: -r copies recursively but drops ownership, timestamps, and symlinks. -a is equivalent to -dR --preserve=all — what you want when cloning config trees or backups.
find — the real workhorse
# Basic patterns
find /var/log -name "*.log" -mtime -1 # modified in last 24h
find /home -name "*.sh" -perm /111 # files with any execute bit set
find /tmp -type f -atime +7 -delete # delete files not accessed in 7 days
find / -xdev -name "*.conf" 2>/dev/null # stay on same filesystem (skip /proc, /sys)
# Size-based
find /var -type f -size +100M # files over 100MB
find . -size 0 -delete # remove empty files
# Owner/permission audits
find / -nouser -o -nogroup 2>/dev/null # orphaned files (user deleted but files remain)
find / -perm -4000 -type f 2>/dev/null # SUID files — security audit
find / -perm -2000 -type f 2>/dev/null # SGID files
# Exec pattern
find /etc -name "*.conf" -exec grep -l "password" {} \; # find conf files with passwords
find . -name "*.py" -exec chmod 644 {} + # + batches args (faster than \;)
Senior tip:
-exec cmd {} +batches multiple files into one invocation.-exec cmd {} \;forks a new process per file. For 10,000 files,+is 100x faster.
| Flag | Meaning |
|---|---|
-mtime -1 | modified < 1 day ago |
-mtime +7 | modified > 7 days ago |
-mmin -30 | modified < 30 minutes ago |
-newer file | newer than reference file |
-xdev | don’t cross filesystem boundaries |
-maxdepth 2 | recurse at most 2 levels |
-prune | don’t descend into matched dir |
ln — Hard Links vs Soft Links
ln target linkname # hard link: same inode, same data
ln -s /path/to/target link # symlink: pointer to path (can cross filesystems)
ln -sf new_target existing # force: update existing symlink
readlink -f symlink # resolve symlink to absolute real path
Hard link internals: A hard link is just a directory entry pointing to the same inode. The file’s data is not deleted until all hard link count reaches zero (ls -li shows inode + link count). Hard links cannot span filesystems or point to directories (kernel restriction to prevent cycles).
Soft link internals: A symlink is a special file containing a path string. When the kernel resolves it, it substitutes the path. If the target moves or is deleted, the symlink is “dangling” — it still exists but points nowhere.
# Detect dangling symlinks
find /usr/local/bin -type l ! -exec test -e {} \; -print
stat / file
stat /etc/passwd # full metadata: inode, permissions, atime/mtime/ctime, blocks
file /usr/bin/python3 # determine file type by magic bytes, not extension
file -b /some/binary # brief output (no filename prefix)
Gotcha:
atime(access time) is often disabled vianoatimemount option for performance. Don’t rely on it for auditing access without verifying mount options in/proc/mounts.
tree
tree -L 2 /etc/nginx # depth 2 only
tree -I "*.pyc|__pycache__" # ignore patterns
tree -sh # show sizes, human-readable
tree -d # directories only
2. Text Processing
cat / less / head / tail
cat -n file.txt # show line numbers
cat -A file.txt # show non-printing chars (^ for control, $ for EOL) — debug Windows CRLF
less -S file.log # don't wrap long lines (horizontal scroll with arrow keys)
less +F file.log # tail-follow mode inside less (press F to activate, ctrl+c to stop)
head -n 50 file.log # first 50 lines
tail -n 100 -f /var/log/syslog # follow: print new lines as written
tail -c 1M large.log # last 1 megabyte
Senior tip:
less +Fis better thantail -ffor log analysis — you can pressctrl+cto stop following and scroll back through history, then pressFto resume following.
grep
grep -rn "error" /var/log/ # recursive, show line numbers
grep -i "fail" /var/log/auth.log # case insensitive
grep -v "DEBUG" app.log # invert match (exclude lines)
grep -E "error|warn|crit" app.log # extended regex (alternation)
grep -o 'ip=[0-9.]*' access.log # print only matched part
grep -c "ERROR" app.log # count matching lines
grep -A 3 -B 3 "Exception" app.log # 3 lines after and before match (context)
grep -l "password" /etc/*.conf # print only filenames, not matches
grep -w "user" /etc/passwd # whole word match
grep --color=always "ERROR" log | less -R # colorized grep piped to less
# Practical log analysis
grep "$(date +%b\ %d)" /var/log/syslog # today's entries
grep -P '\d{3}-\d{4}' contacts.txt # Perl regex (more powerful)
egrep vs grep -E: egrep is just grep -E. Use grep -E — egrep is deprecated in newer coreutils.
sed — Stream EDitor
# Substitution
sed 's/foo/bar/' file # replace first occurrence per line
sed 's/foo/bar/g' file # replace all occurrences
sed 's/foo/bar/gi' file # case-insensitive, global
sed -i 's/old/new/g' config.txt # in-place edit
sed -i.bak 's/old/new/g' file # in-place with backup (safer)
# Line operations
sed -n '10,20p' file # print lines 10-20 (-n suppresses default output)
sed '5d' file # delete line 5
sed '/^#/d' file # delete comment lines
sed '/^$/d' file # delete empty lines
sed -n '/START/,/END/p' file # print between patterns
# Real-world usage
sed -i "s/APP_VERSION=.*/APP_VERSION=${VERSION}/" .env # update version in .env
sed 's/\r//' file.txt > unix_file.txt # remove Windows CR characters
Senior tip: Always use
-i.bakfor in-place edits in scripts. Ifsedcrashes mid-write, you still have the original. Only drop the backup when you’re confident in the pattern.
awk — Pattern Scanning and Processing
# Field processing (default delimiter: whitespace)
awk '{print $1, $3}' file # print fields 1 and 3
awk -F: '{print $1}' /etc/passwd # custom delimiter: print usernames
awk 'NR==5' file # print line 5 (NR = line number)
awk 'NR>=5 && NR<=10' file # print lines 5-10
# Conditionals and patterns
awk '/ERROR/ {print $0}' app.log # print lines matching pattern
awk '$3 > 100 {print $1, $3}' data # conditional: field 3 > 100
awk 'END {print NR}' file # count total lines
# Aggregation
awk '{sum += $4} END {print sum}' metrics.log # sum column 4
awk '{count[$1]++} END {for (k in count) print k, count[k]}' access.log # frequency count
# Real-world: extract HTTP status codes from nginx log
awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn
# Print 4th field of lines where 2nd field equals "failed"
awk '$2 == "failed" {print $4}' auth.log
| Variable | Meaning |
|---|---|
$0 | entire line |
$1..$NF | fields |
NR | current line number |
NF | number of fields in current line |
FS | field separator (set with -F) |
OFS | output field separator |
RS | record separator |
cut / sort / uniq / wc
cut -d: -f1,3 /etc/passwd # delimiter ':', fields 1 and 3
cut -c1-10 file # characters 1 to 10
sort -k2 -n file # sort by field 2, numerically
sort -k2 -rn file # reverse numeric sort
sort -u file # sort + deduplicate
sort -t: -k3 -n /etc/passwd # sort passwd by UID
uniq -c sorted_file # count consecutive duplicates (must be sorted first)
uniq -d file # show only duplicate lines
uniq -u file # show only unique lines
wc -l file # line count
wc -w file # word count
wc -c file # byte count
Gotcha:
uniqonly removes adjacent duplicates. Always pipe throughsortfirst:sort file | uniq -c | sort -rn— the standard frequency-count idiom.
tr / paste / column / tee / diff
tr 'a-z' 'A-Z' < file # translate lowercase to uppercase
tr -d '\r' < windows.txt # delete carriage returns
tr -s ' ' < file # squeeze repeated spaces into one
paste file1 file2 # merge files side by side (tab-delimited)
paste -d, file1 file2 # use comma as delimiter
column -t -s, data.csv # pretty-print CSV as aligned columns
column -t /etc/fstab # align any whitespace-delimited file
tee output.log | grep ERROR # write to file AND stdout simultaneously
command 2>&1 | tee -a debug.log # append stderr+stdout to file while displaying
diff file1 file2 # compare files line by line
diff -u file1 file2 # unified diff (what patch expects)
diff -r dir1/ dir2/ # recursive directory diff
Senior tip:
tee -ain deployment scripts lets you stream output to console while capturing to a log file. Combine with timestamps:command | ts '[%Y-%m-%d %H:%M:%S]' | tee -a deploy.log(requiresmoreutils).
3. File Permissions & Ownership
Permission Fundamentals
Every file has three permission sets: owner, group, others. Each set has read (r=4), write (w=2), execute (x=1).
-rwxr-x--- 1 deploy app 1234 May 1 10:00 deploy.sh
^^^ ^^^ ^^^
| | others: no permissions (---)
| group 'app': r-x (read, execute)
owner 'deploy': rwx (read, write, execute)
chmod — Change Mode
# Symbolic mode
chmod u+x script.sh # add execute for owner
chmod go-w file.txt # remove write from group and others
chmod a+r file.txt # add read for all
chmod u=rwx,g=rx,o= file # set exact permissions
# Numeric (octal) mode
chmod 755 script.sh # rwxr-xr-x (typical executable)
chmod 644 config.txt # rw-r--r-- (typical config file)
chmod 600 ~/.ssh/id_rsa # rw------- (private key — SSH refuses if looser)
chmod 700 ~/.ssh/ # rwx------ (SSH directory)
# Recursive
chmod -R 755 /var/www/html # recursive
chmod -R u=rwX,go=rX dir/ # capital X: set execute only if already executable or directory
| Common Octal | Symbolic | Use Case |
|---|---|---|
| 777 | rwxrwxrwx | Never (security risk) |
| 755 | rwxr-xr-x | Directories, public scripts |
| 644 | rw-r–r– | Regular config files |
| 640 | rw-r—– | Config with sensitive data |
| 600 | rw——- | Private keys, credentials |
| 700 | rwx—— | Private directories |
| 664 | rw-rw-r– | Shared write in same group |
Senior tip: Use capital
Xwith-Rinstead of lowercasex.chmod -R 755sets execute on files too, which is usually wrong for web content.chmod -R u=rwX,go=rXonly sets execute on directories.
chown / chgrp / umask
chown user:group file # change owner and group
chown -R nginx:nginx /var/www # recursive
chown --reference=ref file # copy ownership from reference file
chgrp docker /var/run/docker.sock # change group only
umask # show current umask (e.g., 022)
umask 027 # new files: rw-r----- new dirs: rwxr-x---
umask -S # symbolic display: u=rwx,g=rx,o=
How umask works: umask is a subtraction mask applied to default permissions. Default for files is 666, for directories 777. With umask 022: files get 666 - 022 = 644, dirs get 777 - 022 = 755.
Special Bits
# SUID (Set User ID) — execute as file owner, not caller
chmod u+s /usr/bin/passwd # passwd needs root access even when run by normal user
chmod 4755 program # octal: 4 = SUID
# SGID (Set Group ID) — execute as file's group
chmod g+s /usr/bin/wall # run as group 'tty'
chmod 2755 program
# On directories: new files inherit directory's group
chmod g+s /shared/project/ # all files created here get group 'project'
# Sticky bit — only owner can delete files in directory
chmod +t /tmp # anyone can write to /tmp, only owner can delete own files
chmod 1777 /tmp
ls -ld /tmp # drwxrwxrwt (note the 't')
Gotcha: SUID on scripts is ignored by the Linux kernel for security reasons. It only works on compiled binaries. If you need privilege escalation for scripts, use
sudo.
ACLs — Access Control Lists
Standard Unix permissions are limited to one owner, one group. ACLs add per-user/per-group entries.
getfacl /shared/file # view ACL
setfacl -m u:jenkins:rw file # grant jenkins user read+write
setfacl -m g:developers:rx dir # grant developers group read+execute
setfacl -x u:jenkins file # remove ACL entry for jenkins
setfacl -b file # remove all ACLs
setfacl -d -m g:devs:rwx dir # set default ACL (applied to new files in dir)
setfacl -R -m u:deploy:rx /var/app # recursive
# Check if ACL is in effect: ls shows '+' after permission string
ls -la file # -rw-r--r--+ (the + means ACL present)
Senior tip: Default ACLs (
-d) on directories are essential in CI/CD shared environments. Set them once:setfacl -d -m g:deploy:rwx /opt/releases/and every new file inherits the ACL automatically.
4. Process Management
Viewing Processes
ps aux # all processes, BSD format (no dash)
ps -ef # all processes, UNIX format
ps -eo pid,ppid,pcpu,pmem,comm # custom output columns
ps --sort=-%cpu | head -10 # top 10 CPU-consuming processes
ps -u nginx # processes for user nginx
ps -fp $(pgrep java) # full details for java processes
pgrep -la nginx # find PIDs by name, show command
pgrep -u root sshd # PID of sshd owned by root
top / htop
top -b -n 1 -o %CPU # batch mode (for scripts), sort by CPU
top -p 1234,5678 # monitor specific PIDs
# top interactive keys:
# k - kill process M - sort by memory P - sort by CPU
# u - filter by user f - field management 1 - per-CPU view
# q - quit z - color mode c - show full command
Senior tip: In
top, press1to see per-CPU utilization. A process pegging one of 32 CPUs won’t look alarming in aggregate but is obvious per-core. Critical for diagnosing single-threaded bottlenecks.
kill / killall / pkill
kill -l # list all signal numbers and names
kill -15 1234 # SIGTERM: polite shutdown (default)
kill -9 1234 # SIGKILL: immediate kill (unblockable)
kill -HUP 1234 # SIGHUP: reload config (nginx, sshd)
kill -STOP 1234 # pause process
kill -CONT 1234 # resume paused process
killall nginx # kill all processes named 'nginx'
killall -HUP sshd # send HUP to all sshd processes
pkill -9 -u baduser # kill all processes by user
pkill -f "python worker.py" # match against full command line
kill -0 1234 # test if process exists (no signal sent, returns 0/1)
| Signal | Number | Default Action | When to Use |
|---|---|---|---|
| SIGHUP | 1 | Terminate | Reload config (daemons) |
| SIGINT | 2 | Terminate | Ctrl+C equivalent |
| SIGTERM | 15 | Terminate | Graceful shutdown |
| SIGKILL | 9 | Kill | Force kill — last resort |
| SIGSTOP | 19 | Stop | Pause process |
| SIGCONT | 18 | Continue | Resume paused |
Gotcha:
SIGKILLcannot be caught, blocked, or ignored by the process. But it also means the process has no chance to flush buffers, close connections, or write state. Data loss is possible. Always trySIGTERMfirst and wait.
nice / renice — Process Priority
nice -n 19 backup.sh # start with lowest priority (19=lowest, -20=highest)
nice -n -10 critical_job # higher priority (requires root for negative values)
renice -n 10 -p 1234 # change priority of running process
renice -n 5 -u nginx # change priority of all nginx processes
Priority range: -20 (highest) to 19 (lowest). Default is 0.
Background Jobs & Job Control
command & # run in background
jobs -l # list background jobs with PIDs
fg %1 # bring job 1 to foreground
bg %2 # resume job 2 in background
ctrl+z # suspend foreground process (sends SIGSTOP)
disown %1 # detach job from shell (won't be killed on logout)
disown -a # disown all jobs
nohup long-job.sh > output.log 2>&1 & # immune to SIGHUP (survives logout)
Senior tip:
disownvsnohup:nohupredirects signals from terminal hangup at startup.disownremoves the job from the shell’s job table after it’s running, preventing SIGHUP on shell exit. Usenohupfor jobs you expect the terminal to close. Usedisownwhen you forgotnohup.
systemctl / journalctl
# Service management
systemctl start nginx
systemctl stop nginx
systemctl restart nginx
systemctl reload nginx # reload config without dropping connections
systemctl enable nginx # enable at boot
systemctl disable nginx
systemctl status nginx # detailed status + recent logs
systemctl is-active nginx # exit 0 if active (use in scripts)
systemctl is-enabled nginx
# List and query
systemctl list-units --type=service --state=running
systemctl list-unit-files --type=service
systemctl list-dependencies nginx
# journalctl log viewing
journalctl -u nginx # logs for nginx unit
journalctl -u nginx -f # follow logs
journalctl -u nginx --since "1 hour ago"
journalctl -u nginx --since "2026-01-01" --until "2026-01-02"
journalctl -p err -b # errors since last boot
journalctl -b -1 -p crit # critical messages from previous boot
journalctl --disk-usage # journal disk consumption
journalctl --vacuum-size=500M # trim journal to 500MB
5. Disk & Storage
df / du
df -hT # human-readable, show filesystem type
df -i # show inode usage (can fill up before disk is "full")
df -h /var # space for specific mount point
du -sh /var/log # total size of directory
du -h --max-depth=1 /var # size of each subdirectory, one level
du -h --exclude='*.log' /app # exclude pattern
du -sh * | sort -h | tail -20 # find 20 largest items in current dir
ncdu /var # interactive ncurses du (install separately)
Gotcha: Disk showing full but
dusays it’s not? Deleted files held open by a running process still occupy space. Find them:lsof | grep deleted— the file will show(deleted)but the process still has the fd open. Restart the process to reclaim space.
lsblk / mount / umount
lsblk # tree view of block devices
lsblk -f # show filesystem type and UUID
lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT,UUID
mount | column -t # show mounted filesystems, formatted
mount -t ext4 /dev/sdb1 /mnt/data # mount with explicit type
mount -o remount,ro / # remount root read-only (emergency)
mount -o loop image.iso /mnt/iso # mount ISO file
mount -t nfs server:/share /mnt/nfs
umount /mnt/data # unmount
umount -l /mnt/data # lazy umount (detach when no longer busy)
fuser -m /mnt/data # show which processes use the mount
lsof +D /mnt/data # alternative: list open files on mount
fdisk / parted / mkfs / fsck
fdisk -l # list all disks and partitions
fdisk /dev/sdb # interactive partition editor (MBR, up to 2TB)
parted /dev/sdb print # show partition table (works with GPT too)
parted -s /dev/sdb mklabel gpt # create GPT partition table
parted -s /dev/sdb mkpart primary ext4 0% 100%
mkfs.ext4 -L data /dev/sdb1 # format as ext4 with label
mkfs.xfs /dev/sdb1
mkfs.btrfs /dev/sdb1
# fsck — do NOT run on mounted filesystems
fsck -n /dev/sdb1 # dry run check (read-only)
fsck -y /dev/sdb1 # auto-repair
e2fsck -p /dev/sdb1 # ext4 auto-repair
dd — Disk Duplication
# Disk imaging
dd if=/dev/sda of=/mnt/backup/sda.img bs=4M status=progress
# Wipe disk (overwrite with zeros)
dd if=/dev/zero of=/dev/sdb bs=4M status=progress
# Create a test file of exact size
dd if=/dev/zero of=testfile bs=1M count=1024 # 1GB file
# Bootable USB
dd if=ubuntu.iso of=/dev/sdc bs=4M status=progress oflag=sync
Gotcha:
ddhas no guard againstif=andof=being swapped.dd if=/dev/sdb of=/dev/sdadestroys sda. Triple-check your source and destination. Considerddrescuefor recovery tasks — it’s safer and handles I/O errors better.
Swap Management
swapon -s # show swap usage
free -h # show RAM and swap
cat /proc/swaps # swap devices
# Create swap file
fallocate -l 4G /swapfile # fast allocation
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
# Add to /etc/fstab: /swapfile swap swap defaults 0 0
swapoff -a # disable all swap (before removing swapfile)
cat /proc/sys/vm/swappiness # current swappiness (default 60)
sysctl vm.swappiness=10 # reduce swap tendency (better for servers)
6. Networking
ip — Modern Network Tool (replaces ifconfig/route)
ip addr show # show all interfaces and IPs
ip addr show eth0 # specific interface
ip addr add 192.168.1.10/24 dev eth0 # add IP
ip addr del 192.168.1.10/24 dev eth0 # remove IP
ip link set eth0 up # bring interface up
ip link set eth0 down # bring interface down
ip link show # show link layer (MAC, MTU, state)
ip route show # routing table
ip route add default via 192.168.1.1 # add default gateway
ip route add 10.0.0.0/8 via 172.16.0.1 # static route
ip route del 10.0.0.0/8 # remove route
ip route get 8.8.8.8 # show which route is used for a destination
ip neigh show # ARP table
ip -s link show eth0 # interface statistics (packets, errors, drops)
ping / traceroute / dig / nslookup
ping -c 4 8.8.8.8 # 4 packets, then stop
ping -i 0.2 -c 100 host # fast ping (200ms interval)
ping -M do -s 1472 router # don't fragment, 1472 byte payload (MTU discovery)
traceroute -n 8.8.8.8 # numeric only (faster — no DNS lookups)
traceroute -T -p 443 host # TCP traceroute on port 443 (bypasses UDP blocks)
mtr -n --report host # combined ping+traceroute, report mode
dig google.com # DNS lookup
dig google.com MX # MX records
dig @8.8.8.8 google.com # query specific DNS server
dig +short google.com # just the IP
dig -x 8.8.8.8 # reverse DNS lookup
dig google.com +trace # trace DNS resolution from root
dig ANY google.com +noall +answer
nslookup google.com
host -t MX google.com # simple, fast DNS query
Senior tip:
dig +shortin scripts.nslookupis interactive and harder to parse. For cert expiry and DNS validation in CI pipelines, combine:dig +short TXT _dmarc.example.com
netstat / ss — Socket Statistics
# ss is the modern replacement for netstat
ss -tlnp # TCP, listening, numeric, show process
ss -tulnp # TCP + UDP
ss -s # socket summary statistics
ss -tnp state established # established TCP connections
ss -tnp dst 192.168.1.1 # connections to specific destination
ss -tnp src :443 # connections from port 443
# legacy netstat (may not be installed)
netstat -tlnp # same as ss -tlnp
netstat -an | grep ESTABLISHED | wc -l # count established connections
netstat -rn # routing table
curl / wget
# curl
curl -I https://example.com # headers only
curl -o file.tar.gz https://url/file # save to file
curl -L -o file url # follow redirects
curl -u user:pass https://api/endpoint # basic auth
curl -H "Authorization: Bearer $TOKEN" https://api/v1/data
curl -X POST -H "Content-Type: application/json" -d '{"key":"val"}' https://api/
curl -k https://self-signed.host # skip TLS verification (dev only)
curl --max-time 10 --retry 3 https://url # timeout + retries
curl -w "@curl-format.txt" -o /dev/null -s https://url # timing breakdown
curl --resolve example.com:443:1.2.3.4 https://example.com # force IP (CDN testing)
# wget
wget -q -O - https://url | tar xz # download and extract in one pipe
wget --mirror -p --no-parent https://site/ # mirror website
wget -c https://large-file.tar.gz # resume download
Senior tip:
curl -wwith a format file reveals DNS time, TCP connect time, TLS handshake time separately. Essential for diagnosing “slow site” — tells you if slowness is DNS, network, or server response.
nc (netcat) — Network Swiss Army Knife
nc -zv host 22 # port scan (verbose)
nc -zv host 20-80 # scan port range
nc -l 8080 # listen on port 8080 (simple server)
nc host 80 # connect to port 80 (type HTTP manually)
nc -l 1234 > received.txt # receive file
nc host 1234 < file.txt # send file
echo "test" | nc -w 1 host 25 # test SMTP connectivity
scp / rsync
scp file user@host:/remote/path # copy file to remote
scp -r dir/ user@host:/remote/ # recursive
scp -P 2222 file user@host:/path # custom port
rsync -avz /src/ user@host:/dst/ # archive, verbose, compress
rsync -avz --delete /src/ /dst/ # sync: delete files in dst not in src
rsync -avz --exclude='*.log' /src/ /dst/
rsync -avzn /src/ /dst/ # dry run (-n)
rsync -avz --progress /large/ /backup/ # show progress per file
rsync -e "ssh -p 2222 -i key.pem" /src/ user@host:/dst/ # custom SSH
Senior tip: Always include trailing slash on source
/src/vs/src— the difference is whether the directory itself is copied or just its contents./src/means “copy contents of src into dst”./srcmeans “copy src directory into dst”. Many engineers get bitten by this.
tcpdump — Packet Capture
tcpdump -i eth0 # capture on eth0
tcpdump -i any port 80 # any interface, port 80
tcpdump -i eth0 'host 10.0.0.1' # traffic to/from host
tcpdump -i eth0 'port 443 and host api.example.com'
tcpdump -i eth0 -w capture.pcap # write to file (for Wireshark)
tcpdump -r capture.pcap # read from file
tcpdump -i eth0 -n -nn # no name resolution (faster)
tcpdump -i eth0 tcp[tcpflags] & tcp-syn != 0 # SYN packets only (new connections)
iptables / nftables Basics
iptables -L -n -v # list rules with packet/byte counts
iptables -A INPUT -p tcp --dport 22 -j ACCEPT # allow SSH
iptables -A INPUT -s 10.0.0.0/8 -j ACCEPT # allow from subnet
iptables -A INPUT -j DROP # default deny (add last)
iptables -I INPUT 1 -p tcp --dport 80 -j ACCEPT # insert at position 1
iptables-save > /etc/iptables/rules.v4 # persist rules
iptables-restore < /etc/iptables/rules.v4
# nftables (modern replacement)
nft list ruleset
nft add rule ip filter input tcp dport 22 accept
7. User & Group Management
User Operations
useradd -m -s /bin/bash -G sudo,docker username # create user with home, shell, groups
useradd -r -s /sbin/nologin serviceaccount # system account (no login)
usermod -aG docker username # add to group (without -a, replaces groups)
usermod -s /bin/zsh username # change shell
usermod -L username # lock account
usermod -U username # unlock account
usermod -e 2026-12-31 username # set expiry date
userdel -r username # delete user and home directory
# Change password
passwd username # interactive
echo "user:newpass" | chpasswd # non-interactive (use in scripts carefully)
passwd -e username # force password change at next login
passwd -l username # lock (same as usermod -L)
# Query
id username # show uid, gid, groups
groups username # show groups
finger username # detailed user info (if installed)
getent passwd username # query NSS (works for LDAP users too)
/etc/passwd, /etc/shadow, /etc/group Structure
/etc/passwd: username:x:uid:gid:comment:home:shell
root:x:0:0:root:/root:/bin/bash
nginx:x:101:103:nginx web server,,,:/var/lib/nginx:/sbin/nologin
/etc/shadow: username:hashed_password:lastchg:min:max:warn:inactive:expire
root:$6$salt$hash:19000:0:99999:7:::
/etc/group: groupname:x:gid:member1,member2
docker:x:998:deploy,jenkins
Gotcha:
usermod -G group user(without-a) replaces the user’s group list with justgroup. The-aflag appends. Forgetting-ahas locked people out of sudo access in production.
sudo / visudo
sudo command # run as root
sudo -u postgres psql # run as specific user
sudo -i # interactive root shell (loads root environment)
sudo -l # list allowed sudo commands for current user
sudo -k # expire sudo timestamp (require password next time)
visudo # safely edit /etc/sudoers (syntax validation)
# In /etc/sudoers:
deploy ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart nginx # passwordless specific command
%developers ALL=(ALL) NOPASSWD: ALL # group with full access
jenkins ALL=(ALL) NOPASSWD: /usr/bin/docker # CI/CD common pattern
Senior tip: Never edit
/etc/sudoersdirectly. Usevisudo— it validates syntax before saving. A syntax error in sudoers can completely lock you out of sudo.visudo -cchecks syntax without editing.
8. Package Management
apt (Debian/Ubuntu)
apt update # refresh package index
apt upgrade # upgrade installed packages
apt full-upgrade # upgrade + handle dependency changes
apt install nginx postgresql # install packages
apt install -y nginx # non-interactive (yes to all)
apt remove nginx # remove (keep config)
apt purge nginx # remove + delete config
apt autoremove # remove orphaned packages
apt search "web server" # search packages
apt show nginx # package details
apt list --installed # list installed packages
apt list --upgradable # show available upgrades
apt-cache policy nginx # show installed and candidate versions
# Hold/unhold packages (prevent upgrades)
apt-mark hold nginx
apt-mark unhold nginx
apt-mark showhold
dpkg (low-level)
dpkg -i package.deb # install local .deb file
dpkg -l nginx # show installed package info
dpkg -L nginx # list files installed by package
dpkg -S /usr/sbin/nginx # which package owns a file
dpkg -r nginx # remove package
dpkg --configure -a # fix broken installs
yum / dnf (RHEL/CentOS/Fedora)
dnf install nginx # install
dnf update # update all
dnf update nginx # update specific
dnf remove nginx
dnf search nginx
dnf info nginx
dnf list installed
dnf history # transaction history
dnf history undo 15 # undo transaction 15
dnf autoremove # remove unused dependencies
# Repos
dnf repolist
dnf config-manager --enable epel
dnf install epel-release # enable EPEL repo
rpm (low-level)
rpm -ivh package.rpm # install verbose with progress
rpm -qa # list all installed RPMs
rpm -ql nginx # list files from package
rpm -qf /usr/sbin/nginx # which package owns file
rpm -qi nginx # package info
rpm --checksig package.rpm # verify GPG signature
9. Archiving & Compression
tar
# Create archives
tar -czf archive.tar.gz dir/ # create gzip-compressed
tar -cjf archive.tar.bz2 dir/ # bzip2 (slower, better compression)
tar -cJf archive.tar.xz dir/ # xz (best compression, slowest)
tar -cf archive.tar dir/ # no compression
# Extract
tar -xzf archive.tar.gz # extract gzip
tar -xzf archive.tar.gz -C /target/ # extract to specific directory
tar -xzf archive.tar.gz file.txt # extract single file
tar -tzf archive.tar.gz # list contents without extracting
# Useful combinations
tar -czf - /data | ssh host "tar -xzf - -C /backup" # remote backup over SSH
tar -czf - /src | pv | tar -xzf - -C /dst # copy with progress (pv)
tar --exclude='*.log' --exclude='.git' -czf app.tar.gz app/
Memory aid: czf = Create + gZip + File. xzf = eXtract + gZip + File. The verb (c/x/t) always comes first.
gzip / bzip2 / xz
gzip file.txt # compress (replaces original with file.txt.gz)
gzip -d file.txt.gz # decompress
gzip -k file.txt # keep original
gzip -9 file.txt # max compression
zcat file.txt.gz # view without decompressing
zgrep "error" file.gz # grep inside gzipped file
bzip2 file.txt # .bz2 — better compression than gzip
bunzip2 file.txt.bz2
xz file.txt # .xz — best compression ratio
xz -d file.txt.xz
# Compression speed vs ratio (approximately)
# gzip: fastest, ~50% reduction
# bzip2: 2x slower than gzip, ~55% reduction
# xz: 10x slower than gzip, ~65% reduction
zip / unzip
zip -r archive.zip dir/ # create zip recursively
zip -e secrets.zip file.txt # encrypt with password
unzip archive.zip # extract
unzip archive.zip -d /target/ # extract to directory
unzip -l archive.zip # list contents
unzip -p archive.zip file.txt # extract single file to stdout
10. System Information & Monitoring
uname / hostname / uptime / free
uname -a # all: kernel, hostname, arch
uname -r # kernel version only
uname -m # machine hardware (x86_64, aarch64)
hostname # current hostname
hostname -f # FQDN
hostnamectl set-hostname new-name # set hostname (systemd)
uptime # load averages: 1, 5, 15 minute
w # who is logged in + load average
who # logged-in users
free -h # RAM and swap, human-readable
free -h -s 2 # refresh every 2 seconds
cat /proc/meminfo # detailed memory breakdown
Load average interpretation: Load average represents average active (running + waiting) processes. On a 4-core system, load of 4.0 means full utilization. Load of 8.0 means processes are waiting. Load of 1.0 on a 4-core system = 25% utilization.
vmstat / iostat / sar
vmstat 2 10 # 2-second intervals, 10 samples
# Output: procs, memory, swap, io, system, cpu
# bi/bo: blocks in/out per second (disk I/O)
# wa: CPU waiting for I/O (high wa = I/O bottleneck)
# si/so: swap in/out (non-zero = memory pressure)
iostat -x 2 # extended disk stats, 2-second intervals
# %util: disk utilization (near 100% = saturated)
# await: average I/O wait time in ms
# r_await/w_await: separate read/write wait
sar -u 2 5 # CPU utilization, 2s interval, 5 samples
sar -r 2 5 # memory utilization
sar -d 2 5 # disk I/O
sar -n DEV 2 5 # network interface stats
sar -f /var/log/sysstat/sa20 # historical data from specific day
Senior tip: When investigating a production slowdown, run
vmstat 1and watchwa(I/O wait) andsi/so(swap activity) simultaneously. Highwa+ lowsi/so= disk bottleneck. Highsi/so= memory pressure causing swapping.
dmesg / lsof / strace
dmesg # kernel ring buffer (boot messages, hardware events)
dmesg -T # human-readable timestamps
dmesg -w # follow (like tail -f)
dmesg | grep -i error # filter errors
dmesg | grep -i "oom" # out-of-memory events
lsof # list open files (all processes)
lsof -p 1234 # files opened by PID 1234
lsof -u nginx # files opened by user nginx
lsof -i :80 # processes using port 80
lsof -i TCP:443 # TCP connections on 443
lsof +D /var/log # files open in directory
lsof | grep deleted # deleted files still held open
strace -p 1234 # trace syscalls of running process
strace -f -p 1234 # trace including forked children
strace -c command # count syscalls (summary)
strace -e trace=open,read,write command # trace specific syscalls
strace -o strace.log -ff command # write to file
Senior tip:
lsof | grep deletedis your first move whendfshows high usage butdudoesn’t explain it. Deleted log files held open by a process don’t free space until the process closes them or is restarted.
/proc Filesystem
cat /proc/cpuinfo # CPU details (model, cores, flags)
cat /proc/meminfo # memory stats
cat /proc/loadavg # load averages + running/total processes
cat /proc/uptime # seconds since boot
cat /proc/version # kernel version string
cat /proc/mounts # currently mounted filesystems
cat /proc/net/tcp # TCP socket table (hex addresses)
ls /proc/1234/ # directory for PID 1234
cat /proc/1234/cmdline # full command line of process
cat /proc/1234/status # process status (memory, threads)
ls -la /proc/1234/fd/ # file descriptors opened by process
cat /proc/1234/net/tcp # process's network connections
cat /proc/sys/net/ipv4/ip_forward # check IP forwarding
echo 1 > /proc/sys/net/ipv4/ip_forward # enable IP forwarding (temporary)
11. Shell & Scripting Essentials
Pipes and Redirection
command > file # stdout to file (overwrite)
command >> file # stdout to file (append)
command 2> errors.log # stderr to file
command 2>&1 # redirect stderr to same place as stdout
command &> file # both stdout and stderr to file (bash 4+)
command > /dev/null 2>&1 # silence all output
# Pipes
ls | grep ".sh" | wc -l
ps aux | sort -k3 -rn | head -10 # top 10 by CPU
cat access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head # top IPs
# Here document
cat > /etc/motd << 'EOF'
Welcome to production server
Unauthorized access is prohibited
EOF
# single-quoted 'EOF' prevents variable expansion
# Process substitution
diff <(ls dir1) <(ls dir2) # diff output of two commands
comm <(sort file1) <(sort file2) # compare sorted files
xargs
find /tmp -name "*.tmp" | xargs rm -f # delete found files
find . -name "*.py" | xargs grep "TODO" # grep in found files
cat servers.txt | xargs -I{} ssh {} uptime # -I{}: placeholder
cat urls.txt | xargs -P 8 -I{} curl -o /dev/null {} # 8 parallel
echo "a b c" | xargs -n1 echo # one arg per line
find . -name "*.log" | xargs -r gzip # -r: don't run if no input
Senior tip:
xargs -P Nruns N processes in parallel. Combine withfind -print0andxargs -0when filenames might contain spaces:find . -name "*.txt" -print0 | xargs -0 grep "pattern". Without-0, filenames with spaces break everything.
Variables and Loops
# Variables
VAR="value"
echo "$VAR" # always quote variables
echo "${VAR}_suffix" # braces for disambiguation
readonly CONST="immutable" # prevent modification
unset VAR # delete variable
# Default values
echo "${VAR:-default}" # use 'default' if VAR is unset/empty
echo "${VAR:=default}" # assign and use 'default' if unset
VAR="${1:?Error: argument required}" # exit with error if unset
# For loop
for host in web1 web2 web3; do
ssh "$host" "systemctl status nginx"
done
for file in *.log; do
gzip "$file"
done
for i in $(seq 1 10); do
echo "Step $i"
done
# While loop
while read -r line; do
echo "Processing: $line"
done < input_file
while true; do
if systemctl is-active --quiet nginx; then break; fi
sleep 5
done
# C-style for loop
for ((i=0; i<10; i++)); do
echo "$i"
done
Conditionals
# File tests
if [ -f file.txt ]; then echo "regular file"; fi
if [ -d /var/log ]; then echo "directory exists"; fi
if [ -e file ]; then echo "exists (any type)"; fi
if [ -r file ]; then echo "readable"; fi
if [ -x script.sh ]; then echo "executable"; fi
if [ -s file ]; then echo "non-empty"; fi
if [ -L symlink ]; then echo "is symlink"; fi
# String tests
if [ -z "$VAR" ]; then echo "empty string"; fi
if [ -n "$VAR" ]; then echo "non-empty string"; fi
if [ "$A" = "$B" ]; then echo "equal"; fi
if [[ "$string" =~ ^[0-9]+$ ]]; then echo "numeric"; fi
# Numeric tests
if [ "$count" -gt 10 ]; then echo "greater than 10"; fi
if [ "$count" -eq 0 ]; then echo "zero"; fi
# Logical
if [ -f f1 ] && [ -f f2 ]; then echo "both exist"; fi
if [[ -f f1 && -f f2 ]]; then echo "both exist (bash)"; fi
[ vs [[: [[ is bash-specific but safer — handles spaces in strings without quoting issues, supports regex with =~, and doesn’t word-split. Use [[ in bash scripts, [ for POSIX portability.
Safe Script Patterns
#!/usr/bin/env bash
set -euo pipefail
# -e: exit on error
# -u: treat unset variables as errors
# -o pipefail: pipe fails if any command fails (not just last)
# Trap for cleanup
TMPFILE=$(mktemp)
trap "rm -f $TMPFILE" EXIT SIGINT SIGTERM
# Trap for debugging
trap 'echo "Error on line $LINENO"' ERR
# Logging
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >&2; }
log "Starting deployment"
# Lock file (prevent concurrent runs)
LOCKFILE="/tmp/my-script.lock"
exec 200>$LOCKFILE
flock -n 200 || { log "Already running"; exit 1; }
Senior tip:
set -euo pipefailis the mandatory header for all production scripts. Without-o pipefail,false | truereturns exit code 0. Without-u, referencing$UNSET_VARsilently expands to empty string — a common source of bugs likerm -rf "$DIR/"whenDIRis unset.
12. SSH & Remote
ssh Basics
ssh user@host # connect
ssh -p 2222 user@host # custom port
ssh -i ~/.ssh/deploy_key user@host # specify key
ssh -v user@host # verbose (debug connection)
ssh -A user@host # forward SSH agent (enables jump hosts)
ssh -X user@host # X11 forwarding
ssh user@host "command" # run remote command
ssh user@host 'bash -s' < script.sh # run local script remotely
ssh-keygen / ssh-agent
ssh-keygen -t ed25519 -C "user@company.com" # generate key (Ed25519 recommended)
ssh-keygen -t rsa -b 4096 -C "comment" # RSA 4096 (legacy compat)
ssh-keygen -p -f ~/.ssh/id_ed25519 # change passphrase
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@host # deploy public key
eval $(ssh-agent -s) # start agent in current shell
ssh-add ~/.ssh/id_ed25519 # add key to agent
ssh-add -l # list keys in agent
ssh-add -t 3600 key # add key with 1-hour expiry
SSH Config (~/.ssh/config)
Host bastion
HostName 203.0.113.1
User ec2-user
IdentityFile ~/.ssh/prod_key
StrictHostKeyChecking no
ServerAliveInterval 60
Host prod-*
User ubuntu
IdentityFile ~/.ssh/prod_key
ProxyJump bastion
Host prod-web-01
HostName 10.0.1.10
Host prod-db-01
HostName 10.0.2.10
After this config: ssh prod-web-01 auto-jumps through bastion.
Port Forwarding
# Local port forwarding: access remote service locally
ssh -L 5432:db.internal:5432 user@bastion
# Now: psql -h localhost -p 5432 connects to db.internal through bastion
# Remote port forwarding: expose local service to remote
ssh -R 8080:localhost:3000 user@public-server
# Now: public-server:8080 forwards to your local port 3000
# Dynamic (SOCKS proxy)
ssh -D 1080 user@bastion
# Now: configure browser to use localhost:1080 as SOCKS5 proxy
# Browse as if you're on the bastion's network
# Background tunnel with persistent reconnect
ssh -fNL 5432:db:5432 -o ServerAliveInterval=30 -o ExitOnForwardFailure=yes user@bastion
autossh -M 0 -fNL 5432:db:5432 user@bastion # auto-reconnect (requires autossh)
Senior tip:
ProxyJump(or-Jflag) replacedProxyCommandfor jump hosts. It’s cleaner and properly chains SSH agent forwarding. Use-J bastion user@internal-hoston command line orProxyJump bastionin config.
sshfs
sshfs user@host:/remote/path /local/mountpoint # mount remote filesystem
sshfs -o reconnect,ServerAliveInterval=15 user@host:/data /mnt/data
fusermount -u /local/mountpoint # unmount (Linux)
umount /local/mountpoint # alternative
13. Cron & Scheduling
crontab
crontab -e # edit crontab for current user
crontab -l # list
crontab -r # remove all (careful — no confirmation)
crontab -u username -l # list for specific user
Cron syntax:
# ┌───────────── minute (0-59)
# │ ┌───────────── hour (0-23)
# │ │ ┌───────────── day of month (1-31)
# │ │ │ ┌───────────── month (1-12)
# │ │ │ │ ┌───────────── day of week (0-7, 0 and 7 are Sunday)
# │ │ │ │ │
# * * * * * command
0 2 * * * /opt/scripts/backup.sh # daily at 2 AM
0 */6 * * * /opt/scripts/sync.sh # every 6 hours
0 9 * * 1-5 /opt/scripts/report.sh # weekdays at 9 AM
*/5 * * * * /opt/scripts/healthcheck.sh # every 5 minutes
@reboot /opt/scripts/startup.sh # on boot
@daily /opt/scripts/daily.sh # midnight daily
Cron gotchas:
- PATH is minimal in cron (
/usr/bin:/bin). Use full paths or set PATH at top of crontab. - No interactive terminal — redirect output:
command >> /var/log/cron.log 2>&1 - Environment variables in shell profile (~/.bashrc) are NOT loaded.
# Good cron job pattern
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
MAILTO=ops@company.com
0 2 * * * /opt/scripts/backup.sh >> /var/log/backup.log 2>&1
at — One-time Scheduling
at 2:30pm tomorrow # schedule for tomorrow afternoon
at now + 2 hours # 2 hours from now
at 10:00 2026-06-01 # specific date
atq # list pending jobs
atrm 3 # remove job 3
systemd Timers (modern cron replacement)
# /etc/systemd/system/backup.timer
[Unit]
Description=Daily Backup Timer
[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true # run if missed (e.g., system was off)
Unit=backup.service
[Install]
WantedBy=timers.target
# /etc/systemd/system/backup.service
[Unit]
Description=Backup Service
[Service]
Type=oneshot
ExecStart=/opt/scripts/backup.sh
User=backup
systemctl enable --now backup.timer
systemctl list-timers # show all timers + next run time
systemctl list-timers --all # include inactive
journalctl -u backup.service # view execution logs
Senior tip: Systemd timers have
Persistent=truewhich retriggers missed runs after downtime. Standard cron silently skips missed runs. For critical batch jobs, systemd timers are more reliable.
14. Log Management
/var/log Structure
| Path | Contents |
|---|---|
/var/log/syslog or /var/log/messages | General system messages |
/var/log/auth.log or /var/log/secure | Authentication, sudo, SSH |
/var/log/kern.log | Kernel messages |
/var/log/dmesg | Boot + hardware messages |
/var/log/nginx/access.log | Nginx access |
/var/log/nginx/error.log | Nginx errors |
/var/log/postgresql/ | PostgreSQL logs |
/var/log/apt/ | APT package actions |
journalctl — systemd Journal
journalctl -n 100 # last 100 lines
journalctl -f # follow (like tail -f)
journalctl -u nginx -f # follow specific service
journalctl -p err # errors only (emerg,alert,crit,err)
journalctl -p warning..err # warning through error
journalctl -b # since current boot
journalctl -b -1 # previous boot (crashed system)
journalctl --since "2026-01-01 00:00" --until "2026-01-01 23:59"
journalctl --since "1 hour ago"
journalctl -u nginx --since today -o json-pretty # JSON output
journalctl --no-pager | grep "Connection refused" | wc -l # count occurrences
# Space management
journalctl --disk-usage
journalctl --vacuum-time=7d # delete entries older than 7 days
journalctl --vacuum-size=1G # trim to 1GB
Log Analysis Patterns
# Real-time monitoring with filtering
tail -f /var/log/nginx/access.log | grep --line-buffered "500"
# Count HTTP status codes
awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn
# Top 10 IPs
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -10
# Slowest requests (from access log with request time)
awk '{print $NF, $7}' /var/log/nginx/access.log | sort -rn | head -20
# Failed SSH logins
grep "Failed password" /var/log/auth.log | awk '{print $11}' | sort | uniq -c | sort -rn
# OOM kills
grep -i "killed process" /var/log/kern.log
dmesg | grep -i "oom\|out of memory"
# Find errors in last 10 minutes
journalctl -p err --since "10 minutes ago" --no-pager
logrotate
logrotate -d /etc/logrotate.conf # dry run (show what would happen)
logrotate -f /etc/logrotate.conf # force rotation now
cat /etc/logrotate.d/nginx # per-app config
Example logrotate config:
/var/log/myapp/*.log {
daily
rotate 14 # keep 14 rotations
compress # gzip old logs
delaycompress # don't compress most recent rotation (process may still write)
missingok # don't error if log missing
notifempty # skip if empty
postrotate
nginx -s reopen # signal app to reopen log files
endscript
}
Gotcha: After logrotate moves a log file, the application still writes to the old file descriptor (the moved file). You must send the app a signal to reopen its log file handles, or use
copytruncate(copy then truncate in place — causes small data loss window). Always usepostrotate/prerotatefor proper rotation with running services.
Quick Reference: One-Liners for Production
# What process is listening on port X?
ss -tlnp | grep :8080
lsof -i :8080
# Disk space used by largest directories
du -h --max-depth=2 / 2>/dev/null | sort -h | tail -20
# Find recently changed files (last 10 minutes)
find /etc -mmin -10 -type f
# Kill all processes matching a name
pkill -9 -f "stale_worker"
# Watch a command output refresh every 2s
watch -n 2 'ss -s'
# Show environment variables of a running process
cat /proc/$(pgrep nginx | head -1)/environ | tr '\0' '\n'
# Follow multiple log files simultaneously
tail -f /var/log/nginx/error.log /var/log/app/error.log
# Benchmark disk write speed
dd if=/dev/zero of=/tmp/test bs=4k count=100000 oflag=direct 2>&1
# Check if a remote port is open (pure bash, no nc needed)
timeout 3 bash -c "cat < /dev/null > /dev/tcp/host/22" && echo open
# Capture all traffic to/from a host for 30 seconds
timeout 30 tcpdump -i eth0 -w /tmp/capture.pcap host 10.0.0.50
# Real-time memory pressure
watch -n 1 'free -h; echo; vmstat 1 1'
# Find which service is causing most disk writes
iotop -obP -d 2
# Quick cert expiry check
echo | openssl s_client -connect host:443 2>/dev/null | openssl x509 -noout -dates
# List all open ports in one line
ss -tlnp | awk 'NR>1 {print $4}' | sort -u