Part 1: Getting Your Bearings with df
Before you start hunting, you need a high-level view of what is mounted and how full each filesystem is.
Basic disk usage overview
df -hThe -h flag gives human-readable output. The column to watch is Use%. Anything above 85% warrants attention; above 95% is urgent.
Filesystem Size Used Avail Use% Mounted on
/dev/vda1 40G 38G 1.6G 96% /
tmpfs 2.0G 512M 1.5G 26% /dev/shm
/dev/vda2 100G 42G 58G 43% /var/lib/dockerCheck inodes too
You can run out of inodes before you run out of bytes. This happens when a directory contains millions of tiny files — common with mail spools, session storage, or misconfigured PHP applications.
df -iIf IUse% is near 100% on a filesystem that shows plenty of free space, inode exhaustion is your problem.
Part 2: Drilling Down with du
du (disk usage) reports actual space consumed by files and directories. It's your primary tool for narrowing down bloat.
Find top-level hogs
du -h --max-depth=1 / 2>/dev/null | sort -rh | head -20Drill down recursively
Once you identify a large directory, keep going one level at a time:
du -h --max-depth=1 /var | sort -rh | head -20Find individual large files
find / -xdev -type f -size +500M 2>/dev/null | sort -k5 -rnThe -xdev flag prevents crossing filesystem boundaries, avoiding Docker overlays and network mounts.
Size-sorted with human-readable output
find /var -type f -printf '%s %p\n' 2>/dev/null | sort -rn | head -20 | awk '{printf "%.1fM %s\n", $1/1048576, $2}'Count files in a directory (inode diagnosis)
find /var/spool/clientmqueue -type f | wc -lIf this returns millions of files, you've found your inode problem.
Part 3: Interactive Exploration with ncdu
ncdu is a terminal-based, interactive disk usage browser that makes drill-down much faster than repeated du invocations.
# Debian/Ubuntu
apt install ncdu
# RHEL/CentOS/AlmaLinux
dnf install ncdu
# Or download a static binary (no dependencies)
curl -Lo ncdu.tar.gz https://dev.yorhel.nl/download/ncdu-2.7-linux-x86_64.tar.gz
tar -xf ncdu.tar.gz
mv ncdu /usr/local/bin/ncdu /Navigation
- Arrow keys or
j/kto move Enterto enter a directoryqto go back / quitdto delete (asks confirmation)eto show hidden files
Scan to file for later analysis
ncdu -o /tmp/disk-scan.json /
# Later:
ncdu -f /tmp/disk-scan.jsonssh user@server 'ncdu -o- /' | ncdu -f-Usual Suspects: Log Files
Unrotated or misconfigured logs are the single most common cause of surprise disk consumption on VPS instances.
find /var/log -type f -name "*.log" -printf '%s %p\n' | sort -rn | head -20 | awk '{printf "%.1fM %s\n", $1/1048576, $2}'find /var/log -type f -name "*.log.*" ! -name "*.gz" | head -20Common culprits
/var/log/auth.log— brute force SSH attempts can bloat to gigabytes/var/log/nginx/access.log— high-traffic sites with no rotation/var/log/mysql/error.log- Application logs in
/var/wwwor custom paths
Safely truncate a live log file
Important: Do not rm a log file while a process has it open — the process will keep writing to the deleted inode and space will not be reclaimed.
# Truncate to zero
truncate -s 0 /var/log/nginx/access.log
# Or redirect an empty string (same effect)
> /var/log/nginx/access.logFix the root cause with logrotate
# Check logrotate config
cat /etc/logrotate.conf
ls /etc/logrotate.d/
# Test a specific config without rotating
logrotate --debug /etc/logrotate.d/nginx
# Force immediate rotation
logrotate --force /etc/logrotate.d/nginx/var/log/nginx/*.log {
daily
missingok
rotate 7
compress
delaycompress
notifempty
create 0640 www-data adm
sharedscripts
postrotate
nginx -s reopen
endscript
}Usual Suspects: Old Kernel Packages
On Debian/Ubuntu, kernel updates leave old kernels installed. Each takes 150–300 MB. A system running for a year or two can accumulate a gigabyte or more.
# List all installed kernels
dpkg --list | grep -E 'linux-image|linux-headers' | awk '{print $2, $3}'
# See which kernel is currently running
uname -r
# Automated removal of kernels not needed for the current boot
apt autoremove --purge# List installed kernels
rpm -q kernel
# Keep only the 2 most recent kernels
dnf remove $(dnf repoquery --installonly --latest-limit=-2 -q)Before removing kernels: Make sure your system boots successfully on the current kernel. Verify you can access the rescue console in case a reboot is needed.
Usual Suspects: Package Manager Caches
Package managers cache downloaded packages locally. On an active system, these caches can grow to several gigabytes.
Debian/Ubuntu (APT)
# Check cache size
du -sh /var/cache/apt/archives/
# Remove only obsolete packages
apt autoclean
# Remove all cached packages (safe — they can be re-downloaded)
apt cleanRHEL/CentOS/AlmaLinux (DNF/YUM)
# Check cache size
du -sh /var/cache/dnf/
# Clean all cached data
dnf clean allLanguage-specific caches
# Python pip
pip cache info
pip cache purge
# npm
npm cache verify
npm cache clean --force
# Composer (PHP)
composer clear-cache
du -sh ~/.cache/composer/Usual Suspects: Orphaned Docker Data
Docker is one of the most aggressive accumulators of silent disk usage. Stopped containers, dangling images, unused volumes, and stale build cache can quietly consume tens of gigabytes.
# Get a complete overview
docker system df
# Verbose breakdown per object
docker system df -vTargeted cleanup
# Prune dangling images only (safe default)
docker image prune
# Prune ALL unused images (including tagged but not running)
docker image prune -a
# Remove stopped containers
docker container prune
# Remove unused volumes (review first!)
docker volume ls
docker volume prune
# Remove unused networks
docker network prune
# Clean build cache
docker builder prune
docker builder prune --keep-storage 1GBNuclear option: docker system prune -a --volumes removes everything Docker is not actively using. Do not run this on a system with intentionally stopped containers or databases stored in Docker volumes without external backups.
Usual Suspects: Temp Files & Core Dumps
Temporary files
# Check temp directory sizes
du -sh /tmp /var/tmp
# Find old files in /tmp (not modified in over 7 days)
find /tmp -type f -mtime +7 -delete
# systemd-tmpfiles manages /tmp cleanup on systemd systems
systemd-tmpfiles --cleanCore dumps
Application crashes leave core dump files that can be enormous.
# Find core dumps
find / -xdev -name "core" -o -name "core.[0-9]*" 2>/dev/null | head -20
# Check systemd's coredump storage
ls -lh /var/lib/systemd/coredump/
coredumpctl list
# Remove all stored coredumps
rm -rf /var/lib/systemd/coredump/*Usual Suspects: Mail Spool
Undelivered or bounced mail can accumulate in the mail spool, consuming both space and inodes.
# Check postfix queue sizes
postqueue -p | tail -1
# Check the mail spool directory
du -sh /var/spool/postfix/deferred /var/spool/postfix/active /var/spool/postfix/bounce
# Flush the deferred queue (retry all)
postqueue -f
# Delete all messages in the deferred queue (be careful)
postsuper -d ALL deferredPart 5: Reclaiming Space Safely
Verify space was actually freed
Deleted files may not immediately show as free space if a running process still has them open. This is common with log files that were deleted instead of truncated.
# Find deleted files still held open by running processes
lsof +L1 | grep deletedIf restarting the process isn't an option:
# Get the file descriptor path from lsof output (PID and FD number)
# Then truncate via /proc
truncate -s 0 /proc/<PID>/fd/<FD>Check filesystem free blocks
# ext4 filesystems
tune2fs -l /dev/vda1 | grep -E "Block count|Free blocks|Block size"
# XFS filesystems
xfs_info /dev/vda1Set Up Monitoring to Prevent Recurrence
#!/bin/bash
THRESHOLD=85
ALERT_EMAIL="you@example.com"
df -h | awk 'NR>1' | while read line; do
USAGE=$(echo "$line" | awk '{print $5}' | tr -d '%')
MOUNT=$(echo "$line" | awk '{print $6}')
if [ "$USAGE" -gt "$THRESHOLD" ]; then
echo "DISK ALERT: $MOUNT is at ${USAGE}% on $(hostname)" | \
mail -s "Disk Space Warning" "$ALERT_EMAIL"
fi
done# Check every 30 minutes
*/30 * * * * /usr/local/bin/disk-check.shFor a more complete solution, consider integrating with your existing monitoring stack. Prometheus with node_exporter exposes disk metrics out of the box. If you're running Grafana, set threshold alerts on node_filesystem_avail_bytes.
Quick Reference: Space Recovery Cheatsheet
| Action | Command | Risk |
|---|---|---|
| Truncate a live log | truncate -s 0 /path/to/app.log | Safe |
| Force log rotation | logrotate --force /etc/logrotate.d/app | Safe |
| Clean APT cache | apt clean | Safe |
| Clean DNF cache | dnf clean all | Safe |
| Remove old kernels | apt autoremove --purge | Low |
| Prune dangling Docker images | docker image prune | Safe |
| Prune all unused Docker images | docker image prune -a | Medium |
| Prune Docker volumes | docker volume prune | High |
| Full Docker system prune | docker system prune -a --volumes | High |
| Delete deferred mail queue | postsuper -d ALL deferred | Medium |
| Clear pip cache | pip cache purge | Safe |
| Clear npm cache | npm cache clean --force | Safe |
Rule of thumb: Always run df -h before and after each cleanup step to confirm space was reclaimed. Start with safe operations and work toward impactful ones only if necessary.
