Saltar al contenido
Linux 2026-09-05 15 min de lectura

Qué hacer cuando tu VPS se queda sin RAM

Un VPS sin RAM puede volverse lento, cerrar la base de datos o dejar de responder por SSH. Reiniciar recupera servicio, pero borra evidencia y no corrige la causa. Si puedes, captura métricas primero.

Qué hacer cuando tu VPS se queda sin RAM
#VPS#RAM#Linux#Troubleshooting
T
Equipo Terranode
Editorial

Confirmar el problema

bash
free -h
vmstat 1
ps aux --sort=-%mem | head -20
journalctl -k | grep -i -E 'out of memory|killed process'

La columna available es más útil que free. En vmstat, actividad sostenida en si/so indica intercambio; los logs del kernel confirman OOM.

Recuperación inmediata

Detén o reinicia solo el servicio causante, reduce temporalmente workers y pausa tareas pesadas. Si SSH no entra, usa la consola del proveedor. Añadir swap puede dar margen, pero no lo hagas llenando el último espacio del disco.

Encontrar la causa

Compara consumo por proceso y grupo:

bash
systemd-cgtop
docker stats --no-stream

Revisa crecimiento con el tiempo. Una fuga aumenta incluso sin tráfico; demasiados workers crecen con concurrencia; una importación o backup produce un pico puntual.

PHP-FPM, PostgreSQL, MySQL, Java y Node.js tienen límites propios. Docker no limita memoria automáticamente si no la declaras. Establece topes que dejen capacidad para el sistema, pero evita límites tan bajos que provoquen reinicios continuos.

Evitar la repetición

Configura alertas sobre memoria available, swap, reinicios y OOM. Ajusta concurrencia, corrige consultas, controla colas y programa tareas intensas. Si la carga legítima supera el plan, escala basándote en cuánta RAM necesita un VPS.

Primero conserva evidencia y luego recupera servicio

Si todavía puedes entrar, guarda hora, memoria, procesos y mensajes del kernel antes de reiniciar. Un reinicio libera RAM y borra el estado que distinguía una fuga de un pico. No hace falta investigar durante una hora con usuarios afectados: una captura de comandos y logs permite volver al análisis después.

Comprueba también disco y carga. Un servidor con almacenamiento lleno o entrada/salida saturada puede parecer “sin memoria”. Si available es razonable y no hay swap ni OOM, busca otro cuello de botella. Linux usa RAM libre como caché; que free esté cerca de cero no es por sí solo una emergencia.

Si el servicio está caído, recupera la función mínima. Detén el trabajo secundario, no toda la máquina. Pausar una importación o reducir workers conserva mejor la evidencia y evita una interrupción mayor que reiniciar indiscriminadamente.

Leer correctamente free y vmstat

En free -h, available estima lo que puede entregarse a nuevas aplicaciones sin intercambio perjudicial. buff/cache no es memoria perdida: Linux la reutiliza. Observa swap usada, pero interpreta tendencia; unas páginas antiguas en swap pueden permanecer aunque ya exista RAM.

Con vmstat 1, las columnas si y so muestran entrada y salida de swap. Actividad sostenida mientras el servicio responde lento indica presión. La columna r ayuda a ver procesos esperando CPU, y wa espera de entrada/salida. Así evitas atribuir toda lentitud a RAM.

Captura al menos un minuto durante el problema. Un solo segundo puede coincidir con una rotación o consulta normal. Anota qué tarea se ejecutaba para correlacionar después.

Confirmar que intervino el OOM killer

Cuando el kernel no puede satisfacer una asignación, puede terminar un proceso para recuperar memoria. Busca mensajes Out of memory y Killed process en el journal. Registra qué proceso fue elegido y cuánta memoria usaba. El servicio que murió no siempre fue quien causó el crecimiento; pudo ser la víctima más conveniente.

En contenedores, revisa estado y código de salida:

bash
docker ps -a
docker inspect --format '{{.Name}} exit={{.State.ExitCode}} oom={{.State.OOMKilled}}' $(docker ps -aq)

