Cuando el disco se llena, los síntomas aparecen por todas partes: bases de datos que no arrancan, errores No space left on device, logs que dejan de escribirse o una aplicación web que devuelve errores 500. En esta guía aprenderás a localizar qué ocupa el espacio en Ubuntu 24.04, a liberarlo sin borrar nada importante y a configurar el sistema para que no vuelva a ocurrir.

Requisitos previos

  • Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath. En Debian 12 los comandos son los mismos.
  • Un usuario no root con privilegios sudo.

Paso 1: Confirmar qué sistema de ficheros está lleno

df muestra el uso de cada sistema de ficheros. Excluye los sistemas virtuales para ver solo los discos reales:

df -hT -x tmpfs -x devtmpfs -x squashfs -x efivarfs
Filesystem     Type  Size  Used Avail Use% Mounted on
/dev/vda1      ext4   39G   38G  0     100% /
/dev/vda16     ext4  881M  112M  707M  14% /boot
/dev/vda15     vfat  105M  6.1M   99M   6% /boot/efi

Fíjate en la columna Mounted on: a partir de aquí buscarás dentro de ese punto de montaje.

Un disco también se considera "lleno" si se agotan los inodos (el número máximo de ficheros), aunque quede espacio libre. Compruébalo:

df -i /
Filesystem      Inodes   IUsed  IFree IUse% Mounted on
/dev/vda1      2580480 2580480      0  100% /

Si IUse% está al 100 %, salta directamente al paso 6.

Paso 2: Encontrar los directorios que más ocupan

du suma el tamaño de cada directorio. La opción -x evita entrar en otros sistemas de ficheros y -d1 limita la salida al primer nivel:

sudo du -xh -d1 / 2>/dev/null | sort -rh | head -15
38G     /
29G     /var
6.1G    /usr
1.8G    /home

Repite el comando bajando por el directorio más grande hasta encontrar el culpable:

sudo du -xh -d1 /var 2>/dev/null | sort -rh | head -10
sudo du -xh -d1 /var/lib 2>/dev/null | sort -rh | head -10

Para explorar de forma interactiva, ncdu es mucho más cómodo. Te permite navegar con las flechas y borrar con d:

sudo apt install ncdu
sudo ncdu -x /

Paso 3: Encontrar los ficheros más grandes

Si prefieres ir directamente a los ficheros, este comando lista los 20 mayores de 100 MB en el sistema de ficheros raíz, con el tamaño en formato legible:

sudo find / -xdev -type f -size +100M -printf '%s %p\n' 2>/dev/null | sort -rn | head -20 | numfmt --field=1 --to=iec
12G /var/log/myapp/debug.log
4.3G /var/lib/docker/containers/3f2a.../3f2a...-json.log
2.0G /swapfile
1.1G /var/lib/mysql/binlog.000412

Los culpables más habituales son logs de aplicaciones sin rotación, logs de contenedores Docker, binlogs de MySQL, copias de seguridad antiguas y volcados de memoria.

Paso 4: Buscar ficheros borrados que siguen abiertos

Si df dice que el disco está lleno pero du suma mucho menos, lo más probable es que haya ficheros borrados que un proceso mantiene abiertos. El espacio no se libera hasta que el proceso cierra el fichero. Ocurre a menudo al borrar un log con rm mientras el servicio sigue escribiendo en él. Encuéntralos con lsof:

sudo lsof -nP +L1
COMMAND   PID     USER   FD   TYPE DEVICE    SIZE/OFF NLINK   NODE NAME
java    18233   myapp    5w   REG  252,1 9663676416     0 524301 /var/log/myapp/app.log (deleted)

La forma limpia de liberar ese espacio es reiniciar el servicio que tiene el fichero abierto:

sudo systemctl restart myapp

Si no puedes reiniciarlo, vacía el fichero a través del descriptor usando el PID y el número de la columna FD (sin la letra):

sudo truncate -s 0 /proc/18233/fd/5

Para evitarlo en el futuro, vacía los logs activos con truncate en lugar de borrarlos con rm:

sudo truncate -s 0 /var/log/myapp/debug.log

Paso 5: Liberar espacio en los sitios habituales

Journal de systemd

Comprueba cuánto ocupa el journal y redúcelo:

journalctl --disk-usage
sudo journalctl --vacuum-size=500M
Archived and active journals take up 3.8G in the file system.
Vacuuming done, freed 3.3G of archived journals from /var/log/journal/...

Para que no vuelva a crecer, fija un tamaño máximo con un fichero de configuración adicional:

sudo mkdir -p /etc/systemd/journald.conf.d
sudo nano /etc/systemd/journald.conf.d/size.conf
[Journal]
SystemMaxUse=500M

Reinicia el servicio para aplicarlo:

sudo systemctl restart systemd-journald

Logs rotados en /var/log

Los logs que ya ha rotado logrotate (con extensión .gz o .1) se pueden borrar sin problemas:

sudo find /var/log -type f \( -name '*.gz' -o -name '*.[0-9]' \) -mtime +7 -delete

Caché de apt y paquetes huérfanos

