List Newest Source Files Before Backup
Before trusting a backup, know which files changed most recently.
find source -type f -printf '%TY-%Tm-%Td %TH:%TM %p\n' | sort
problem area
Web hosting, SSL, DNS, Nginx, deployment, backups, and VPS management.
71 checked fixes
Before trusting a backup, know which files changed most recently.
find source -type f -printf '%TY-%Tm-%Td %TH:%TM %p\n' | sort
A file list says what exists; checksums say whether bytes match.
sha256sum source/app/config.yml source/content/index.md source/content/about.md source/assets/logo.svg
A checksum file is only useful if you actually verify it.
sha256sum -c checksums.sha256
A backup can be missing files and still look plausible at a glance.
comm -3 <(find source -type f | sed 's#^source/##' | sort) <(find backup -type f | sed 's#^backup/##' | sort)
Rsync can tell you what would change before it changes anything.
rsync -ain --delete source/ backup/
Zero-byte files can be normal, or they can be failed writes.
find backup -type f -size 0 -print
Large backup files are where storage surprises usually start.
find backup -type f -printf '%s %p\n' | sort -nr | head
Files newer than the last snapshot are the ones most likely missing from it.
find source -type f -newer backup/.snapshot -print | sort
The database was running, but it was not ready.
pg_isready -h 127.0.0.1 -p 5432
The database was not down. It was full.
psql -X -A -F '|' -c "select pid,usename,datname,state,client_addr from pg_stat_activity order by state, pid;"
One query can make the whole app look broken.
psql -X -c "select pid, now() - query_start as age, state, left(query, 80) as query from pg_stat_activity where query_start is not null order by age desc limit 10;"
The outage was a queue, not a crash.
psql -X -c "select pid, wait_event_type, wait_event, state, left(query, 80) as query from pg_stat_activity where wait_event_type is not null order by pid;"
Disk pressure starts with knowing what grew.
psql -X -c "select datname, pg_size_pretty(pg_database_size(datname)) as size from pg_database order by pg_database_size(datname) desc;"
The port was open. MySQL still had to answer.
mysqladmin ping -h 127.0.0.1 -P 3306
The app was waiting behind busy sessions.
mysql -e "show full processlist;"
One old query explained the whole slowdown.
mysql -e "select id,user,host,db,command,time,state,left(info,80) as info from information_schema.processlist where command <> 'Sleep' order by time desc limit 10;"
The storage alert needed a database name.
mysql -e "select table_schema, round(sum(data_length + index_length)/1024/1024, 1) as mb from information_schema.tables group by table_schema order by mb desc;"
Skip the full CI log and jump straight to lines that usually explain the failure.
grep -RInE 'error|failed|exception|traceback|fatal' logs/ | tail -50
Confirm what your pipeline actually produced before you deploy it.
find artifacts/ -type f -printf '%TY-%Tm-%Td %TH:%TM %10s %p\n' | sort | tail -20
See your newest release directories without opening a dashboard.
find releases/ -mindepth 1 -maxdepth 1 -type d -printf '%T@ %TY-%Tm-%Td %TH:%TM %p\n' | sort -nr | head -10 | cut -d' ' -f2-
Verify two artifact copies match before blaming deployment code.
sha256sum artifacts/app.tar.gz releases/current/app.tar.gz
Find the image tags your deployment files reference without printing env values.
grep -RhoE 'image:[[:space:]]*[^[:space:]]+' deploy/ | sort -u
Turn noisy docker ps output into the few fields operators scan first.
docker ps -a --format 'table {{.Names}}\t{{.Status}}\t{{.Image}}\t{{.Ports}}'
Docker may say a container is running while its health check says otherwise.
docker inspect --format '{{.Name}} health={{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}} status={{.State.Status}}' web
Get Docker resource usage once, without leaving a live dashboard running.
docker stats --no-stream --format 'table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}\t{{.BlockIO}}'
See how Docker storage is split across images, containers, volumes, and cache.
docker system df -v
A container can be healthy and still attached to the wrong network.
docker inspect --format '{{.Name}} {{range $name, $net := .NetworkSettings.Networks}}{{$name}} {{$net.IPAddress}} {{end}}' api
The config looked fine. Nginx disagreed before reload broke anything.
nginx -t
The config existed, but it was not enabled.
ls -l /etc/nginx/sites-enabled/
The wrong server block was answering the domain.
grep -R "server_name" /etc/nginx/sites-enabled/
HTTPS worked. The plain HTTP redirect still mattered.
curl -I http://example.com
The page loaded, but the headers told the operational story.
curl -sI https://example.com
The site was fine. The domain was pointed somewhere else.
dig +short example.com A
The certificate existed. The question was which domains it covered.
certbot certificates
The deploy finished. The symlink told me what was actually live.
readlink -f /srv/www/example.com/current
The missing file was not random. The access log had a pattern.
awk '$9==404 {print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -nr | head
LinkedIn traffic was not a guess. The referrer field showed it.
awk -F'"' '{print $4}' /var/log/nginx/access.log | sort | uniq -c | sort -nr | head
A server feels slow, but you need proof before restarting anything.
ps -eo pid,ppid,stat,pcpu,pmem,comm,args --sort=-pcpu | head -n 10
Memory pressure can look like a slow app, a stuck deploy, or random crashes.
ps -eo pid,ppid,stat,pcpu,pmem,rss,comm,args --sort=-pmem | head -n 10
Sometimes the disk has free bytes but still cannot create files.
df -ih
A file can be deleted but still occupy disk while a process holds it open.
lsof +L1
A failed query is often just a wrong assumption about column names.
sqlite3 app.db ".schema users"
When a SQLite-backed app behaves strangely, first rule out file corruption.
sqlite3 app.db "PRAGMA integrity_check;"
System metadata tables can distract from the app tables you care about.
sqlite3 app.db "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;"
A quick row count can reveal empty imports, runaway events, or missing data.
sqlite3 app.db "SELECT 'users', count(*) FROM users UNION ALL SELECT 'orders', count(*) FROM orders UNION ALL SELECT 'events', count(*) FROM events;"
Slow lookups often start with missing or misunderstood indexes.
sqlite3 app.db "PRAGMA index_list('orders');"
For small apps, the quickest timeline may be inside the SQLite file.
sqlite3 app.db "SELECT created_at, event_type FROM events ORDER BY created_at DESC LIMIT 5;"
A noisy event type stands out faster when you group it.
sqlite3 app.db "SELECT event_type, count(*) FROM events GROUP BY event_type ORDER BY count(*) DESC;"
Duplicate account data is easier to spot with one grouped query.
sqlite3 app.db "SELECT email, count(*) FROM users GROUP BY email HAVING count(*) > 1;"
Copying a live SQLite file blindly can produce a bad backup.
sqlite3 app.db ".backup backup/app.db"
Duplicate titles make a static site harder to scan in search results and browser tabs.
grep -Rho --include='*.html' '[^<]* ' public | sed 's###;s# ##' | sort | uniq -c | sort -nr
Canonical tags are easy to drop when templates branch.
find public -name '*.html' -print | while read -r f; do grep -qi 'rel="canonical"' "$f" || echo "$f"; done
A leftover noindex can hide a page after launch.
grep -Rni --include='*.html' 'noindex' public
Missing descriptions are usually a content template problem, not a mystery.
find public -name '*.html' -print | while read -r f; do grep -qi 'name="description"' "$f" || echo "$f"; done
A sitemap can exist and still be hard to discover.
grep -n '^Sitemap:' public/robots.txt
A page can exist in the build but never make it into the sitemap.
find public -name '*.html' -print | sed 's#^public#https://example.com#' | while read -r url; do grep -q "$url" public/sitemap.xml || echo "$url"; done
Social previews often fail because one template missed Open Graph tags.
find public -name '*.html' -print | while read -r f; do grep -qi 'property="og:title"' "$f" || echo "$f"; done
Your feed can advertise URLs that the sitemap never lists.
grep -o 'https://example.com/[^<]*' public/feed.xml | sed 's###;s###' | while read -r url; do grep -q "$url" public/sitemap.xml || echo "$url"; done
The site was configured, but the port was not.
grep -RInE '^[[:space:]]*listen[[:space:]]' fixtures/nginx/conf.d fixtures/nginx/sites-enabled
The wrong site answered because it was the fallback.
grep -RIn 'default_server' fixtures/nginx/conf.d fixtures/nginx/sites-enabled
The config was valid; it just was not included.
grep -RInE '^[[:space:]]*include[[:space:]]' fixtures/nginx/nginx.conf fixtures/nginx/conf.d fixtures/nginx/sites-enabled
The URL was right. The filesystem path was not.
grep -RInE '^[[:space:]]*(root|alias)[[:space:]]' fixtures/nginx/conf.d fixtures/nginx/sites-enabled
Nginx was healthy. It was proxying to the wrong place.
grep -RInE '^[[:space:]]*proxy_pass[[:space:]]' fixtures/nginx/conf.d fixtures/nginx/sites-enabled
The Apache config existed. The enabled symlink did not.
find fixtures/apache/sites-enabled -maxdepth 1 -type l -printf '%f -> %l\n' | sort
Apache chose a virtual host. You need to know which one.
grep -RInE '
Apache was serving files from a different directory than expected.
grep -RInE '^[[:space:]]*DocumentRoot[[:space:]]' fixtures/apache/sites-enabled
Apache was up. The reverse proxy target was wrong.
grep -RInE '^[[:space:]]*(ProxyPass|ProxyPassReverse)[[:space:]]' fixtures/apache/sites-enabled
The redirect loop was hiding in plain text.
grep -RInE 'return[[:space:]]+30[18]|rewrite[[:space:]]|Redirect[[:space:]]|RewriteRule|RewriteCond' fixtures/nginx fixtures/apache
Before chasing individual lines, get the shape of the whole log.
awk '{count[$9]++} END {for (code in count) print count[code], code}' ./fixtures/nginx/access.log | sort -nr
A 500 spike is easier to triage when the broken path is obvious.
awk '$9 ~ /^5/ {count[$7]++} END {for (path in count) print count[path], path}' ./fixtures/nginx/access.log | sort -nr | head
A few huge responses can explain bandwidth, latency, and suspicious download patterns.
awk '$10 ~ /^[0-9]+$/ && $10 > 1000000 {print $10, $1, $7, $9}' ./fixtures/nginx/access.log | sort -nr | head