Un OOMKilled=true confirma el límite del contenedor o del host. Si no aparece y la aplicación se reinició, examina sus logs y políticas de salud. No todos los cierres con código 137 provienen necesariamente del mismo origen.

Localizar el proceso que crece

Ordenar por memoria muestra a los consumidores actuales, no al que ya murió. Revisa journal, métricas históricas y reinicios. ps, top, systemd-cgtop y docker stats observan niveles distintos: proceso, sesión interactiva, unidad de systemd y contenedor.

Compara RSS a lo largo del tiempo. Una fuga crece sin volver a una base después de terminar el trabajo. Un caché puede crecer y luego estabilizarse porque libera memoria bajo presión. Un pico aparece ligado a una operación concreta. Estas formas requieren soluciones diferentes.

Si varios procesos son iguales, identifica el pool. Veinte workers PHP de 100 MB representan más que cualquier proceso individual. En Java o Node, revisa heap y memoria nativa; el límite del runtime no incluye siempre todo lo que utiliza el proceso.

PHP-FPM: demasiados workers o solicitudes pesadas

PHP-FPM crea procesos para atender solicitudes. pm.max_children define cuántos pueden coincidir. Si cada uno consume 120 MB, veinte pueden reclamar 2,4 GB. Reducir temporalmente el máximo recupera estabilidad, aunque aumente la cola.

Activa el slow log para descubrir rutas que retienen workers y mide memoria bajo carga. Plugins, exportaciones y generación de imágenes producen picos. No calcules con el proceso más pequeño recién iniciado.

Si CPU está libre y la cola crece mientras queda RAM, quizá necesites más workers. Si memoria cae y aparece swap, añadirlos empeora. Ajusta de pocos en pocos y prueba el flujo que causó el incidente.

MySQL y PostgreSQL: memoria global y por conexión

Las bases reservan caché global y pueden asignar memoria por conexión u operación. Un valor aparentemente pequeño multiplicado por muchas conexiones concurrentes supera el presupuesto. Revisa pools, máximo de sesiones y consultas que ordenan o agrupan grandes volúmenes.

No reduzcas buffers a ciegas: podrías trasladar el problema al disco y hacer más lentas las consultas. Captura actividad, consultas lentas y conexiones. Corrige índices y limita el pool antes de aceptar una base permanentemente saturada.

Si aplicación y base comparten VPS, reserva memoria para ambas. Copiar una configuración pensada para un servidor dedicado es una causa común de OOM después de “optimizar” MySQL.

Node.js, Java y límites del runtime

Node administra un heap, pero buffers, módulos nativos y procesos hijos pueden consumir fuera de él. Un error de heap y un OOM del kernel no son iguales: el primero aparece en logs de Node; el segundo en el kernel. Aumentar max-old-space-size sin ampliar el presupuesto puede hacer que el proceso quite memoria al resto.

Java combina heap, metaspace, hilos y memoria nativa. Define límites compatibles con el contenedor y deja margen. Si el heap máximo coincide con toda la memoria asignada, el proceso puede morir antes de alcanzarlo.

Analiza qué datos se mantienen. Cargar un archivo completo o acumular resultados puede reemplazarse por streaming, paginación o lotes. Esa corrección escala mejor que reiniciar periódicamente.

Contenedores sin límites y límites demasiado bajos

Docker no limita memoria por defecto. Un contenedor puede competir con base, proxy y sistema. Declara topes después de medir su pico legítimo y reserva capacidad del host. Un límite evita que un servicio derribe a todos; no corrige una fuga dentro de él.

El extremo contrario es asignar menos de lo necesario. Reinicios repetidos consumen más, pierden trabajo y pueden formar una tormenta. Revisa OOMKilled, consumo máximo y concurrencia antes de cambiar el valor.

En Compose, documenta por qué existe cada límite y prueba con la misma modalidad de despliegue. Algunas claves tienen efecto diferente según la plataforma. Confirma el resultado desde el contenedor y el host.

Swap: margen para respirar, no capacidad de producción

