Skip to main content
Published on November 14, 2026 “No space left on device”: your Linux VPS has filled its disk. From that moment, everything that writes fails. The database rejects queries, PHP can no longer create sessions, an update stops halfway, the journal drops lines. The good news: the space can almost always be recovered without breaking anything, once you know where it went. This guide follows the order of a diagnosis, on Debian, Ubuntu, AlmaLinux and Rocky Linux. Each section gives the problem, the command that fixes it and how to check.

Where a VPS’s disk space goes

The most common sources, as of November 14, 2026:

Before cleaning up: take a snapshot

A cleanup is quick. So is one deletion too many. Take a snapshot from the console before touching binlogs, Docker volumes or kernels. You keep a one-click way back. Backups remain your baseline protection.

Diagnose a full disk

Which filesystem is full

Find the line at 100% and its mount point (Mounted on). Most of the time, it is /.

Blocks or inodes

A disk can be full while df -h shows free space. Every file uses one inode, and on ext4 their number is set when the filesystem is created. Millions of small files run out of inodes before they run out of space.
If IUse% reads 100%, find the directory holding the most files:
Then go one level down into the top directory (sudo du --inodes -x -d 1 /var | sort -n | tail). The usual culprits: PHP sessions, application cache, mail queue.

Find the largest directories

The -x option matters: it stays on the filesystem of / and skips other mounts. For faster navigation, ncdu does the same interactively:
At 100% usage, the install itself may fail. Start with apt clean or dnf clean all (below) to make some room.

Deleted files that are still open

Problem. You deleted a large log with rm and df did not move. The file is gone from the tree, but a process still holds it open: the space only comes back when the file is closed. The same applies when du reports far less than df. Fix. List open files whose last link was removed:
COMMAND gives the process, SIZE/OFF the size. Restart the service involved:
If the service cannot be restarted right away, empty the file through its descriptor. The PID and FD (without the trailing letter, 4w becomes 4) come from the lsof output:
Check. sudo lsof -nP +L1 no longer lists the file, and df -h / shows the space returned.

Shrink the systemd journal

Problem. /var/log/journal takes several gigabytes. Fix. Measure, then shrink:
Vacuuming only touches archived files. To make it stick, set a cap. By default, journald allows itself 10% of the filesystem. The journald.conf man page recommends a separate file in /etc/systemd/journald.conf.d/ rather than editing the main file:
Check. journalctl --disk-usage stays under the chosen value over the following days.

Empty and rotate application logs

Problem. A file in /var/log or in an application directory grows forever. Fix. Find it:
To empty it without stopping the service writing to it, use truncate rather than rm:
Then give it a rotation. logrotate ships with all four distributions and reads /etc/logrotate.d/:
copytruncate copies the file, then empties it in place: the application keeps writing to the same file. If it can reopen its logs on a signal, prefer a postrotate block that sends it. Check. Debug mode simulates the rotation without changing anything:

Clear the package cache

Problem. Packages downloaded during updates stay on disk. Fix.
Both are safe: packages are downloaded again when needed. Check. sudo du -sh /var/cache/apt or sudo du -sh /var/cache/dnf is back to a few megabytes.

Remove old kernels

Problem. Each kernel update installs a new version next to the previous ones, with its modules in /lib/modules and its image in /boot. Fix on Debian and Ubuntu. Simulate first, then remove:
APT protects the running kernel and the newest one. Read the list anyway: autoremove also removes dependencies that have become orphans. Fix on AlmaLinux and Rocky. dnf keeps a fixed number of versions of “installonly” packages, the kernel among them, set by installonly_limit in /etc/dnf/dnf.conf. To remove the extra versions now:
Check. uname -r gives the running kernel, which is still listed by dpkg -l 'linux-image*' or rpm -q kernel.

Clean up Docker

Problem. /var/lib/docker keeps growing: replaced images, stopped containers, build cache, container logs. Fix. Measure by category:
Then clean what is no longer used:
Do not add --volumes before checking the list with docker volume ls. A volume often holds a database’s data. A deleted volume cannot be recovered, except from a snapshot or a backup.
Container logs are the other frequent source. The default driver, json-file, does not rotate. Find the largest:
Turn on rotation in /etc/docker/daemon.json (merge with the existing content if the file exists):
The Docker documentation is explicit: the setting only applies to containers created afterwards. Recreate the existing ones (docker compose up -d --force-recreate for a Compose project). Check. docker system df shows a RECLAIMABLE column close to zero.

Purge MySQL and MariaDB binlogs

Problem. /var/lib/mysql holds dozens of binlog.000123 or mysql-bin.000123 files. Fix. Never delete them with rm: the server keeps an index of these files. Use SQL:
With a replica, first check on it which file it is reading (SHOW REPLICA STATUS\G): a stopped or lagging replica still needs the binlogs it has not read. For automatic expiry, the variable depends on the server: expire_logs_days was removed from MySQL in 8.2: on MySQL 8.4, only binlog_expire_logs_seconds exists. Apply the value live, then make it permanent:
Check. SHOW VARIABLES LIKE 'binlog_expire_logs_seconds'; returns 604800, and SHOW BINARY LOGS; goes back no further than seven days.

The ext4 root reserve

Problem. df shows 100% for your applications, yet root can still write. That is by design. Explanation. By default, ext4 reserves 5% of blocks for privileged processes. The tune2fs man page gives the reason: limit fragmentation and keep system daemons running once regular users can no longer write. This reserve is what still lets you log in and clean up.
On a large data disk, you can lower it. On /, only do it to get out of an emergency, then restore the value:
The reserve only exists on ext2, ext3 and ext4. tune2fs does not apply to XFS: findmnt tells you the type.

When the disk is simply too small

Once the cleanup is done, if / climbs back above 85% within a few weeks, your usage has simply grown. Pick a VPS plan with more storage, then extend the partition to use the new space.

When the VPS stops responding

Prevent it: a threshold alert

A full disk is visible long before it happens. A short script warns you first, for space and for inodes:
Sending relies on the mail command (bsd-mailx on Debian and Ubuntu, s-nail on AlmaLinux and Rocky) and a local mail server. Our SPF, DKIM, DMARC and reverse DNS guide covers deliverability. Check. Run the script with a threshold of 1 to get a test message:
Add a logrotate rule for every application that writes its own logs, and a cap on the journal: those are the two sources that come back fastest.

Common pitfalls

rm on an open log. The space is not returned while the process runs. Use truncate -s 0, or restart the service afterwards. Deleting journal files by hand. journalctl --vacuum-size only removes archives, never the file being written. Let it do the job rather than running rm in /var/log/journal. Files hidden under a mount point. Files written to a directory before a disk was mounted on it take space on / but stay invisible to du. Bind-mount / elsewhere to see them:
Out of inodes, not space. df -h shows free space and the error persists: look at df -i. docker system prune --volumes in production. It deletes volumes unused at that moment, including the one of a database whose container is stopped. Binlogs deleted by hand. The server keeps their list in an index file that rm does not update. Always go through PURGE BINARY LOGS.

Further reading

Have a question about a server hosted with us? Our team is available from your client area.