apt guarda los paquetes descargados en /var/cache/apt/archives. Vacíalo y elimina las dependencias que ya no se usan, incluidos los kernels antiguos:

sudo apt clean
sudo apt autoremove --purge

Revisiones antiguas de snap

snap conserva varias versiones de cada paquete. Lista las desactivadas:

snap list --all | awk '/disabled/ {print $1, $3}'
lxd 29351
core22 1564

Elimina cada una indicando su revisión y limita las que se conservan en el futuro a 2 (el mínimo permitido):

sudo snap remove lxd --revision=29351
sudo snap set system refresh.retain=2

Docker

Si usas Docker, consulta cuánto ocupan imágenes, contenedores, volúmenes y caché de compilación:

docker system df

Elimina los contenedores parados, las redes sin uso, las imágenes sin etiqueta y la caché de compilación:

docker system prune

Añade -a para borrar también todas las imágenes que no usa ningún contenedor. No uses --volumes salvo que tengas claro que no necesitas los datos de los volúmenes sin usar.

Los logs de los contenedores (/var/lib/docker/containers/*/*-json.log) crecen sin límite por defecto. Limítalos en la configuración del demonio:

sudo nano /etc/docker/daemon.json
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

Reinicia Docker. El límite solo se aplica a los contenedores que se creen a partir de ahora, así que recrea los existentes (por ejemplo con docker compose up -d --force-recreate):

sudo systemctl restart docker

Binlogs de MySQL

MySQL 8 guarda los binary logs 30 días por defecto. Si ocupan mucho, puedes purgar los antiguos desde el cliente de MySQL:

PURGE BINARY LOGS BEFORE NOW() - INTERVAL 3 DAY;

Si usas replicación, asegúrate antes de que las réplicas han leído esos ficheros. Para reducir la retención de forma permanente, fija binlog_expire_logs_seconds = 259200 (3 días) en la sección [mysqld] de la configuración.

Volcados de memoria

Los informes de fallos se acumulan en /var/crash y, si usas systemd-coredump, en /var/lib/systemd/coredump. Revísalos y bórralos si no los necesitas:

sudo du -sh /var/crash /var/lib/systemd/coredump 2>/dev/null
sudo rm -f /var/crash/*.crash

Comprueba después el espacio recuperado:

df -h /

Paso 6: Resolver el agotamiento de inodos

Si el problema son los inodos, busca el directorio con más ficheros. du --inodes cuenta ficheros en lugar de bytes:

sudo du --inodes -x -d1 / 2>/dev/null | sort -rn | head -10
2580479 /
2311004 /var
201330  /usr

Baja nivel a nivel igual que en el paso 2. Las causas habituales son sesiones de PHP (/var/lib/php/sessions), cachés de aplicaciones con millones de ficheros pequeños y colas de correo (/var/spool/postfix). Por ejemplo, para borrar sesiones de PHP de más de un día:

sudo find /var/lib/php/sessions -type f -mtime +1 -delete

find -delete es la forma correcta de borrar millones de ficheros: rm * fallará con el error Argument list too long.

Paso 7: Configurar la rotación de logs de tus aplicaciones

La causa más frecuente de un disco lleno es un log propio sin rotación. Crea una regla de logrotate para tu aplicación:

sudo nano /etc/logrotate.d/myapp
/var/log/myapp/*.log {
    daily
    rotate 7
    compress
    delaycompress
    missingok
    notifempty
    copytruncate
}

copytruncate copia el log y vacía el original, así que funciona con aplicaciones que no reabren el fichero al rotar. Prueba la configuración en modo de depuración, que no hace cambios:

sudo logrotate -d /etc/logrotate.d/myapp

Solución de problemas

  • df muestra el 100 % pero du suma menos: hay ficheros borrados abiertos (paso 4), o ficheros escritos en un directorio antes de montar encima otro disco. Para ver estos últimos, monta la raíz en otro punto con sudo mount --bind / /mnt y ejecuta sudo du -xh -d1 /mnt.
  • Avail es 0 pero Use% no llega al 100 %: ext4 reserva un 5 % del disco para root. Los servicios que se ejecutan como root pueden seguir escribiendo, pero el resto no. Libera espacio en lugar de reducir la reserva.
  • /boot lleno y apt falla al instalar un kernel: lista los kernels instalados con dpkg -l 'linux-image-*' | grep ^ii, comprueba el que usas con uname -r y elimina uno antiguo con sudo apt purge linux-image-VERSION. Nunca borres el kernel en uso.
  • MySQL o PostgreSQL no arrancan tras llenarse el disco: libera espacio primero y revisa después su log (sudo journalctl -u mysql). No borres ficheros dentro de su directorio de datos.

Conclusión

Has aprendido a localizar qué llena el disco con df, du, ncdu y find, a recuperar el espacio retenido por ficheros borrados abiertos y a limpiar de forma segura el journal, apt, snap, Docker y los binlogs. Como siguientes pasos, limita el tamaño del journal y de los logs de Docker, añade logrotate a tus aplicaciones y configura una alerta de uso de disco al 80 % en tu sistema de monitorización. Si el disco se queda corto de forma habitual, amplía el almacenamiento de tu VPS.