Cuando un servidor Linux se queda sin memoria, puede ocurrir una de dos cosas: que empiece a usar swap de forma intensiva y todo se vuelva lento, o que el OOM killer del kernel termine un proceso, a menudo el más importante. Ninguna es buena, pero ambas se pueden controlar. En este tutorial diagnosticarás el uso de memoria de un servidor Ubuntu 24.04, crearás un archivo de swap, ajustarás cuándo y cómo el kernel lo usa, activarás zswap para comprimir páginas en RAM y decidirás con systemd qué servicios se sacrifican primero si la memoria se agota.
Requisitos previos
- Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath.
- Un usuario no root con privilegios
sudo. - Espacio libre en disco para el archivo de swap (entre 1 y 4 GB en la mayoría de casos).
Paso 1: Diagnosticar el uso de memoria
Empieza por una foto del estado actual:
free -h
total used free shared buff/cache available
Mem: 3.8Gi 1.9Gi 312Mi 48Mi 1.8Gi 1.7Gi
Swap: 0B 0B 0B
La columna importante es available, no free. Linux usa la memoria libre como caché de archivos (buff/cache) y la devuelve cuando un proceso la pide, así que un free bajo no es un problema. Este servidor tiene 1,7 GB disponibles y no tiene swap, algo habitual en imágenes cloud de Ubuntu.
Para saber si la memoria es realmente un cuello de botella, consulta la información de presión (PSI), que mide el porcentaje de tiempo que los procesos pasan esperando por memoria:
cat /proc/pressure/memory
some avg10=0.00 avg60=0.00 avg300=0.00 total=10432
full avg10=0.00 avg60=0.00 avg300=0.00 total=2211
some indica el tiempo en que al menos un proceso estuvo bloqueado esperando memoria, y full el tiempo en que todos lo estuvieron. Los valores avg10, avg60 y avg300 son porcentajes de los últimos 10, 60 y 300 segundos. Si some avg60 supera con frecuencia el 5-10 %, al servidor le falta memoria de verdad y ningún ajuste lo resolverá: toca reducir consumo o ampliar RAM.
Por último, comprueba si el OOM killer ya ha actuado alguna vez:
journalctl -k -g 'Out of memory' --no-pager
sep 20 03:12:44 web01 kernel: Out of memory: Killed process 20417 (php-fpm8.3) total-vm:812344kB, anon-rss:402112kB, ...
Si no aparece nada, verás -- No entries --.
Paso 2: Crear un archivo de swap
Un poco de swap da margen al kernel para sacar de la RAM páginas que casi nunca se usan (memoria de arranque de servicios, librerías cargadas y olvidadas) y deja más espacio para la caché y los procesos activos. Además, convierte un pico puntual de memoria en una ralentización temporal en lugar de un proceso muerto.
Como referencia de tamaño:
| RAM del servidor | Swap recomendada |
|---|---|
| Hasta 2 GB | Igual a la RAM |
| 2 a 8 GB | 2 a 4 GB |
| Más de 8 GB | 4 GB, o más si usas hibernación (no aplica a servidores) |
Comprueba que no hay swap activa:
swapon --show
Si el comando no devuelve nada, crea un archivo de 2 GB. fallocate reserva el espacio al instante en ext4 y XFS:
sudo fallocate -l 2G /swapfile
Notasi la raíz está en Btrfs,
fallocateno sirve para swap. Usa en su lugarsudo btrfs filesystem mkswapfile --size 2g /swapfile, que crea el archivo con los atributos que Btrfs necesita, y continúa conswapon.
Restringe los permisos para que solo root pueda leerlo, ya que contendrá fragmentos de memoria de los procesos:
sudo chmod 600 /swapfile
Formatea el archivo como swap y actívalo:
sudo mkswap /swapfile
sudo swapon /swapfile
Verifica que el kernel lo está usando:
swapon --show
NAME TYPE SIZE USED PRIO
/swapfile file 2G 0B -2
Para que se active en cada arranque, añádelo a /etc/fstab:
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
Comprueba la entrada sin reiniciar: desactiva la swap y vuelve a activar todo lo que esté en fstab. Si la línea tuviera un error, swapon -a lo mostraría:
sudo swapoff /swapfile
sudo swapon -a
swapon --show
Paso 3: Ajustar swappiness y la caché de archivos
vm.swappiness controla cuánto prefiere el kernel mover memoria de procesos a swap frente a descartar caché de archivos. El valor por defecto es 60. En un servidor con swap en SSD, un valor de 10 mantiene los procesos en RAM y solo usa swap cuando la memoria está realmente justa, sin desactivarla del todo.
vm.vfs_cache_pressure controla la tendencia a liberar la caché de metadatos del sistema de archivos (dentries e inodos). El valor por defecto es 100. Bajarlo a 50 ayuda en servidores que recorren muchos archivos pequeños, como un servidor web con miles de archivos estáticos o un servidor de correo.
Consulta los valores actuales:
sysctl vm.swappiness vm.vfs_cache_pressure
vm.swappiness = 60
vm.vfs_cache_pressure = 100
Guarda los nuevos valores en un archivo propio para que persistan:
sudo nano /etc/sysctl.d/99-memoria.conf
vm.swappiness = 10
vm.vfs_cache_pressure = 50
Aplica el archivo y verifica:
sudo sysctl -p /etc/sysctl.d/99-memoria.conf
vm.swappiness = 10
vm.vfs_cache_pressure = 50
Nota
vm.swappiness = 0no desactiva la swap, pero hace que el kernel solo la use en el último momento, cuando ya está cerca del OOM. Casi nunca es lo que quieres; si no quieres swap, no la crees.
Escrituras pendientes: vm.dirty_ratio
Los parámetros vm.dirty_background_ratio (10 % por defecto) y vm.dirty_ratio (20 %) marcan cuánta memoria pueden ocupar los datos escritos pero aún no volcados a disco antes de que el kernel empiece a volcarlos en segundo plano, y antes de que bloquee a los procesos que escriben. En servidores con mucha RAM, un 20 % pueden ser decenas de gigabytes pendientes, y el volcado provoca pausas largas. En ese caso, fija límites en bytes, que sustituyen a los porcentajes. Añádelos al mismo archivo /etc/sysctl.d/99-memoria.conf:
vm.dirty_background_bytes = 268435456
vm.dirty_bytes = 1073741824
Con esto el volcado empieza con 256 MB pendientes y los procesos se bloquean a partir de 1 GB. En servidores pequeños (menos de 8 GB) los valores por defecto suelen estar bien y puedes omitir este bloque.
Paso 4: Activar zswap
zswap es una caché comprimida en RAM que se sitúa delante de la swap. Cuando el kernel decide llevar una página a swap, primero la comprime y la guarda en memoria; solo si esa caché se llena, escribe en disco. Con ratios de compresión típicos de 2:1 o 3:1, reduce mucho las E/S de swap a cambio de algo de CPU. Necesita una swap real detrás, que ya creaste en el paso 2.
Comprueba si está activo, qué compresor usa y qué porcentaje máximo de la RAM puede ocupar:
grep . /sys/module/zswap/parameters/{enabled,compressor,max_pool_percent}
/sys/module/zswap/parameters/enabled:N
/sys/module/zswap/parameters/compressor:lzo-rle
/sys/module/zswap/parameters/max_pool_percent:20
Para activarlo de forma persistente, añade los parámetros a la línea de arranque del kernel. Edita la configuración de GRUB:
sudo nano /etc/default/grub
Añade la opción de zswap al final del valor de GRUB_CMDLINE_LINUX_DEFAULT, conservando lo que ya tenga:
GRUB_CMDLINE_LINUX_DEFAULT="zswap.enabled=1"
Se mantiene el compresor por defecto del kernel, que está integrado en él y por tanto disponible desde el arranque. Regenera la configuración de GRUB:
sudo update-grub
Notaen algunas imágenes cloud de Ubuntu, la línea de arranque se sobrescribe desde
/etc/default/grub.d/50-cloudimg-settings.cfg. Si tras reiniciar zswap sigue desactivado, añade la opción en ese archivo en lugar de en/etc/default/grub.
Para activarlo ahora sin reiniciar, escribe en su parámetro enabled:
echo 1 | sudo tee /sys/module/zswap/parameters/enabled
Verifica el estado. Tras un tiempo con presión de memoria, las líneas Zswap (memoria usada por la caché comprimida) y Zswapped (datos originales que representa) de /proc/meminfo mostrarán valores distintos de cero:
cat /sys/module/zswap/parameters/enabled
grep -E '^(Zswap|Zswapped)' /proc/meminfo
Y
Zswap: 41220 kB
Zswapped: 118904 kB
Paso 5: Controlar el OOM killer con systemd
Cuando la memoria se agota del todo, el OOM killer elige un proceso según su puntuación oom_score, que depende sobre todo de cuánta memoria usa. Eso significa que suele morir la base de datos, justo el proceso que menos quieres perder. Puedes influir en la elección con OOMScoreAdjust= en la unidad de systemd: los valores van de -1000 (nunca matar) a 1000 (matar primero).
Protege la base de datos con un override. El ejemplo usa MySQL; sustituye el nombre por el de tu servicio:
sudo systemctl edit mysql
Añade entre las líneas de comentario del editor:
[Service]
OOMScoreAdjust=-500
Reinicia el servicio y comprueba el valor aplicado al proceso:
sudo systemctl restart mysql
cat /proc/"$(pidof mysqld)"/oom_score_adj
-500
Evita usar -1000 salvo en casos muy justificados: si el proceso protegido es el que tiene la fuga de memoria, el kernel matará todo lo demás, incluido SSH, antes de tocarlo.
La otra cara es limitar a los servicios que pueden desbocarse, como un pool de PHP-FPM. Con MemoryHigh= el kernel empieza a reclamar memoria agresivamente de ese servicio al superar el umbral, y con MemoryMax= el OOM actúa solo dentro del servicio, sin afectar al resto del servidor:
sudo systemctl edit php8.3-fpm
[Service]
MemoryHigh=1536M
MemoryMax=2G
sudo systemctl restart php8.3-fpm
systemctl show php8.3-fpm -p MemoryHigh -p MemoryMax -p MemoryCurrent
MemoryHigh=1610612736
MemoryMax=2147483648
MemoryCurrent=312475648
Con esto, si PHP-FPM consume de más, será él quien sufra el OOM y systemd lo reiniciará según su política Restart=, mientras la base de datos sigue en pie.
Paso 6: Vigilar la swap bajo carga
Que haya swap usada no es un problema en sí; lo es que el sistema esté leyendo y escribiendo swap continuamente. vmstat lo muestra en las columnas si (KB/s leídos de swap) y so (KB/s escritos a swap):
vmstat 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
1 0 84120 298412 61220 1712400 0 0 3 41 512 880 6 2 91 1 0
2 0 84120 297980 61220 1712452 0 0 0 12 498 861 5 1 94 0 0
Si si y so se mantienen en cero o casi, la swap está haciendo su trabajo: guarda páginas frías. Si ambas columnas muestran actividad constante junto con valores altos en wa (CPU esperando E/S) y en la presión some de PSI, el servidor está en thrashing: necesita más RAM o menos carga.
Para saber qué procesos tienen memoria en swap:
sudo grep -H VmSwap /proc/[0-9]*/status | sort -k2 -n -r | head -n 5
/proc/20417/status:VmSwap: 41280 kB
/proc/1123/status:VmSwap: 12044 kB
/proc/988/status:VmSwap: 3920 kB
Identifica el proceso con ps -p 20417 -o comm=.
Solución de problemas
swapon: /swapfile: insecure permissions 0644, 0600 suggested: el archivo tiene permisos demasiado abiertos. Ejecuta sudo chmod 600 /swapfile.
swapon: /swapfile: swapon failed: Invalid argument: el sistema de archivos no admite archivos de swap creados con fallocate (Btrfs, algunos sistemas en red). Borra el archivo y créalo con el método de tu sistema de archivos, o con sudo dd if=/dev/zero of=/swapfile bs=1M count=2048 en ext4 si fallocate no está disponible.
El OOM killer sigue matando la base de datos: comprueba con systemctl cat mysql que el override está presente, y recuerda que OOMScoreAdjust solo ajusta la puntuación: si la base de datos es con diferencia el proceso más grande, puede seguir siendo elegida. Reduce su consumo (por ejemplo innodb_buffer_pool_size) o limita a los demás servicios con MemoryMax.
Conclusión
Has diagnosticado la memoria del servidor con free y PSI, añadido un archivo de swap persistente, ajustado swappiness, la caché de archivos y las escrituras pendientes, activado zswap y establecido con systemd qué servicios se protegen y cuáles se limitan. El resultado es un servidor que absorbe picos de memoria con una ralentización moderada en lugar de perder procesos críticos.
Como siguientes pasos, puedes configurar alertas sobre la presión de memoria PSI en tu sistema de monitorización, revisar la configuración de memoria de tu base de datos o ajustar el programador de E/S del disco donde reside la swap.
