VPS Tutorials
VPS Disk Full? Disk Cleanup + Expansion in One Guide (With Commands)
When a VPS disk hits 100%, the worst part is "I deleted a bunch of stuff and there's still no space." I wrote this article as a real-world workflow: first find out what's taking up space, then deal with inodes / deleted-but-still-held files, and clean up Docker, logs, and caches along the way; finally, how to safely expand the disk and grow the filesystem with it.

Everyone who's had a disk suddenly fill up knows the feeling:
- The website starts throwing 500s
- MySQL says it can't write
- Even SSH login lags (because it can't write to
~/.bash_historyeither)
What's worse: you delete files for ages, and df -h is still 100%.
The workflow below is what I usually use for firefighting, and the goal is clear: first pinpoint "who ate the space," then decide whether to clean, migrate, or expand.
Check capacity first:
df -hT
Focus on three columns:
Use%: whether a particular mount point (like/or/var) blew up on its ownType: ext4 / xfs / overlay (Docker) affects how you expand laterMounted on: don't clean a data disk as if it were the system disk—cleaning the wrong disk is even more awkward
Then check inodes (a very common pitfall):
df -ih
If there's still capacity left but inodes are at 100%, the symptoms are exactly the same as a full disk. Common causes: cache directories, temp directories, small-file explosions (like some logs rotating by the second).
I usually scan down from the root directory, finding the "largest layer" first.
# 统计 / 下一级目录占用(可按需换成 /var、/home、/opt)
sudo du -xh --max-depth=1 / | sort -h | tail -n 20
If you want to save time, just use ncdu (highly recommended to install):
# Debian/Ubuntu
sudo apt-get update && sudo apt-get install -y ncdu
# 交互式查看
sudo ncdu /
The advantage of ncdu: you don't have to guess "is it this directory?"—it puts the largest files/directories right in front of you.
3) You deleted files but space didn't come back? It's probably "deleted but still held by a process"
Typical scenario:
- You deleted a huge log
- The service is still writing to that log (the file handle wasn't closed)
dflooks completely unchanged
Find it with this:
sudo lsof +L1
If the output shows deleted but the file size is huge, that's basically it.
The fix is simple:
- Most reliable: restart the corresponding service (Nginx / PHP-FPM / MySQL / your app)
- Don't want to restart: make the process reopen the file (for example, some services support
logrotate'scopytruncateor sending aUSR1signal)
Most of the time, restarting the service is the fastest firefighting.
If you use Docker, take a look first:
docker system df
Clean up (start with the "safe" stuff):
# 删除没用的构建缓存
docker builder prune -f
# 删除没用的镜像/容器/网络(不会删正在跑的容器)
docker system prune -af
# 如果你确定不需要旧 volume(慎用)
docker volume prune -f
By the way: for many people, a full disk isn't images—it's container logs.
# 看看某个容器日志有多大
sudo du -sh /var/lib/docker/containers/*/*-json.log | sort -h | tail -n 20
If logs really blew up, it's recommended to configure log rotation for Docker (a long-term solution).
# 看 journal 占用
sudo journalctl --disk-usage
# 只保留 7 天(示例)
sudo journalctl --vacuum-time=7d
# 或者限制到 1G
sudo journalctl --vacuum-size=1G
The two most common directories:
/var/log/nginx//var/log/apache2/
Quickly find the largest:
sudo du -sh /var/log/* | sort -h | tail -n 20
If you're just firefighting, compress/delete historical logs first (don't touch files currently being written, to avoid the pitfall in section 3).
# 清缓存
sudo apt-get clean
# 删没用的依赖
sudo apt-get autoremove --purge -y
sudo du -sh /tmp/* 2>/dev/null | sort -h | tail -n 20
When "some task crashed" and filled up temp files, this one usually makes it obvious at a glance.
First find where small files are concentrated:
# 找出 /var 下面小文件数量最多的目录
sudo find /var -xdev -type f 2>/dev/null | awk -F/ '{print $1"/"$2"/"$3"/"$4}' | sort | uniq -c | sort -nr | head
Common culprits:
- Some service frantically writing small logs
- A cache directory with no TTL
- Temp files never cleaned up
The cleanup approach is simple: delete the "regenerable" (cache/tmp), keep the "non-regenerable" (data/uploads/databases).
Before expanding, confirm: did you actually expand the disk in the cloud console, or do you just "want to expand"?
lsblk -f
# 也可以看分区表
sudo fdisk -l
You need to figure out:
- Whether it's the same disk (like
/dev/vdaor/dev/sda) - Which partition your filesystem is mounted on (like
/dev/vda1) - Whether there's LVM (if you see
lvm2_member, that's a different path)
# 安装 growpart(Debian/Ubuntu)
sudo apt-get update && sudo apt-get install -y cloud-guest-utils
# 把第 1 号分区扩到整块盘(示例:/dev/vda1)
sudo growpart /dev/vda 1
# 扩文件系统(ext4)
sudo resize2fs /dev/vda1
# 验证
df -hT
XFS can't use resize2fs; do it like this:
# 先 growpart 扩分区
sudo growpart /dev/vda 1
# 然后对挂载点扩(假设挂载点是 /)
sudo xfs_growfs /
df -hT
# 扩物理卷
sudo pvresize /dev/vda1
# 看逻辑卷路径
sudo lvdisplay
# 扩逻辑卷(示例:把所有空闲都加给 root)
sudo lvextend -l +100%FREE /dev/mapper/ubuntu--vg-ubuntu--lv
# ext4
sudo resize2fs /dev/mapper/ubuntu--vg-ubuntu--lv
# xfs(如果是 xfs)
# sudo xfs_growfs /
If you're not familiar with expansion, I suggest saving the output of lsblk -f before you start—don't mess up the partition table while firefighting.
The most basic way: cron + threshold alert.
cat >/usr/local/bin/disk-alert.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
THRESHOLD=85
MOUNT=${1:-/}
usep=$(df -P "$MOUNT" | awk 'NR==2 {gsub(/%/,"",$5); print $5}')
if [ "$usep" -ge "$THRESHOLD" ]; then
echo "[disk-alert] $MOUNT usage=${usep}%" >&2
fi
EOF
chmod +x /usr/local/bin/disk-alert.sh
- Nginx/Apache: make sure logrotate is running
- Application logs: don't write endlessly to one file; rotate daily and keep 7-14 days
- Docker: enable log rotation (don't let json logs become several GB)
df -hT/df -ih: confirm whether it's capacity full or inode fulldu -xh --max-depth=1orncdu: find the largest directorylsof +L1: rule out "deleted but not released"- Focus on checking
/var/log, Docker, journal, cache - Not enough after cleanup: expand +
growpart+resize2fs/xfs_growfs
If you follow this order, you basically won't take detours.