La swap puede evitar que un pico corto active OOM y permite que páginas poco usadas salgan de RAM. Es útil como red de seguridad, especialmente en VPS pequeños. Pero si la aplicación lee y escribe swap continuamente, la latencia se dispara porque el disco es mucho más lento que la memoria.

Comprueba espacio antes de crearla y sigue la guía de swap en Ubuntu. No coloques un archivo enorme en un disco casi lleno. Ajustar swappiness cambia preferencias; no crea memoria.

Después de añadir swap, verifica que desaparecen cierres y que el intercambio no es sostenido. Si solo convirtió una caída rápida en varios minutos de lentitud, necesitas corregir carga o ampliar RAM.

Backups, importaciones y tareas que coinciden

Si el incidente ocurre a la misma hora, revisa cron y temporizadores. Un volcado, compresión, antivirus, importación y rotación pueden comenzar juntos. Distribuir horarios reduce el máximo sin comprar recursos.

Procesa archivos por fragmentos y limita concurrencia. Mantén backups: eliminarlos para estabilizar el servidor cambia una alerta visible por riesgo de pérdida de datos. Puedes enviarlos en streaming o ejecutar compresión con prioridad menor.

Observa también actualizaciones y despliegues. Durante un reemplazo pueden coexistir contenedores viejos y nuevos; reserva memoria para esa ventana o usa una estrategia que no duplique todo.

Diferenciar fuga, pico y crecimiento real

Una fuga aumenta con el tiempo y no regresa después de bajar el tráfico. Un pico coincide con una tarea y luego libera. El crecimiento real repite patrones normales con una base cada vez mayor porque hay más clientes o trabajo. Reiniciar oculta los tres, pero solo al primero puede darle horas de alivio predecible.

Para una fuga, captura perfil, actualiza o corrige código y usa un reinicio controlado solo como mitigación temporal. Para un pico, limita o reprograma. Para crecimiento legítimo, optimiza y escala con margen.

Documenta la clasificación y evidencia. Sin ella, el mismo incidente se resolverá otra vez con un reinicio y volverá.

Recuperación cuando SSH ya no responde

Usa la consola del proveedor para entrar fuera de la red. Evita apagar a la fuerza salvo que no exista alternativa; una base escribiendo puede requerir recuperación. Si obtienes shell, detén el proceso secundario o reduce trabajadores.

Comprueba después sistemas de archivos y logs del arranque anterior. En systemd, journalctl -b -1 consulta el boot previo. Verifica base, cola y trabajos antes de declarar recuperación. Un proceso activo puede haber perdido una tarea.

Si debes ampliar el plan, conserva capturas y repite el escenario. El cambio de recursos no reemplaza el análisis postincidente.

Construir alertas que avisen antes de OOM

Alerta sobre memoria disponible baja durante varios minutos, actividad de swap, OOM y reinicios. Evita disparar por “RAM usada” porque incluye caché. Añade métricas de aplicación: cola, duración, workers ocupados y conexiones.

Cada alerta debe incluir servidor, servicio y acción inicial. Prueba la notificación y la guardia. Un panel que nadie mira no reduce tiempo de caída.

Establece umbrales a partir de una semana normal y revisa después de cambios. El objetivo es recibir una señal cuando todavía puedes capturar evidencia y limitar trabajo.

Validar la corrección

Reproduce de forma controlada la tarea que provocó presión. Observa available, swap, procesos, latencia y logs. La corrección es válida cuando completa el trabajo, no registra OOM, conserva margen y vuelve a su base.

Déjala atravesar al menos el siguiente ciclo programado. Si redujiste workers, verifica que la cola no crece sin límite. Si ampliaste RAM, confirma que el proceso utiliza el margen para trabajo real y no continúa creciendo.

Finalmente registra causa, cambio, métricas y reversión. Ese documento transforma un fallo repetitivo en conocimiento operativo.

Distinguir memoria de procesos, caché y kernel

Si la suma de RSS de los procesos parece menor que la RAM ocupada, la diferencia no es necesariamente una fuga invisible. Linux usa memoria para caché de archivos, tablas del kernel, buffers de red y estructuras llamadas slabs. La caché se puede reclamar cuando una aplicación la necesita; las slabs no siempre disminuyen con la misma rapidez.

