La mayoría de los problemas de lag en un servidor de juegos no se arreglan con decenas de ajustes del kernel, sino identificando el cuello de botella real: un núcleo de CPU saturado, falta de memoria, paquetes UDP descartados o un disco lento. En este tutorial medirás primero cada recurso en Ubuntu 24.04 y después aplicarás solo los ajustes que tienen efecto demostrable: prioridad y límites con systemd, búferes de red para UDP, uso de swap y memoria de la JVM para servidores Java.
Requisitos previos
- Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath.
- Un usuario no root con privilegios
sudo. - Un servidor de juego que se ejecute como servicio systemd. En esta guía se llama
gameserver.service; sustitúyelo por el nombre de tu unidad.
Paso 1: Medir antes de cambiar nada
Instala las herramientas de diagnóstico. htop muestra el uso por núcleo y por proceso, y sysstat aporta mpstat, pidstat e iostat:
sudo apt update
sudo apt install htop sysstat
Mide con el servidor lleno o en su hora punta, no vacío. Empieza por la CPU de cada núcleo, en muestras de 5 segundos:
mpstat -P ALL 5 3
La mayoría de servidores de juego ejecutan la simulación del mundo en un solo hilo. Si un núcleo está cerca del 100 % mientras el resto está libre, el límite es la velocidad de ese núcleo, no el número de núcleos. Fíjate también en la columna %steal: si es alta de forma sostenida en un VPS, el hipervisor no te está dando todo el tiempo de CPU.
Consulta el consumo del proceso del juego con pidstat, usando el PID principal del servicio:
pid=$(systemctl show -p MainPID --value gameserver.service)
pidstat -u -r -p "$pid" 5 3
Average: UID PID %usr %system %guest %wait %CPU CPU Command
Average: 1001 12345 78.40 4.20 0.00 0.60 82.60 - valheim_server.
Revisa la memoria disponible y el uso de swap:
free -h
Si available es muy baja o la swap está en uso constante, el servidor necesita más RAM o un límite de memoria más ajustado.
Comprueba si el disco es un cuello de botella durante los guardados del mundo:
iostat -x 5 3
Valores altos de %util y r_await/w_await de decenas de milisegundos coincidiendo con los tirones indican que el problema es el almacenamiento.
Por último, busca paquetes UDP descartados por falta de búfer, una causa habitual de lag con muchos jugadores:
nstat -az UdpRcvbufErrors UdpSndbufErrors
#kernel
UdpRcvbufErrors 0 0.0
UdpSndbufErrors 0 0.0
Si estos contadores crecen mientras hay jugadores, aplica el paso 3.
Paso 2: Dar prioridad al proceso con systemd
Si en el mismo servidor corren otros procesos (copias de seguridad, bases de datos, otro juego), puedes dar más prioridad de CPU y de disco al servidor de juego. Hazlo con un drop-in de systemd, que sobrevive a reinicios y actualizaciones del paquete:
sudo systemctl edit gameserver.service
Añade este bloque en la zona indicada del editor:
[Service]
Nice=-5
IOWeight=500
Nice=-5da al proceso más prioridad que los procesos normales (valor por defecto0). No bajes de-10: podrías restar tiempo de CPU a SSH y al propio sistema.IOWeight(1 a 10000, por defecto 100) da preferencia al servicio cuando hay competencia por el disco.
Si alojas varios servidores de juego en una máquina con muchos núcleos, puedes repartirlos en núcleos distintos con CPUAffinity= para que no compitan por la misma caché. Por ejemplo, CPUAffinity=2 3 en un servicio y CPUAffinity=4 5 en otro. En una máquina con pocos núcleos o con un único servidor, no fijes afinidad: el planificador de Linux lo hace mejor.
Aplica los cambios reiniciando el servicio y comprueba que se han cargado:
sudo systemctl restart gameserver.service
systemctl show gameserver.service -p Nice -p IOWeight
Nice=-5
IOWeight=500
Advertenciano uses prioridad de tiempo real (
chrtoCPUSchedulingPolicy=fifo) con servidores de juego. Un proceso en tiempo real que se queda en bucle puede bloquear por completo el servidor, incluido el acceso por SSH.
Paso 3: Ampliar los búferes de red para UDP
Los juegos multijugador usan UDP. Cuando llegan muchos paquetes a la vez y el búfer de recepción del socket está lleno, el kernel los descarta y los jugadores notan saltos. Aumenta el tamaño máximo de búfer que pueden pedir las aplicaciones y la cola de entrada de la interfaz. Crea un archivo de configuración propio:
sudo nano /etc/sysctl.d/90-gameserver.conf
# Tamaño máximo de búfer de socket (bytes) que pueden solicitar las aplicaciones
net.core.rmem_max = 26214400
net.core.wmem_max = 26214400
# Tamaño por defecto para sockets que no lo fijan
net.core.rmem_default = 1048576
net.core.wmem_default = 1048576
# Paquetes en cola por CPU antes de que el kernel los procese
net.core.netdev_max_backlog = 5000
Carga la configuración y verifica un valor:
sudo sysctl --system
sysctl net.core.rmem_max
net.core.rmem_max = 26214400
Reinicia el servidor de juego para que sus sockets usen los nuevos valores por defecto y vuelve a observar UdpRcvbufErrors con el servidor lleno. Si ya era 0 antes del cambio, este ajuste no mejorará nada y puedes omitirlo.
Paso 4: Controlar el uso de swap
Cuando el kernel mueve a swap memoria del servidor de juego, cada acceso a esas páginas provoca un tirón. Con vm.swappiness bajo, Linux prefiere liberar caché de disco antes que enviar memoria de procesos a la swap. Comprueba el valor actual:
cat /proc/sys/vm/swappiness
60
Bájalo a 10 añadiendo una línea al mismo archivo del paso anterior:
sudo nano /etc/sysctl.d/90-gameserver.conf
vm.swappiness = 10
sudo sysctl --system
Mantén una swap pequeña (1 a 2 GB) como red de seguridad: sin ella, un pico de memoria hace que el OOM killer termine el proceso del juego. Si no tienes swap (swapon --show no devuelve nada), crea un archivo:
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
Para proteger al resto del sistema de una fuga de memoria del juego, puedes añadir un límite en el drop-in del paso 2, por ejemplo MemoryMax=6G en un servidor de 8 GB. Si el proceso lo supera, systemd lo termina y, con Restart=on-failure, lo vuelve a arrancar.
Paso 5: Ajustar la memoria de servidores Java
Servidores como Minecraft Java o Project Zomboid se ejecutan en la JVM, y la configuración de memoria influye directamente en las pausas del recolector de basura. Tres reglas cubren la mayoría de los casos:
- Asigna el mismo valor a
-Xmsy-Xmxpara que el heap no crezca ni encoja durante la partida. - Deja al menos 1 o 2 GB de RAM libres para el sistema operativo y la memoria nativa de la JVM. Un heap que ocupa toda la RAM provoca swap y es peor que uno más pequeño.
- Usa el recolector G1, que viene por defecto en Java 17 y 21, y no mezcles opciones de varios recolectores.
Un ejemplo de arranque para un servidor Minecraft Paper con 6 GB de heap:
java -Xms6G -Xmx6G -XX:+UseG1GC -XX:+ParallelRefProcEnabled -XX:MaxGCPauseMillis=200 -jar paper.jar --nogui
Para ver si las pausas del recolector coinciden con el lag, activa el log de GC añadiendo -Xlog:gc:file=gc.log al comando y busca pausas largas en el archivo. En Project Zomboid la memoria se define en ProjectZomboid64.json, en el argumento -Xmx del array vmArgs.
Paso 6: Reducir la carga del disco
En un VPS con disco NVMe o SSD el planificador de E/S ya es el adecuado (none o mq-deadline) y no hay que tocarlo. Lo que más ayuda es reducir escrituras innecesarias:
- Espacia los autoguardados del mundo si el juego lo permite; guardar cada minuto un mapa grande genera picos de E/S.
- Programa las copias de seguridad y las compresiones fuera de la hora punta y con baja prioridad, por ejemplo
nice -n 19 ionice -c3 tar .... - Limita el tamaño del journal para que los logs verbosos del juego no llenen el disco:
sudo mkdir -p /etc/systemd/journald.conf.d
sudo nano /etc/systemd/journald.conf.d/size.conf
[Journal]
SystemMaxUse=1G
sudo systemctl restart systemd-journald
journalctl --disk-usage
Paso 7: Ajustes propios del juego
Los ajustes del sistema no compensan una configuración del juego demasiado exigente. Lo que más carga suele ser, por este orden: número de jugadores, distancia de visión o de simulación, cantidad de entidades (zombis, animales, construcciones) y mods. Algunos ejemplos concretos:
| Juego | Ajuste | Efecto |
|---|---|---|
| Minecraft (Paper) | view-distance y simulation-distance en server.properties | Menos chunks cargados y simulados por jugador |
| Project Zomboid | MaxPlayers y población de zombis en SandboxVars.lua | Menos entidades que simular |
| Valheim | Tamaño y número de construcciones en el mundo | Menos objetos que sincronizar |
Cambia un ajuste cada vez y vuelve a medir como en el paso 1 para saber cuál tiene efecto.
Solución de problemas
- Un núcleo al 100 % y el resto libres: el juego está limitado por un solo hilo. Reduce la carga del juego (paso 7) o usa un plan con CPU de mayor frecuencia; añadir núcleos no ayudará.
- El proceso desaparece y en
journalctl -kapareceOut of memory: Killed process: el sistema se quedó sin memoria. Reduce el heap de la JVM, añade swap (paso 4) o amplía la RAM. %stealalto de forma sostenida: el servidor comparte CPU física con otros. Considera un plan con CPU dedicada.- Lag solo para algunos jugadores: probablemente es la ruta de red entre ellos y el servidor, no el servidor. Pide a los afectados un
mtr your_server_ippara localizar dónde se pierden paquetes.
Conclusión
Has aprendido a identificar el recurso que limita tu servidor de juegos y a aplicar solo los ajustes que corresponden: prioridad y límites con systemd, búferes UDP, swappiness, memoria de la JVM y menos carga de disco. Como siguientes pasos, vigila estas métricas de forma continua con una herramienta de monitorización como Netdata o Prometheus, automatiza el mantenimiento con LinuxGSM y revisa la configuración del juego cada vez que crezca el número de jugadores.
