En un VPS de 1 GB o 2 GB, MySQL y MariaDB funcionan rápido y estables en cuanto unos pocos ajustes de my.cnf se adaptan a la RAM que realmente tienes: innodb_buffer_pool_size, max_connections, los límites de tablas temporales y performance_schema. Esta guía te da configuraciones listas para ambos tamaños, te muestra cómo aplicarlas y comprobarlas con seguridad en Debian, Ubuntu, Rocky Linux y AlmaLinux, y cómo encontrar las consultas lentas que ningún ajuste puede arreglar.
mysql te pide contraseña, añade -u root -p. En MariaDB 11 (Debian 13), si no existe un comando mysql…, usa su nombre mariadb… (mariadb, mariadb-dump, mariadb-dumpslow).Paso 1: Comprueba la versión y el uso actual de memoria
Algunas opciones solo existen en MySQL o solo en MariaDB, así que primero averigua qué servidor y qué versión usas y cuánta memoria consume ahora mismo.
mysql --version # MariaDB 11 (Debian 13): mariadb --version
mysql -e "SELECT VERSION();"
free -h
ps -o pid,rss,cmd -C mysqld,mariadbd # RSS = memory really used, in KB
RSS está en kilobytes (350000 son unos 340 MB). Si con la carga normal available en free -h ya está por debajo de unos 150 MB, baja valores antes de subir ninguno. Versiones habituales: Debian 12 trae MariaDB 10.11, Debian 13 MariaDB 11.8, y Ubuntu 22.04/24.04 ofrecen MySQL 8.0 o MariaDB.
Paso 2: Haz primero una copia de seguridad de las bases y de la configuración
Cambiar la configuración no toca tus datos, pero un error durante las pruebas sí puede. Haz primero un volcado completo y copia el directorio de configuración. Si el disco está casi lleno, libera espacio con Disco lleno en Linux: cómo encontrar y liberar espacio en un VPS.
df -h / # enough free space for the dump?
mkdir -p /root/db-backup
mysqldump --all-databases --single-transaction --routines --events \
| gzip > /root/db-backup/all-$(date +%F).sql.gz
# MariaDB 11 (Debian 13): use mariadb-dump with the same options
cp -a /etc/mysql /root/db-backup/etc-mysql-$(date +%F)
# Rocky / AlmaLinux: cp -a /etc/my.cnf /etc/my.cnf.d /root/db-backup/
ls -lh /root/db-backup
--single-transaction copia las tablas InnoDB de forma coherente sin bloquear el sitio (no cubre las tablas MyISAM, así que vuélcalas en un momento tranquilo).
Paso 3: Encuentra dónde está la configuración
No edites el archivo grande que viene por defecto. Crea tu propio archivo pequeño en el directorio de inclusión: se lee después de los predeterminados, así que sus valores mandan, y las actualizaciones de paquetes nunca lo sobrescriben.
| Sistema | Directorio de inclusión | Archivo principal del servidor |
|---|---|---|
| Debian 12/13, Ubuntu 22.04/24.04 con MariaDB | /etc/mysql/mariadb.conf.d/ | 50-server.cnf |
| Ubuntu 22.04/24.04 con MySQL 8.0 | /etc/mysql/mysql.conf.d/ | mysqld.cnf |
| Rocky Linux / AlmaLinux (MariaDB o MySQL) | /etc/my.cnf.d/ | mariadb-server.cnf o mysql-server.cnf |
En todos los casos, crea zz-tuning.cnf en ese directorio. Los archivos se leen en orden alfabético, así que el prefijo zz- hace que el tuyo se cargue el último, y el nombre debe terminar en .cnf.
Paso 4: Ajustes recomendados para 1 GB y 2 GB
Empieza por el perfil que corresponda a tu RAM. Los dos dejan espacio para un servidor web y PHP en el mismo VPS.
VPS de 1 GB
# zz-tuning.cnf - 1 GB VPS, database shares the server with nginx/Apache and PHP
[mysqld]
performance_schema = OFF
skip-name-resolve
innodb_buffer_pool_size = 256M
max_connections = 40
thread_cache_size = 8
tmp_table_size = 32M
max_heap_table_size = 32M
table_open_cache = 400
table_definition_cache = 400
key_buffer_size = 16M
# 1 = safest (default), see Step 5
innodb_flush_log_at_trx_commit = 1
VPS de 2 GB
# zz-tuning.cnf - 2 GB VPS, database shares the server with nginx/Apache and PHP
[mysqld]
performance_schema = OFF
skip-name-resolve
innodb_buffer_pool_size = 512M
max_connections = 80
thread_cache_size = 16
tmp_table_size = 64M
max_heap_table_size = 64M
table_open_cache = 1000
table_definition_cache = 800
key_buffer_size = 32M
# 1 = safest (default), see Step 5
innodb_flush_log_at_trx_commit = 1
- innodb_buffer_pool_size: la caché de InnoDB para datos e índices, el valor más importante. La famosa regla del "70-80 % de la RAM" es para servidores dedicados solo a la base de datos. Con un stack web en el mismo VPS, empieza por alrededor del 25 % de la RAM; un VPS solo para la base puede usar unos 512M con 1 GB y 1G con 2 GB.
- max_connections: cada conexión puede reservar sus propios búferes (paso 10), así que el valor por defecto de 151 es arriesgado en un plan pequeño. Un sitio PHP rara vez necesita más que sus workers de PHP-FPM y unas pocas para cron y herramientas de administración.
- tmp_table_size y max_heap_table_size: el límite de las tablas temporales en memoria (
GROUP BY,ORDER BY,DISTINCT). Se aplica el menor, así que mantenlos iguales. Valores más altos sobre todo aumentan la RAM por consulta en el peor caso. - table_open_cache y table_definition_cache: unos cientos bastan para un VPS con pocos sitios; súbelos si
Opened_tablesno deja de crecer (paso 6). - performance_schema = OFF: viene activado en MySQL 8, donde puede ocupar más de 100 MB; en MariaDB ya está desactivado. Pierdes algunas vistas de diagnóstico, un precio razonable en un plan pequeño.
- skip-name-resolve: sin consulta DNS en cada conexión nueva, así los inicios de sesión no se cuelgan cuando el DNS va lento. A partir de ahí, las cuentas deben usar una dirección IP o
localhost, no un nombre de host. - key_buffer_size: solo cachea índices MyISAM (128 MB por defecto en MariaDB); los sitios con InnoDB necesitan muy poco.
Antes de activar skip-name-resolve, revisa las cuentas; si algún host es un nombre como app.example.com, vuelve a crear esa cuenta con su IP (por ejemplo 203.0.113.10). En MySQL 8, comprueba también cuánto espacio ocupan los logs binarios:
mysql -e "SELECT user, host FROM mysql.user;"
mysql -e "SHOW BINARY LOGS;" # MySQL 8: how much space binary logs use
Líneas según la versión de tu servidor
Algunas opciones solo existen en uno de los dos servidores. Añade el bloque que te corresponda al mismo archivo, bajo [mysqld], y ajusta los valores para 1 GB como indican los comentarios:
# --- MySQL 8.0.30 or newer only (Ubuntu MySQL 8.0, MySQL 8.4) ---
# Redo log: default 100M is fine on 1 GB; 256M for write-heavy sites on 2 GB
innodb_redo_log_capacity = 256M
# RAM cap for internal temporary tables: 64M on 1 GB, 128M on 2 GB
temptable_max_ram = 128M
# Keep binary logs 3 days instead of the default 30
binlog_expire_logs_seconds = 259200
# --- MariaDB, or MySQL older than 8.0.30 ---
# Redo log: default 96M (MariaDB) is fine on 1 GB; 128M-256M on 2 GB
innodb_log_file_size = 128M
Los logs binarios sirven para la replicación y la recuperación a un momento concreto; 30 días de logs de un sitio con mucho tráfico pueden llenar un disco de 20 GB. Si no usas ninguna de las dos cosas, MySQL 8 también acepta skip-log-bin.
unknown variable 'innodb_redo_log_capacity', y MySQL 8 rechaza query_cache_size porque la caché de consultas se eliminó. Si el servidor no arranca tras un cambio, lee el log (paso 6), corrige la línea y vuelve a iniciarlo.Paso 5: innodb_flush_log_at_trx_commit, el compromiso explicado con honestidad
Este ajuste decide cuántos datos confirmados puedes perder si el VPS se queda sin corriente o el kernel falla. Elígelo según lo que tu sitio se pueda permitir perder, no según benchmarks.
- 1 (por defecto): el redo log se vuelca a disco en cada commit, así que no se pierde nada confirmado. Úsalo para tiendas, facturación, cuentas de usuario y todo lo que tenga que ver con dinero.
- 2: en cada commit se escribe al sistema operativo y se vuelca a disco aproximadamente una vez por segundo. Un fallo del sistema o del VPS puede costar hasta ~1 segundo de transacciones; si solo falla la base de datos, no se pierde nada. Aceptable para blogs, cachés y estadísticas.
- 0: se escribe y se vuelca aproximadamente una vez por segundo, así que incluso un fallo de la propia base puede costar ~1 segundo. Rara vez mejor que 2; no recomendado.
El valor 2 puede acelerar bastante muchas escrituras pequeñas, pero cambias durabilidad por velocidad. Si dudas, deja 1; agrupar escrituras pequeñas en una sola transacción dentro de tu aplicación es una ganancia más segura.
Paso 6: Aplica los cambios con seguridad y comprueba el resultado
Revisa los archivos, reinicia y lee el log enseguida. Usa el nombre de servicio de tu servidor.
# 1. Show the options the server will read (MariaDB: mariadbd --print-defaults)
mysqld --print-defaults
# MySQL 8.0.16+ only: check the option files without starting the server
mysqld --validate-config
# 2. Restart and read the log
# Service name: mariadb (MariaDB) | mysql (MySQL on Ubuntu) | mysqld (MySQL on Rocky/Alma)
systemctl restart mariadb
systemctl status mariadb --no-pager
journalctl -u mariadb -n 30 --no-pager
Después confirma los nuevos valores y mira los contadores:
mysql -e "SHOW VARIABLES WHERE Variable_name IN ('innodb_buffer_pool_size','max_connections','tmp_table_size','performance_schema','skip_name_resolve');"
mysql -e "SHOW GLOBAL STATUS WHERE Variable_name IN ('Uptime','Max_used_connections','Created_tmp_tables','Created_tmp_disk_tables','Opened_tables','Innodb_buffer_pool_reads','Innodb_buffer_pool_read_requests','Aborted_connects');"
Los contadores de estado son totales desde el último arranque, así que léelos después de al menos un día de tráfico normal (Uptime está en segundos).
- Max_used_connections cerca de
max_connections: la aplicación abre demasiadas conexiones o tiene demasiados workers de PHP. Corrígelo antes de subir el límite. - Created_tmp_disk_tables por encima de ~25 % de
Created_tmp_tables: busca esas consultas en el log de consultas lentas (paso 7). Un índice suele ayudar más que untmp_table_sizemayor. - Innodb_buffer_pool_reads por encima del 1 % de
Innodb_buffer_pool_read_requests: los datos de trabajo no caben. Sube el buffer pool solo sifree -hmuestra memoria disponible de sobra. - Opened_tables sigue subiendo rápido tras el calentamiento: sube
table_open_cacheen pasos de unos 200.
Si Aborted_connects no deja de crecer, suele haber bots llegando al puerto 3306; si no tiene que ser público, bloquéalo como se explica en Configurar el firewall en Linux: UFW y firewalld en un VPS.
Paso 7: Encuentra las consultas lentas con el slow query log
La mayoría de los problemas de "base de datos lenta" son unas pocas consultas malas, no la configuración. El log de consultas lentas registra cada consulta que tarda más de long_query_time segundos.
# Turn the slow query log on now (until the next restart)
mysql -e "SET GLOBAL slow_query_log = 1; SET GLOBAL long_query_time = 1;"
mysql -e "SHOW VARIABLES LIKE 'slow_query_log_file';"
Para mantenerlo activo tras un reinicio, añade estas líneas a tu zz-tuning.cnf:
# Add to zz-tuning.cnf to keep it after restarts
[mysqld]
slow_query_log = 1
slow_query_log_file = /var/log/mysql/mysql-slow.log
long_query_time = 1
# Rocky / AlmaLinux with MariaDB: /var/log/mariadb/mysql-slow.log
Un nuevo long_query_time solo se aplica a las conexiones nuevas. Deja que el log recoja un día de tráfico normal y luego resúmelo:
# Top 10 query patterns by total time (MariaDB 11: mariadb-dumpslow)
mysqldumpslow -s t -t 10 /var/log/mysql/mysql-slow.log
# Turn the log off again when you are done
mysql -e "SET GLOBAL slow_query_log = 0;"
mysqldumpslow agrupa las consultas parecidas, sustituye los números por N y las cadenas por 'S', y muestra cuántas veces se ejecutó cada una y cuánto tardó. Empieza por arriba: una consulta ejecutada 10.000 veces a 0,2 segundos cuesta mucho más que una ejecutada una sola vez a 5 segundos. El log crece mientras está activo, así que apágalo al terminar o asegúrate de que /etc/logrotate.d/ lo rota.
Paso 8: Índices y EXPLAIN, lo básico
Pon EXPLAIN delante de un SELECT lento para ver cómo lo ejecuta el servidor. El ejemplo usa una tabla hipotética orders:
EXPLAIN SELECT id, total FROM orders
WHERE customer_id = 42 ORDER BY created_at DESC LIMIT 20;
SHOW INDEX FROM orders;
ALTER TABLE orders ADD INDEX idx_customer_created (customer_id, created_at);
-- Run EXPLAIN again: type should change from ALL to ref, key = idx_customer_created
EXPLAIN SELECT id, total FROM orders
WHERE customer_id = 42 ORDER BY created_at DESC LIMIT 20;
En la salida, type = ALL con key = NULL significa un recorrido completo de la tabla; ref, eq_ref o range indican que se usa un índice. Una estimación de rows muy alta para una consulta que devuelve 20 filas apunta a un índice que falta, y Using filesort o Using temporary en Extra significan trabajo extra de ordenación.
- Indexa primero las columnas del
WHERE(las que se comparan con=, al principio) y después la delORDER BY. - Evita funciones sobre columnas indexadas:
WHERE DATE(created_at) = '2026-11-01'no puede usar un índice, pero un rango comocreated_at >= '2026-11-01' AND created_at < '2026-11-02'sí. - No indexes todas las columnas: cada índice ralentiza las escrituras y ocupa memoria del buffer pool.
Paso 9: Usa mysqltuner como segunda opinión
mysqltuner lee los contadores del servidor y enumera posibles mejoras. Está empaquetado en Debian y Ubuntu; en Rocky/AlmaLinux suele estar disponible desde EPEL.
apt install mysqltuner # Debian / Ubuntu
mysqltuner
Úsalo con cuidado: ejecútalo solo tras 24 horas o más de tráfico normal y recuerda que no sabe nada de PHP ni del servidor web del mismo VPS, así que puede sugerir un buffer pool que no les deje sitio. Contrasta cada sugerencia con free -h y cambia un ajuste cada vez. Trata su salida como una lista de preguntas, no como instrucciones.
Paso 10: Evita el OOM killer
Cuando se acaba la RAM, el OOM killer de Linux detiene el proceso que más memoria usa, que suele ser la base de datos. Haz una suma aproximada del peor caso antes de subir nada:
# Per-connection buffers (about 2-3 MB per connection with defaults)
mysql -e "SHOW VARIABLES WHERE Variable_name IN ('sort_buffer_size','join_buffer_size','read_buffer_size','read_rnd_buffer_size','thread_stack');"
# Rough worst case, 1 GB profile:
# buffer pool 256 MB + overhead ~60 MB + 40 connections x ~2.5 MB ~100 MB
# = ~420 MB, leaving ~500 MB for the OS, web server and PHP
# Has the kernel already killed the database?
journalctl -k | grep -i -E 'out of memory|killed process'
El uso real suele ser menor, porque estos búferes solo se reservan cuando una consulta los necesita, y cada consulta puede usar además tablas temporales de hasta tmp_table_size; planifica para el pico. Nunca subas sort_buffer_size ni join_buffer_size a varios megabytes de forma global: multiplicados por max_connections, superan enseguida un plan de 1 GB. Si un informe necesita un búfer de ordenación grande, ejecuta SET SESSION sort_buffer_size = 8 * 1024 * 1024; solo en esa conexión.
Añade swap como colchón para picos cortos, como se explica en Cómo añadir memoria swap en un VPS Linux. La swap evita una caída, pero no hace rápida una base de datos a la que le falta RAM; si tras ajustar la memoria sigue escasa, la solución real es un plan mayor, consulta Cómo ampliar RAM, disco SSD o IP de un VPS.
Preguntas frecuentes
¿Debo activar la caché de consultas (query cache)?
No. MySQL 8 la eliminó, y en MariaDB (desactivada por defecto) se convierte en un cuello de botella por bloqueos con escrituras concurrentes. Cachea en la aplicación, por ejemplo con una caché de objetos.
Mis cambios no aparecen después de reiniciar. ¿Por qué?
Normalmente el archivo está en un directorio que no se incluye, su nombre no termina en .cnf, o un archivo leído después vuelve a fijar la misma opción. mysqld --print-defaults (en MariaDB, mariadbd --print-defaults) muestra la lista final de opciones, y SHOW VARIABLES muestra lo que está realmente en vigor.
¿Puedo usar una base de datos en el plan mini de 512 MB?
Para un sitio pequeño, sí, con swap y valores contenidos, como un buffer pool de 128M, 20 conexiones y performance_schema desactivado. Con ese tamaño, MariaDB suele ser la opción más ligera; los sitios con más tráfico van más holgados con 1 GB o más.
Si la base de datos sigue cayendo o no sabes qué valor cambiar, abre un ticket en https://www.justg.com/submitticket.php con la IP de tu servidor, tu zz-tuning.cnf y la salida de free -h. JUSTG ofrece soporte con IA 24/7 por ticket, y los ingenieros se encargan de los casos que necesitan a una persona.