bash
grep -E 'MemTotal|MemAvailable|Cached|Buffers|Slab|SReclaimable|SUnreclaim|AnonPages' /proc/meminfo
sudo slabtop -o
ps -eo pid,user,comm,rss,vsz --sort=-rss | head -20

AnonPages aproxima memoria anónima usada por heaps y stacks; Cached suele representar archivos; SReclaimable es parte de slab que el kernel puede recuperar y SUnreclaim merece atención si crece sin estabilizarse. No vacíes cachés con drop_caches como solución habitual. Puede bajar el valor “used” durante unos minutos, pero obliga a releer archivos del disco y no corrige el proceso o patrón que creó la presión.

También revisa memoria compartida y procesos relacionados. PostgreSQL usa segmentos compartidos; varios procesos PHP pueden compartir páginas de código, y sumar RSS los cuenta repetidamente. Métricas como PSS, disponibles en herramientas que leen smaps, reparten esas páginas y ofrecen una estimación más fiel. Durante una emergencia basta con identificar los grandes consumidores; para dimensionar, mide el servicio completo bajo carga.

Contener un servicio con systemd antes del próximo pico

Cuando identificas un proceso que puede crecer sin límite, systemd permite asignarle una frontera mediante cgroups. MemoryHigh introduce presión antes del límite duro y MemoryMax impide que el servicio consuma toda la máquina. El valor correcto debe salir de una prueba funcional: un límite demasiado bajo transforma un incidente global en reinicios continuos de ese servicio.

Consulta primero la unidad y su consumo actual:

bash
systemctl show miapp.service -p MemoryCurrent -p MemoryPeak -p MemoryHigh -p MemoryMax
systemd-cgtop

Crea un override en lugar de editar la unidad distribuida por el paquete:

bash
sudo systemctl edit miapp.service
ini
[Service]
MemoryHigh=1200M
MemoryMax=1500M
OOMPolicy=stop
Restart=on-failure
RestartSec=5s

Después recarga, reinicia en una ventana y valida:

bash
sudo systemctl daemon-reload
sudo systemctl restart miapp.service
systemctl status miapp.service
systemctl show miapp.service -p MemoryCurrent -p MemoryPeak

Reserva memoria para el sistema, SSH, proxy y base. Si la aplicación y la base comparten VPS, no asignes a cada una un límite basado en toda la RAM. La suma de límites, más procesos sin limitar, debe dejar margen para kernel y operaciones como backups o actualizaciones. Comprueba además los logs de systemd tras una prueba controlada para reconocer cómo se verá un límite alcanzado.

En Docker existe la misma idea con límites del contenedor. Verifica el valor efectivo con docker inspect y el uso con docker stats; una línea escrita en Compose que nunca se aplicó no protege al host. Evita limitar solo Redis o la base sin entender su persistencia: un reinicio en medio de una escritura puede tener consecuencias distintas a reiniciar un frontend reconstruible.

Recuperar memoria sin destruir datos

Cuando el VPS todavía responde, ordena acciones por reversibilidad. Primero pausa la entrada de trabajo: desactiva temporalmente un cron pesado, reduce consumidores de cola o retira una instancia del tráfico. Luego detén de manera limpia el proceso identificado, permitiendo que cierre conexiones y archivos. kill -9 debe quedar para un proceso que no responde a SIGTERM y cuya pérdida de estado entiendes.

Antes de detener nada, captura una fotografía mínima en una ruta con espacio:

bash
date -Is
free -h
vmstat 1 5
ps -eo pid,ppid,user,comm,%mem,rss,vsz --sort=-rss | head -30
sudo journalctl -k --since '-30 min' | grep -Ei 'oom|out of memory|killed process'

Si el disco también está lleno, no generes volcados enormes. Guarda las líneas decisivas fuera del servidor o copia el journal desde la consola. Un heap dump de Node o Java puede ocupar tanto como el heap y empeorar el incidente; solo créalo cuando exista espacio y una ruta segura para analizarlo.

