Para que un VPS Linux de 512 MB o 1 GB funcione con fluidez, mide qué usa realmente la memoria, añade swap comprimida en RAM (zram) sobre un pequeño archivo swap, limita el journal de systemd y desactiva los servicios que un servidor no necesita. Esta guía recorre cada paso en Debian 12/13 y Ubuntu 22.04/24.04, señala las diferencias en Rocky Linux y AlmaLinux, y te enseña a leer los mensajes del OOM killer cuando aun así se agota la memoria.
Paso 1: Mide antes de cambiar nada
Encuentra primero a los verdaderos consumidores de memoria, para arreglar lo que de verdad importa.
free -h
ps -eo pid,user,rss,comm --sort=-rss | head -n 15 # RSS in KB, biggest first
systemd-cgtop -m -n 1 # memory per service
cat /proc/pressure/memory # memory pressure (PSI)
ps lista los procesos más grandes y systemd-cgtop suma la memoria por servicio, algo más claro cuando un servicio tiene muchos procesos worker. En /proc/pressure/memory, un valor avg60 que se mantiene por encima de ~10 indica que las tareas esperan memoria a menudo. (En Rocky/AlmaLinux, PSI está desactivado salvo que el kernel arranque con psi=1.)
RSS cuenta las bibliotecas compartidas completas en cada proceso, así que la suma parece mayor de lo que es. smem muestra PSS, que reparte la memoria compartida de forma justa entre los procesos:
apt install smem # Rocky / AlmaLinux: from EPEL
smem -tk -s pss -r | head -n 15
Paso 2: Lee bien free -h: available, no free
total used free shared buff/cache available
Mem: 961Mi 402Mi 74Mi 5.0Mi 484Mi 410Mi
Swap: 1.0Gi 12Mi 1.0Gi
Un valor free pequeño es normal. Linux usa la RAM libre como caché de páginas (buff/cache) para mantener archivos en memoria, y la devuelve en cuanto los programas la necesitan. El número que importa es available: una estimación de cuánta memoria puede obtener una nueva carga sin recurrir a la swap. En el ejemplo, solo hay 74 MB "libres", pero 410 MB disponibles, así que el servidor está bien. Preocúpate cuando available se quede por debajo de ~10 % de la RAM y el uso de swap siga creciendo.
Paso 3: Añade zram (swap comprimida en RAM)
zram crea un dispositivo swap dentro de la RAM que guarda las páginas comprimidas, a menudo entre 2 y 3 veces más pequeñas. Hacer swap ahí es mucho más rápido que en disco, así que es una forma muy eficaz de estirar un plan pequeño. El precio es algo de CPU para la compresión.
Comprueba que tu kernel tiene zram
modprobe zram && echo "zram is available"
swapon --show
Si modprobe responde Module zram not found, tu kernel se compiló sin él (pasa con algunos kernels cloud mínimos). Salta al paso 4 y usa solo un archivo swap.
Opción A: zram-tools (Debian, Ubuntu 22.04 y 24.04)
apt install zram-tools
cp /etc/default/zramswap /etc/default/zramswap.bak
cat > /etc/default/zramswap <<'EOF'
ALGO=zstd
PERCENT=50
PRIORITY=100
EOF
systemctl restart zramswap
zramctl
swapon --show
PERCENT=50 da al dispositivo zram la mitad de la RAM como tamaño (capacidad sin comprimir); la RAM que usa de verdad es el tamaño comprimido. PRIORITY=100 hace que el kernel use zram antes que cualquier swap en disco.
Opción B: systemd zram-generator (Debian 12/13, Ubuntu 24.04, Rocky/AlmaLinux 9)
apt install systemd-zram-generator # Rocky / AlmaLinux 9: dnf install zram-generator
cat > /etc/systemd/zram-generator.conf <<'EOF'
[zram0]
zram-size = min(ram / 2, 2048)
compression-algorithm = zstd
swap-priority = 100
EOF
systemctl daemon-reload
systemctl start [email protected]
zramctl
En Rocky/AlmaLinux, si dnf no encuentra el paquete, activa primero EPEL (dnf install epel-release).
zstd no se acepta, usa lz4, que además es más rápido en un plan de un solo núcleo con carga.Paso 4: Mantén un pequeño archivo swap de reserva y ajusta swappiness
zram vive en la RAM, así que un pequeño archivo swap en disco sigue siendo útil como última reserva: en estos planes bastan 512 MB-1 GB. Nuestra guía Cómo añadir memoria swap en un VPS Linux explica paso a paso cómo crearlo, añadirlo a fstab y cambiar su tamaño. Por defecto, un archivo swap tiene menor prioridad que el dispositivo zram, así que solo se usa cuando zram está lleno.
Con zram, sube vm.swappiness: llevar páginas comprimidas a la RAM sale más barato que descartar la caché de archivos y volver a leerla del disco. vm.page-cluster = 0 evita que el kernel lea varias páginas de swap a la vez, algo que solo ayuda con discos.
cat > /etc/sysctl.d/99-zram.conf <<'EOF'
vm.swappiness = 100
vm.page-cluster = 0
EOF
sysctl --system
sysctl vm.swappiness vm.vfs_cache_pressure
Los archivos de /etc/sysctl.d/ se leen en orden alfabético, así que 99-zram.conf sustituye el vm.swappiness=10 del 99-swap.conf de nuestra guía de swap. Sin zram, deja 10. El vm.vfs_cache_pressure=50 de esa guía está bien; nunca lo pongas a 0, porque el kernel dejaría de liberar los metadatos del sistema de archivos.
Paso 5: Limita journald
El journal de systemd guarda los logs en disco en /var/log/journal, o en /run/log/journal cuando el journal no es persistente. /run es un tmpfs, así que esa copia vive en la RAM. Limita ambos:
journalctl --disk-usage
mkdir -p /etc/systemd/journald.conf.d
cat > /etc/systemd/journald.conf.d/size.conf <<'EOF'
[Journal]
SystemMaxUse=100M
RuntimeMaxUse=20M
MaxRetentionSec=1month
EOF
systemctl restart systemd-journald
journalctl --vacuum-size=100M
journalctl --disk-usage
SystemMaxUse limita el journal en disco, RuntimeMaxUse el de memoria, y MaxRetentionSec borra las entradas con más de un mes. En Ubuntu, rsyslog escribe además los mismos mensajes en /var/log/syslog; logrotate lo rota, así que normalmente puedes dejarlo.
Paso 6: Desactiva los servicios que el servidor no necesita
Revisa qué se está ejecutando antes de parar nada, y desactiva solo lo que sepas identificar:
systemctl list-units --type=service --state=running
systemctl status multipathd --no-pager # what is it and what started it?
systemctl list-dependencies --reverse multipathd.service # does anything need it?
| Servicio | Dónde aparece | Se puede desactivar si |
|---|---|---|
snapd | Ubuntu | snap list no muestra ningún snap que uses (ver abajo) |
multipathd | Ubuntu Server | multipath -ll no muestra nada, lo normal en un VPS |
ModemManager | Algunas imágenes de Ubuntu | Siempre en un VPS: no hay módem |
tuned | Rocky / AlmaLinux | Opcional; ahorra algo de RAM. Si dudas, déjalo |
# Ubuntu: multipath storage daemon (not needed without SAN/multipath disks)
multipath -ll # no output = not in use
systemctl disable --now multipathd.service multipathd.socket
# Modem manager - a VPS has no modem
systemctl disable --now ModemManager
# Undo at any time:
systemctl enable --now multipathd.service multipathd.socket
snapd ocupa decenas de MB y monta un dispositivo loop por cada snap. Al quitarlo se eliminan también todos los snaps instalados con sus datos, así que revisa antes snap list. Si, por ejemplo, instalaste certbot como snap, instálalo desde apt antes de eliminar snapd. El archivo de pin impide que apt vuelva a instalar snapd como dependencia.
snap list # what is installed as a snap?
apt purge snapd
cat > /etc/apt/preferences.d/no-snapd.pref <<'EOF'
Package: snapd
Pin: release a=*
Pin-Priority: -10
EOF
ssh/sshd, los servicios propios de systemd (systemd-journald, systemd-logind, systemd-networkd, systemd-resolved), networking o NetworkManager, cron, la sincronización horaria (chrony o systemd-timesyncd) ni tu firewall (ufw, firewalld, nftables). Deja también qemu-guest-agent y cloud-init si están instalados: la plataforma puede depender de ellos y, una vez arrancado el servidor, apenas usan memoria. Usa disable en lugar de mask, para poder reactivar un servicio fácilmente.Paso 7: Mueve las actualizaciones automáticas a una hora tranquila
No desactives unattended-upgrades: las actualizaciones de seguridad importan más que unos pocos MB de RAM. El pico de memoria lo provoca apt al ejecutarse, así que prográmalo para un momento tranquilo. Por defecto, apt descarga a una hora aleatoria dentro de una ventana de 12 horas y actualiza entre las 06:00 y las 07:00 aproximadamente; estos drop-ins pasan la descarga a las 04:00 y la actualización a las 04:30, hora del servidor (compruébala con timedatectl):
mkdir -p /etc/systemd/system/apt-daily.timer.d /etc/systemd/system/apt-daily-upgrade.timer.d
cat > /etc/systemd/system/apt-daily.timer.d/override.conf <<'EOF'
[Timer]
OnCalendar=
OnCalendar=*-*-* 04:00
RandomizedDelaySec=15m
EOF
cat > /etc/systemd/system/apt-daily-upgrade.timer.d/override.conf <<'EOF'
[Timer]
OnCalendar=
OnCalendar=*-*-* 04:30
RandomizedDelaySec=15m
EOF
systemctl daemon-reload
systemctl list-timers 'apt-*'
En Rocky/AlmaLinux con dnf-automatic, cambia dnf-automatic.timer de la misma forma.
Paso 8: Elige software más ligero
- Quédate con OpenSSH. En reposo usa solo unos pocos MB. Alternativas como Dropbear apenas ahorran, carecen de algunas funciones de seguridad y las distribuciones las actualizan con menos frecuencia, así que no compensa el riesgo.
- Prefiere nginx a Apache con mod_php. Con el modelo prefork de Apache, cada proceso hijo lleva un intérprete PHP completo. nginx con PHP-FPM, o al menos Apache con
mpm_eventy PHP-FPM, usa mucha menos memoria. - Ejecuta PHP-FPM con
pm = ondemand. Los workers arrancan cuando llegan peticiones y terminan tras un rato inactivos, en lugar de esperar en memoria. - Ajusta la base de datos al plan. Los valores por defecto de MySQL y MariaDB suponen más RAM: baja el buffer pool y
max_connections, y desactivaperformance_schema.
; /etc/php/8.2/fpm/pool.d/www.conf (Rocky / AlmaLinux: /etc/php-fpm.d/www.conf)
pm = ondemand
pm.max_children = 5
pm.process_idle_timeout = 10s
pm.max_requests = 500
La versión de PHP en la ruta depende del sistema: 8.2 en Debian 12, 8.4 en Debian 13, 8.1 en Ubuntu 22.04 y 8.3 en Ubuntu 24.04. Fija pm.max_children como la memoria que puedes dar a PHP dividida entre el tamaño medio de un worker, por ejemplo 200 MB / 40 MB = 5. Mide el tamaño y después comprueba y recarga:
# Average size of a PHP-FPM process
ps -eo rss,comm | awk '/php-fpm/ {s+=$1; n++} END {if (n) printf "%d processes, average %.0f MB\n", n, s/n/1024}'
php-fpm8.2 -t && systemctl reload php8.2-fpm # Rocky / AlmaLinux: php-fpm -t && systemctl reload php-fpm
Paso 9: Entiende el OOM killer y encuentra los eventos OOM
Cuando se agotan la RAM y la swap, el OOM killer del kernel termina un proceso para salvar el sistema, normalmente el de mayor oom_score, que suele ser el más grande. Un servicio que se reinicia "solo" o una base de datos que desaparece suelen ser obra del OOM killer. Busca en el log del kernel:
journalctl -k | grep -i -E 'out of memory|oom-kill|killed process'
dmesg -T | grep -i -E 'out of memory|killed process'
journalctl -k -b -1 | grep -i 'killed process' # previous boot (persistent journal only)
Una línea típica tiene este aspecto:
Out of memory: Killed process 812 (mariadbd) total-vm:1325400kB, anon-rss:301220kB, file-rss:0kB, shmem-rss:0kB, UID:106
Indica el proceso y cuánta memoria tenía (anon-rss, en kB). Si el mismo servicio cae una y otra vez, reduce sus ajustes, añade swap o zram, o limítalo como en el paso 10. En Ubuntu, revisa también systemctl status systemd-oomd: si ese servicio está activo, puede detener servicios enteros bajo presión de memoria antes que el kernel.
Paso 10: Limita un servicio glotón con systemd MemoryMax
Un límite de memoria en un servicio protege al resto del servidor: si PHP-FPM tiene una fuga de memoria, solo se frena a PHP-FPM, mientras SSH y la base de datos siguen funcionando. MemoryHigh es un límite suave: por encima, el kernel ralentiza el servicio y recupera su memoria; MemoryMax es el límite duro, en el que el OOM killer actúa solo dentro de ese servicio.
stat -fc %T /sys/fs/cgroup # cgroup2fs = cgroup v2 (required)
mkdir -p /etc/systemd/system/php8.2-fpm.service.d
cat > /etc/systemd/system/php8.2-fpm.service.d/memory.conf <<'EOF'
[Service]
MemoryHigh=250M
MemoryMax=300M
# A killed worker is replaced by the master instead of stopping the service
OOMPolicy=continue
Restart=on-failure
EOF
systemctl daemon-reload
systemctl restart php8.2-fpm
systemctl show php8.2-fpm -p MemoryHigh -p MemoryMax -p MemoryCurrent
Debian 12/13, Ubuntu 22.04/24.04 y Rocky/AlmaLinux 9 usan cgroup v2 por defecto. En sistemas antiguos con cgroup v1, como Rocky/AlmaLinux 8, usa MemoryLimit=; MemoryHigh solo funciona con v2. Evita límites duros en la propia base de datos: un cierre repentino obliga a una recuperación tras fallo. Mejor ajusta sus parámetros.
Paso 11: Sabe cuándo ampliar
El ajuste ayuda cuando la memoria se queda corta de vez en cuando. Si available sigue bajo, la swap está siempre ocupada y vmstat 1 10 muestra entrada y salida de swap constantes (si/so), la carga simplemente necesita más RAM. Los servidores cloud de JUSTG se pueden ampliar desde la página del servicio; consulta Cómo ampliar RAM, disco SSD o IP de un VPS.
Preguntas frecuentes
¿zram es mejor que un archivo swap?
En velocidad, sí: trabaja a la velocidad de la memoria y evita el acceso a disco. Pero está limitado por la RAM, así que en planes pequeños lo mejor es combinar ambos: zram con prioridad alta para el día a día y un pequeño archivo swap como última reserva.
¿zram consume mucha CPU?
La compresión cuesta algo de CPU, normalmente poco. Si en un plan de un solo núcleo el servidor está haciendo swap a zram todo el tiempo, cambia a lz4 y reduce la carga: hacer swap sin parar significa que la memoria se queda corta.
¿Puedo usar Docker en el plan mini de 512 MB?
Uno o dos contenedores con aplicaciones pequeñas pueden funcionar, pero el daemon de Docker y el runtime de contenedores ocupan memoria por sí mismos. Pon límites de memoria a cada contenedor, usa zram y swap, y plantéate 1 GB o más si vas a tener varios contenedores.
Si los servicios siguen cayendo y no encuentras la causa, abre un ticket en https://www.justg.com/submitticket.php con la IP del servidor y la salida de free -h y journalctl -k | grep -i oom. JUSTG ofrece soporte con IA 24/7 por ticket, y los ingenieros se encargan de los casos que necesitan a una persona.