Find the Files Eating Your Disk
The disk was full, but guessing at folders was the slow part.
find /var -type f -printf '%s %p\n' | sort -nr | head -20
topic
The commands to inspect a machine before guessing.
110 checked fixes
The disk was full, but guessing at folders was the slow part.
find /var -type f -printf '%s %p\n' | sort -nr | head -20
The app was failing now. Opening a giant log file was the wrong move.
tail -n 80 -f /var/log/nginx/error.log
The error was in the log. The problem was finding it without reading noise.
grep -iE 'error|failed|denied|timeout' /var/log/nginx/error.log | tail -40
The error was there. The useful part was knowing exactly where it was.
grep -inE 'error|failed|denied|timeout' /var/log/nginx/error.log
The disk was full. The fastest clue was the folder, not the file.
du -sh /var/* 2>/dev/null | sort -h
The log had old failures too. I only cared about the newest ones.
grep -iE 'error|failed|denied|timeout' /var/log/nginx/error.log | tail -10
The file existed. The owner and mode explained why it still failed.
stat -c '%A %U:%G %n' /var/www/example/index.html
The server felt slow. Memory pressure was the first thing to rule out.
ps -eo pid,comm,%mem,%cpu --sort=-%mem | head
Byte counts are precise. Human units are faster under pressure.
find /var -type f -printf '%s %p\n' | sort -nr | head -10 | awk '{printf "%.1f MB %s\n", $1/1024/1024, $2}'
You can inspect an archive without extracting it.
tar -tf archives/site-backup.tar | sort | head
A quick extension count can show whether expected content made it into the source tree.
find source -type f -printf '%f\n' | sed -n 's/.*\.//p' | sort | uniq -c | sort -nr
Before package triage, prove what OS family and release you are actually on.
. /etc/os-release && printf '%s %s %s\n' "$ID" "$VERSION_ID" "$VERSION_CODENAME"
The distro version and kernel version answer different questions.
printf 'kernel=%s arch=%s distro=%s\n' "$(uname -r)" "$(uname -m)" "$(lsb_release -ds)"
A package inventory beats memory when a server is drifting.
dpkg-query -W -f='${Package}\t${Version}\t${Architecture}\n' | sort
Before you upgrade anything, list what would move.
apt list --upgradable
apt policy explains where the next version would come from.
apt policy nginx
For one package, dpkg-query gives a clean status line.
dpkg-query -W -f='${Status} ${Version}\n' openssl
That binary came from somewhere. dpkg can tell you where.
dpkg-query -S /usr/sbin/nginx
Not every package row is cleanly installed.
dpkg-query -W -f='${db:Status-Abbrev}\t${Package}\n' | awk '$1 !~ /^ii$/'
Disk cleanup starts with evidence, not random package removal.
dpkg-query -W -f='${Installed-Size}\t${Package}\n' | sort -nr | head -20
One unexpected architecture can explain confusing dependency output.
dpkg-query -W -f='${Architecture}\t${Package}\n' | awk '$1 != "amd64" && $1 != "all"'
Huge logs often point to loops, noisy tests, or runaway debug output.
find logs/ -type f -printf '%s %p\n' | sort -nr | head -10
Turn noisy test logs into a ranked failure list.
grep -RhoE '[A-Za-z0-9_./-]+\.(test|spec)\.(js|ts|py|rb)' logs/ | sort | uniq -c | sort -nr | head
The certificate can be valid but issued for the wrong name.
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | openssl x509 -noout -subject -issuer -dates
The wrong certificate is often in the server block.
grep -RInE 'ssl_certificate|ssl_certificate_key|server_name' /etc/nginx/sites-enabled /etc/nginx/conf.d 2>/dev/null
Apache can check syntax before you reload a live site.
sudo apachectl configtest
Apache 403 often comes from the directory block, not the file.
grep -RInE 'DocumentRoot|<Directory|Require all|Options|AllowOverride' /etc/apache2/sites-enabled /etc/apache2/conf-enabled /etc/httpd/conf.d 2>/dev/null
The Apache error log usually names the denied directory or rule.
sudo tail -80 /var/log/apache2/error.log 2>/dev/null || sudo tail -80 /var/log/httpd/error_log
Restart loops are visible before rebuilding anything.
docker ps -a --format 'table {{.Names}} {{.Image}} {{.Status}} {{.RestartCount}}'
Recent container logs usually show the failing command or dependency.
docker logs --tail 80 container_name
Policy and exit code explain whether Docker is restarting by design.
docker inspect -f '{{.HostConfig.RestartPolicy.Name}} {{.State.ExitCode}} {{.State.Error}}' container_name
A timer can be inactive, missed, or waiting for the wrong calendar.
systemctl list-timers --all --no-pager
Timer status shows whether the schedule is loaded and active.
systemctl status backup.timer --no-pager
The calendar expression lives in the timer unit.
systemctl cat backup.timer
The auth log proves whether the server refused after too many offered keys.
grep -i 'Too many authentication failures' /var/log/auth.log /var/log/secure 2>/dev/null | tail -20
Too many loaded keys can cause the client to offer the wrong identities first.
ssh-add -l
Force one key when the agent offers too many identities.
ssh -o IdentitiesOnly=yes -i ~/.ssh/id_ed25519 user@example.com
SANs decide which hostnames the certificate covers.
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | openssl x509 -noout -ext subjectAltName
Apache may not be using the vhost file you edited.
find /etc/apache2/sites-enabled -maxdepth 1 -type l -printf '%f -> %l
' 2>/dev/null | sort
Docker events show restart and die loops over time.
docker events --since 30m --until 0s
Timer logs show whether systemd attempted to trigger the task.
journalctl -u backup.timer --since "24 hours ago" --no-pager
Verbose SSH shows which key was offered.
ssh -vvv -o IdentitiesOnly=yes -i ~/.ssh/id_ed25519 user@example.com
curl can show the certificate path a client actually sees.
curl -Iv https://example.com/ 2>&1 | sed -n "/SSL connection/,/expire date/p"
The first error often explains more than the last one.
awk '{buf[NR%5]=$0} tolower($0) ~ /(error|exception|fatal)/ {for (i=NR-4;i<=NR;i++) if (i>0) print buf[i%5]; exit}' /var/log/app/app.log
Exit code 137 often means the kernel has something to say.
journalctl -k --since "2 hours ago" --no-pager -o short-iso | grep -Ei 'out of memory|oom|killed process'
A config test is safer than a blind reload.
sudo nginx -t
A 403 is often in a parent directory, not the file.
namei -l /srv/www/site/index.html
The file can exist and still be unreadable to Nginx.
stat -c '%A %U:%G %n' /srv/www/site/index.html
One deny or alias can explain the whole 403.
grep -RInE 'root|alias|deny|allow' /etc/nginx/sites-enabled /etc/nginx/conf.d 2>/dev/null
The error log often names the denied path.
sudo tail -80 /var/log/nginx/error.log
Certbot has a safe renewal rehearsal.
sudo certbot renew --dry-run
Know which names Certbot thinks it manages.
sudo certbot certificates
Certbot logs usually name the failed challenge.
sudo tail -80 /var/log/letsencrypt/letsencrypt.log
A renewal can fail because DNS points elsewhere.
dig +short example.com A
No default route means packets have nowhere to leave.
ip route
A route problem may actually be an address problem.
ip addr
NetworkManager may know why the interface is disconnected.
nmcli device status
DNS state can distract from route failures.
resolvectl status
Use debug mode before forcing rotation.
logrotate -d /etc/logrotate.conf
The timer may be disabled, missed, or failing.
systemctl status logrotate.timer --no-pager
The journal can show why rotation skipped.
journalctl -u logrotate --since '7 days ago' --no-pager
A full /boot blocks kernel updates.
df -h /boot
Know installed kernels before cleanup.
dpkg -l 'linux-image*' | awk '/^ii/{print $2,$3}'
RPM systems keep multiple kernels too.
rpm -q kernel
Never delete the kernel you are running.
uname -r
Do not edit known_hosts blind.
ssh-keygen -F hostname
Keyscan collects a presented key; it does not verify trust.
ssh-keyscan -t ed25519 hostname
See the stored entry before editing.
grep -n 'hostname' ~/.ssh/known_hosts
The file mode can look fine while a parent directory blocks the whole path.
namei -l /srv/www/example/current/config/prod.token
Linux memory numbers look scary until you know which column matters.
free -h
A high load number is a clue, not a diagnosis.
uptime
Cron problems often hide behind comments, blank lines, and copied folklore.
crontab -l | sed -n '/^[[:space:]]*#/d;/^[[:space:]]*$/d;p'
Cron is easier to debug when the schedule and command stop blending together.
crontab -l | awk 'NF && $1 !~ /^#/ {printf "%-16s %s\n", $1" "$2" "$3" "$4" "$5, substr($0,index($0,$6))}'
A timer is only half the scheduled job. The service is the payload.
systemctl list-timers --all --no-pager --plain | awk 'NR==1 || /\.timer/ {print $(NF-1), "->", $NF}'
A package lock is usually a symptom, not the first thing to delete.
ps -ef | grep -E 'apt|dpkg|dnf|yum|pacman' | grep -v grep
Find the process holding the dpkg lock before touching lock files.
sudo lsof /var/lib/dpkg/lock-frontend /var/lib/dpkg/lock 2>/dev/null
Automatic apt timers can explain locks that seem mysterious.
systemctl list-timers apt-daily* --no-pager
The hidden carriage return is often the whole bug.
head -1 script.sh | cat -v
Confirm CRLF before converting anything.
file script.sh
Find exactly which lines still contain carriage returns.
grep -n $'\r' script.sh | head
Bad fstab entries should be found before the next reboot.
findmnt --verify
systemd often records mount failures as failed units.
systemctl --failed --no-pager
The boot journal often names the mount or dependency that failed.
journalctl -b -p warning --no-pager | grep -iE 'mount|fstab|dependency'
Before restarting a service, prove whether the kernel killed it.
journalctl -k --since '24 hours ago' --no-pager | grep -iE 'out of memory|oom-killer|killed process'
Measure memory and swap before changing service limits.
free -h
Find current memory owners before restarting workloads.
ps -eo pid,comm,%mem,%cpu --sort=-%mem | head
Start sudo troubleshooting with the actual target account.
id username
Group membership explains many sudo and access failures.
groups username
sudo -l shows policy; guessing invites bad access changes.
sudo -l
Before granting sudo, see who already has it.
getent group sudo
Count handles for the target process before raising limits.
sudo lsof -p 1234 | wc -l
A shell ulimit is not the service limit.
systemctl show nginx -p LimitNOFILE --no-pager
Look at one target process, not the whole host, when pressure is scoped.
sudo lsof -p 1234 | head
Before querying a database file, see what tables are actually inside it.
sqlite3 app.db ".tables"
Before comparing sitemap coverage, print the URLs plainly.
grep -o '<loc>[^<]*</loc>' public/sitemap.xml | sed 's#<loc>##;s#</loc>##'
One command tells you which services systemd already knows are broken.
systemctl --failed --no-pager
Make systemctl status safe for scripts, screenshots, and quick incident notes.
systemctl status nginx --no-pager --lines=30
Ignore stale logs and inspect only what happened since this boot.
journalctl -u nginx -b --no-pager -n 80
Before deleting random logs, ask journald how much disk it owns.
journalctl --disk-usage
Find which units made your VPS boot slowly.
systemd-analyze blame | head -20
Running now does not mean it will survive the next reboot.
systemctl is-enabled nginx
Get a clean yes-or-no service state without the full status page.
systemctl is-active nginx
Confirm whether the server actually rebooted and when.
last -x reboot | head -5
See whether memory is actually tight before restarting services.
free -h
Cron is not the only scheduler on modern Linux servers.
systemctl list-timers --all --no-pager
The status page often tells you the failed startup step before you open every log.
systemctl status app-worker --no-pager --lines=50
Turn a noisy service failure into four fields you can paste into an incident note.
systemctl show app-worker --property=Result,ExecMainCode,ExecMainStatus,NRestarts --no-pager
The bug may be in an override file, not the main unit.
systemctl cat app-worker
Clear the red failed state only after you have captured the evidence.
systemctl reset-failed app-worker
Put the failed step next to the unit config that created it.
systemctl status app-worker --no-pager --lines=50 && systemctl cat app-worker