Disco lleno: encontrar y liberar espacio en un servidor Linux
Cuando df informa del 100% y du no encuentra nada: el camino completo, desde la medición hasta la liberación de espacio, para Debian 12 y 13 y para Ubuntu 22.04 y 24.04.
Un disco lleno rara vez avisa con educación. Lo normal es que primero falle un servicio: MariaDB escribe OS error 28 en el log, nginx responde con un 500, un backup se interrumpe con write error: No space left on device y, si la cosa se pone realmente fea, ya nadie entra por SSH en la máquina porque sshd no puede crear los archivos de sesión. Este artículo recorre el camino completo: medir, localizar, liberar y comprobar. Incluidos los dos casos en los que la mayoría de las guías se detiene, es decir, cuando df dice que está lleno y du no encuentra nada.
Primero medir: ¿qué partición está llena realmente?
Antes de borrar nada hay que tener claro qué sistema de archivos está afectado. Un /boot lleno tiene causas completamente distintas a las de un /var lleno.
df -h
df -hT -x tmpfs -x devtmpfs -x squashfs
La segunda línea oculta los pseudosistemas de archivos. En Ubuntu resulta especialmente útil, porque allí cada aplicación Snap instalada aparece como su propio montaje loop squashfs y ensucia la salida. Por cierto, esos montajes loop siempre están ocupados al 100%: es normal y no supone ningún problema.
Lo importante es la columna Mounted on. Constelaciones típicas en servidores:
| Punto de montaje | Causa habitual cuando se llena |
|---|---|
| / | logs, Docker, caché de apt, datos de aplicaciones |
| /boot | kernels antiguos y sus imágenes initramfs |
| /var | journal, rsyslog, cola de correo, bases de datos, Docker |
| /tmp | subidas interrumpidas, sesiones, restos de compilaciones |
Otro detalle que suele generar confusión: ext4 reserva por defecto el cinco por ciento de la capacidad para el usuario root. Un servicio que se ejecuta como www-data o mysql ya recibe un No space left on device mientras que root, en ese mismo segundo, todavía puede escribir sin problemas. En particiones puras de datos (es decir, no en el sistema de archivos raíz) esa reserva se puede bajar sin riesgo:
tune2fs -m 1 /dev/sdb1
En / conviene mantener la reserva. Es justo el colchón que permite reparar un sistema que se ha llenado del todo.
Usar du correctamente, en vez de perderse por el camino
El punto de partida clásico consiste en ir nivel por nivel, siempre con -x:
du -xh --max-depth=1 / | sort -h
du -xh --max-depth=1 /var | sort -h
El -x es el parámetro más importante de todo el artículo. Mantiene a du dentro de un único sistema de archivos y evita que el comando se meta en /proc, /sys, recursos de red montados o discos de backup. Sin -x la búsqueda tarda minutos y devuelve cifras que no tienen nada que ver con la partición llena. sort -h ordena correctamente los tamaños legibles por humanos, de modo que el bloque más grande queda abajo del todo.
Quien prefiera navegar de forma interactiva puede instalar ncdu y arrancarlo también con -x:
apt-get install -y ncdu
ncdu -x /
Los archivos grandes sueltos se localizan más rápido de forma directa:
find /var -xdev -type f -size +100M -exec ls -lh {} +
Aquí hay dos trampas que llevan una y otra vez a conclusiones equivocadas. La primera: du cuenta bloques ocupados, no el tamaño lógico del archivo. En archivos dispersos (tablespaces de bases de datos, imágenes de discos virtuales) ambos valores se separan muchísimo. La comparación lo deja a la vista:
du -sh /var/log
du --apparent-size -sh /var/log
La segunda: du cuenta los enlaces duros una sola vez. Quien trabaje sin root recibe además líneas como du: cannot read directory '/var/lib/private': Permission denied y, con ello, sumas sistemáticamente demasiado pequeñas. Conviene hacer todos los análisis como root o con sudo.
Limitar el journal de systemd
El journal es el devorador silencioso de espacio más habitual en servidores. Por defecto puede ocupar el diez por ciento del sistema de archivos, con un tope de cuatro gigabytes, y además mantiene libre el 15% del sistema de archivos. En un disco de 500 gigabytes eso son hasta cuatro gigabytes de puro log.
journalctl --disk-usage
Aquí hay una diferencia real entre distribuciones que muchas guías se callan. Que el journal acabe o no en el disco depende, con el valor por defecto Storage=auto, únicamente de si existe el directorio /var/log/journal:
ls -d /var/log/journal
ls -d /run/log/journal
En instalaciones mínimas de Debian y en muchas imágenes cloud de Debian 12 y Debian 13 no existe /var/log/journal. El journal queda entonces en /run/log/journal, es decir, en RAM, desaparece con cada reinicio y no carga el disco en absoluto. A cambio, carga la memoria RAM. Ubuntu Server 22.04 y 24.04, por su parte, suelen crear el directorio y registran de forma permanente en disco. Comprobar en lugar de suponer.
Liberar espacio de inmediato:
journalctl --rotate
journalctl --vacuum-size=200M
journalctl --vacuum-time=7d
La llamada previa a --rotate no es ningún adorno: las opciones vacuum borran exclusivamente archivos de journal ya archivados, nunca el que está activo en ese momento. Si el archivo activo es el que ocupa la mayor parte, sin rotar antes parece que no ocurre nada, y ahí es donde tropiezan justamente los lectores que copian el comando de un mensaje de foro.
Para limitarlo de forma permanente conviene usar un archivo propio, así las futuras actualizaciones de paquetes no sobrescriben nada:
mkdir -p /etc/systemd/journald.conf.d
printf '[Journal]\nSystemMaxUse=200M\nRuntimeMaxUse=50M\n' > /etc/systemd/journald.conf.d/00-size.conf
systemctl restart systemd-journald
Comprobación de que realmente ha surtido efecto: journalctl --disk-usage debe indicar ahora un valor menor y df -h debe mostrar más espacio libre. Si el journal encoge mientras df se mantiene igual, algún proceso sigue manteniendo abiertos archivos ya borrados. Más sobre esto un poco más abajo.
Segunda diferencia entre distribuciones: en las instalaciones de servidor clásicas de Debian y Ubuntu suele ejecutarse además rsyslog, que escribe los mismos mensajes una segunda vez en /var/log/syslog. En las imágenes mínimas de Ubuntu (cloud, container) falta rsyslog. Se comprueba con ls -l /var/log/syslog. Si el archivo existe y es enorme, el problema no es el journal, sino una regla de logrotate ausente o rota bajo /etc/logrotate.d/.
La caché de apt y los kernels antiguos
Los paquetes descargados se quedan ahí después de la instalación. En un servidor que lleva mucho tiempo funcionando eso son enseguida varios gigabytes.
du -sh /var/cache/apt
apt-get clean
du -sh /var/lib/apt/lists
apt-get clean vacía por completo /var/cache/apt/archives, mientras que apt-get autoclean solo elimina los paquetes que ya no existen en los repositorios. Lo que clean no toca son las listas de paquetes bajo /var/lib/apt/lists. Con muchos repositorios añadidos pueden alcanzar varios cientos de megabytes y se pueden reconstruir sin riesgo:
rm -rf /var/lib/apt/lists/*
apt-get update
La segunda línea no es opcional, es obligatoria, y además de inmediato. Entre el borrado de las listas y el siguiente apt-get update, apt no conoce ni un solo paquete: cualquier apt-get install en ese estado aborta con E: Unable to locate package ... y código de salida 100, aunque el paquete esté por supuesto disponible en los repositorios.
El segundo clásico son los kernels antiguos. Ubuntu instala kernels nuevos continuamente a través de unattended-upgrades, pero solo limpia los viejos si se le permite expresamente. Cada kernel ocupa, junto con su initramfs, entre 100 y 150 megabytes en un /boot que a menudo solo tiene entre 512 megabytes y un gigabyte.
uname -r
dpkg -l 'linux-image-*'
apt-get autoremove --purge
El kernel en ejecución que devuelve uname -r nunca se elimina, ni tampoco el más reciente. En Ubuntu se previene el problema poniendo en /etc/apt/apt.conf.d/50unattended-upgrades la línea Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";. En Debian 12 y 13, unattended-upgrades no está activo por defecto: allí /boot solo crece si alguien actualiza a mano con regularidad y nunca limpia.
Cuando /boot ya está lleno y apt no llega a terminar
Este es el caso que de verdad duele. Mensajes típicos, literalmente:
update-initramfs: failed for /boot/initrd.img-6.8.0-60-generic with 1.
dpkg: error processing package linux-image-6.8.0-60-generic (--configure):
installed linux-image-6.8.0-60-generic package post-installation script subprocess returned error exit status 1
E: Sub-process /usr/bin/dpkg returned an error code (1)
En ese momento la base de datos de paquetes está a medias y cualquier llamada posterior a apt falla en el mismo punto. La salida, en este orden:
- Anotar
uname -r. Esa versión no se toca bajo ningún concepto. - Sacar la salida de
ls -lh /booty localizar la versión más antigua que no esté en ejecución. - Borrar únicamente su
initrd.img-*, no elvmlinuz-*. El archivo initramfs es con diferencia el más grande y se puede regenerar en cualquier momento. - Ejecutar
apt-get -f installpara que dpkg pueda terminar la configuración interrumpida. - Solo después
apt-get autoremove --purge, para que los paquetes antiguos desaparezcan limpiamente junto con su entrada en GRUB. - Lanzar
update-gruby leer la salida.
Lo que no conviene hacer: borrar con rm archivos de kernel de /boot al azar e ignorar el resto. dpkg sigue creyendo entonces que los paquetes están instalados, GRUB ofrece entradas que apuntan al vacío y el siguiente reinicio acaba en el prompt de rescate de GRUB. Quien ya haya borrado recupera la coherencia con apt-get install --reinstall del paquete afectado o con dpkg --purge, y después obligatoriamente update-grub.
Comprobación: df -h /boot vuelve a mostrar espacio libre, dpkg -l 'linux-image-*' lista ya solo dos o tres entradas con estado ii, y la salida de update-grub nombra exactamente los kernels que están realmente en /boot.
Docker, Snap y logs de contenedores
En hosts Docker la respuesta está casi siempre en /var/lib/docker. No hay que adivinar, hay que preguntar:
docker system df
docker system df -v
La variante detallada separa limpiamente imágenes, contenedores, volúmenes y caché de compilación. Después se limpia de forma dirigida:
docker image prune -a
docker builder prune
docker system prune -a
Una advertencia sobre --volumes: ese parámetro borra también volúmenes a los que no está enganchado ningún contenedor en ejecución. Quien tenga su base de datos en un volumen con nombre y acabe de parar el contenedor pierde así los datos. Sin un backup reciente, docker system prune -a --volumes no pinta nada en un servidor productivo.
La partida infravalorada son los logs de contenedores bajo /var/lib/docker/containers/*/*-json.log. El driver por defecto json-file no rota absolutamente nada mientras no se le indique de forma explícita. Un contenedor hablador escribe así, a lo largo de meses, decenas de gigabytes en un único archivo. El remedio va en /etc/docker/daemon.json:
{
"log-driver": "json-file",
"log-opts": { "max-size": "50m", "max-file": "3" }
}
Después, systemctl restart docker. Atención, y esto no aparece casi en ninguna guía: el ajuste solo vale para los contenedores creados de nuevo. Los existentes conservan su configuración antigua hasta que se vuelvan a crear una vez, con Compose por ejemplo mediante docker compose up -d --force-recreate.
Las versiones de Docker se diferencian bastante en este punto. Debian 12 trae docker.io 20.10, Debian 13 incluye 26.1, y Ubuntu 22.04 y 24.04 van ya por la 29.1. Cuanto más nueva es la versión, más espacio ocupa la caché de BuildKit, y más importante se vuelve docker builder prune, que docker system prune por sí solo no siempre vacía del todo.
En Ubuntu se suma Snap. Las revisiones antiguas se quedan desactivadas y siguen ocupando espacio:
snap list --all
snap set system refresh.retain=2
El valor 2 es el mínimo que acepta snapd, los valores menores se rechazan con un mensaje de error. Debian no lleva Snap, así que allí esta sección sobra por completo.
df dice que está lleno, du no encuentra nada
Ahora viene el caso interesante. df -h muestra el 100%, pero la suma de du da solo la mitad. Para eso existen exactamente cuatro causas plausibles.
Archivos borrados que siguen abiertos
Es con diferencia el motivo más frecuente. Alguien ha ejecutado rm /var/log/riesig.log mientras un servicio mantenía el archivo abierto. La entrada de directorio ha desaparecido, por eso du ya no ve nada. Los bloques siguen ocupados hasta que se cierra el último descriptor de archivo, por eso df los sigue viendo.
apt-get install -y lsof
lsof +L1
En la columna NLINK aparece entonces un 0 y detrás de la ruta pone (deleted). Si lsof no encuentra nada, no hay salida y el valor de retorno es 1, lo cual no es ningún error. Sin lsof también se puede ir directamente al sistema de archivos de procesos:
ls -l /proc/*/fd 2>/dev/null | grep deleted
Importante: hay que ejecutarlo obligatoriamente como root. Como usuario normal solo se ven los descriptores propios y así se pasan por alto justamente los servicios del sistema, que en nueve de cada diez casos son los causantes.
La forma limpia de liberar el espacio es reiniciar el servicio, por ejemplo con systemctl restart rsyslog. Si un reinicio no es viable, el archivo se puede truncar a longitud cero a través de su descriptor. El ID de proceso y el número de descriptor salen de la salida de lsof:
truncate -s 0 /proc/1234/fd/7
Eso libera los bloques al instante y el proceso sigue escribiendo después. Este método está pensado para archivos de log puros, abiertos en modo de anexado. Nunca se debe aplicar a archivos de bases de datos, a imágenes de máquinas virtuales ni a nada con acceso aleatorio, porque ahí provoca pérdida de datos.
Cómo se reconoce que ha funcionado: df -h muestra de inmediato más espacio libre y lsof +L1 ya no lista la entrada. Una segunda pasada de du, en cambio, no cambia en nada, porque allí el archivo ya era invisible antes. Justo por eso reiniciar el servidor parece resolver el problema por arte de magia.
Archivos ocultos bajo un punto de montaje
Un clásico: alguien ha escrito datos en /mnt/backup antes de que el disco real estuviera montado ahí. Los datos siguen en el sistema de archivos raíz, pero quedan tapados por el montaje que está encima. Solo se hacen visibles a través de una segunda vista del mismo sistema de archivos:
mkdir -p /mnt/rootview
mount --bind / /mnt/rootview
du -xh --max-depth=2 /mnt/rootview | sort -h
umount /mnt/rootview
El bind mount es inofensivo, no cambia nada de sitio y se retira de nuevo con umount.
La reserva de root y el fallo de permisos
El cinco por ciento de reserva de ext4 ya mencionado explica el hueco entre "todavía no está del todo lleno" y "los servicios ya están fallando". Y por último: quien lance du sin root no ve árboles de directorios enteros. La supuesta diferencia es entonces sencillamente un problema de permisos. En btrfs y ZFS se suman los snapshots como posible explicación, que du tampoco muestra nunca.
Cuando lo que se agota no es el espacio, sino los inodos
Existe un segundo tipo de "lleno" que genera exactamente el mismo mensaje de error. Cada archivo y cada directorio ocupa un inodo, y en ext4 su cantidad queda fijada en el momento del formateo.
df -i
stat -f /
Si en df -i la columna IUse% marca 100 mientras df -h informa de espacio libre de sobra, el diagnóstico es claro: el servidor no tiene un problema de espacio, sino de cantidad. Millones de archivos diminutos han consumido todos los inodos. Aun así el mensaje de error sigue siendo No space left on device, y justamente por eso la mayoría busca en el sitio equivocado.
A los causantes se les encuentra contando archivos en lugar de bytes:
find /var -xdev -printf '%h\n' | sort | uniq -c | sort -rn | head -20
Candidatos habituales son la cola de correo bajo /var/spool/postfix o /var/spool/exim4, las sesiones de PHP bajo /var/lib/php/sessions, los directorios de caché de aplicaciones web, las dependencias de Node desplegadas en disco y un /tmp que nunca se vacía.
Al borrar espera el siguiente obstáculo. rm /var/lib/php/sessions/* falla cuando hay muchísimos archivos y devuelve bash: /usr/bin/rm: Argument list too long, porque la línea de comandos supera el límite de tamaño. El camino que siempre funciona:
find /var/lib/php/sessions -type f -mtime +7 -delete
Lo que hay que saber: la cantidad de inodos de un sistema de archivos ext4 existente no se puede aumentar a posteriori. Solo queda agrandar el sistema de archivos (con lo que los inodos crecen de forma proporcional) o volver a crearlo con una densidad mayor, por ejemplo con mkfs.ext4 -i 8192 /dev/sdb1. Quien vaya a trabajar previsiblemente con muchísimos archivos pequeños, por ejemplo en servidores de correo o en archivos de imágenes, está mejor servido con XFS, porque XFS crea los inodos de forma dinámica y prácticamente solo se queda sin ellos cuando también se acaba el espacio. Debian y Ubuntu formatean por defecto con ext4, XFS hay que elegirlo de forma consciente.
Comprobar y conseguir que no vuelva a pasar
Una limpieza solo se puede dar por buena cuando encajan tres cosas: df -h muestra más espacio libre, df -i muestra una ocupación de inodos a la baja, y el servicio que falló al principio vuelve a funcionar. Un comando que ha terminado sin mensaje de error no es, por sí solo, ninguna prueba. Sobre todo journalctl --vacuum-size y docker system prune tienden a no hacer absolutamente nada sin decir ni una palabra.
Para el funcionamiento diario da buen resultado una lista corta: limitar el journal de forma estricta con SystemMaxUse, configurar la rotación de logs de Docker en daemon.json, activar en Ubuntu Remove-Unused-Kernel-Packages, recoger los logs de las aplicaciones propias mediante /etc/logrotate.d/ y montar un cron job sencillo que envíe un correo al superar un umbral. En servidores KVM y dedicados merece la pena, además, poner /var o al menos /var/log en una partición propia. Así, un log que se descontrola deja fuera de juego un servicio, pero no el sistema operativo, y en cualquier caso todavía se puede entrar por SSH en la máquina.
Preguntas frecuentes
¿Por qué df muestra el 100% cuando du encuentra bastante menos?
¿Cuánto espacio puede ocupar el journal de systemd y cómo lo limito de forma permanente?
¿El journal se guarda igual en Debian y en Ubuntu?
/boot está lleno y apt aborta con un error de dpkg. ¿Qué hago ahora?
¿Es seguro "docker system prune -a --volumes"?
¿Qué hago si df -i muestra los inodos ocupados al 100%?
2026 KernelHost GmbH. Todos los derechos reservados. Esta guía está protegida por derechos de autor. Su publicación en otros sitios web, aunque sea de forma parcial o modificada, no está permitida sin nuestro consentimiento por escrito. Las citas con indicación de la fuente y un enlace son muy bienvenidas.