Para un proceso web, una recarga gradual suele ser menos disruptiva que matar todos los workers. En PHP-FPM reduce temporalmente la concurrencia y recarga; en Node conserva una instancia sana mientras reemplazas la que crece; en una cola detén consumidores y deja que los trabajos permanezcan pendientes. Para una base, cerrar al azar puede interrumpir transacciones y prolongar la recuperación; identifica conexiones o consultas concretas antes de cancelar.

Si SSH ya no entra, usa consola del proveedor. Añadir swap puede darte minutos para observar y cerrar limpiamente, pero escribir un archivo grande mientras el disco está presionado también consume I/O. No reinicies varias veces: cada arranque puede disparar los mismos servicios y volver a agotar RAM antes de que consigas acceso. Arranca en modo de recuperación o deshabilita temporalmente la unidad responsable si el procedimiento de la distribución lo permite.

Investigar fugas con una prueba que pueda repetirse

Una fuga no se demuestra porque el proceso use mucha RAM. Debes observar crecimiento que no se estabiliza al repetir la misma carga y después retirar el tráfico. Diseña una prueba con un número fijo de solicitudes, trabajos o archivos, registra RSS antes y después y deja un periodo sin actividad. Repite con la misma versión y datos.

En Node observa heap y RSS: si el heap se estabiliza pero RSS crece, pueden intervenir buffers, extensiones nativas o fragmentación. En Java compara heap usado después de recolecciones completas y memoria no-heap. En PHP-FPM un worker puede crecer por una ruta concreta; pm.max_requests recicla procesos y limita el impacto, pero no reemplaza corregir el plugin o código responsable.

Divide el problema por cambios recientes. Desactiva en staging una integración, versión o tipo de trabajo y compara la pendiente, sin alterar cinco variables a la vez. Conserva timestamps para cruzar despliegues, tráfico, colas y consumo. Si el crecimiento depende de un archivo específico, guarda una muestra saneada que permita reproducirlo sin exponer datos del cliente.

La corrección queda validada cuando la misma prueba alcanza una meseta, el servicio mantiene latencia y no aumenta swap de forma continua. Obsérvala además durante un ciclo real de negocio: una prueba de diez minutos no cubre una tarea diaria que libera mal sus recursos. Solo después recalcula la capacidad necesaria; de lo contrario puedes confundir el margen temporal aportado por más RAM con una solución permanente.

Entender por qué el kernel eligió ese proceso

Cuando ya no puede satisfacer una reserva, el kernel calcula qué proceso liberar según consumo y ajustes de prioridad OOM. El proceso muerto no siempre originó la presión: puede haber sido simplemente la víctima que devolvía más memoria. Por eso la línea Killed process es el inicio del diagnóstico, no su conclusión.

bash
for p in $(pgrep -f 'php-fpm|node|java|mysqld|postgres'); do
  printf '%s ' "$p"
  cat "/proc/$p/oom_score" "/proc/$p/oom_score_adj" 2>/dev/null | xargs
done

oom_score_adj modifica la preferencia entre procesos. No protejas una aplicación crítica asignándole el mínimo extremo sin pensar: podrías obligar al kernel a matar SSH, monitoreo o la base que contiene datos. Es mejor limitar el servicio que crece, reservar memoria y definir una recuperación limpia. Si necesitas ajustar prioridades, documenta la razón, prueba presión controlada y confirma qué unidad reinicia systemd.

En contenedores, distingue un OOM dentro del cgroup de un OOM global del host. El primero suele indicar que el límite del contenedor quedó corto; el segundo, que la suma de cargas agotó el VPS. Revisa tanto el estado del contenedor como el journal del kernel antes de aumentar una sola cifra.

Confirma OOM, identifica el proceso y estabiliza el servicio más pequeño posible. Luego distingue fuga, mala configuración y crecimiento real. Reiniciar es una maniobra de recuperación; la solución aparece en las métricas y límites.

¿Listo para llevar tu sitio al siguiente nivel?

Hosting con LiteSpeed, NVMe y soporte 24/7 desde $3/mes.

Ver planes de hosting

Preguntas frecuentes