Ejecutar llama-server en un VPS con systemd
Compila llama-server desde una etiqueta fija, sirve modelos GGUF con la API compatible con OpenAI, limita el uso de memoria y escucha solo en localhost.
Qué está construyendo
Ejecutar el servidor de llama.cpp en un VPS requiere un único binario, llama-server, que carga un solo archivo de modelo GGUF y responde a solicitudes HTTP mediante una API compatible con OpenAI. Apunte cualquier cliente de OpenAI a http://127.0.0.1:8080/v1 y funcionará. La instalación es la parte sencilla.
El resto del trabajo corresponde a las operaciones: fijar una versión, mantener el puerto en localhost, escribir una unidad de systemd y decidir qué ocurre cuando el servidor se queda sin memoria. Esto es lo que cubre esta guía. Si todavía no ha decidido entre las dos opciones evidentes, lea primero las diferencias entre Ollama y llama.cpp, porque esta es la guía práctica que esa comparación omite deliberadamente.
Elija una etiqueta de versión y anótela
llama.cpp etiqueta una versión en casi cada merge, por lo que las etiquetas funcionan como números de compilación. b10488 es la más reciente a fecha de 18 August 2026. No existe una rama estable de larga duración. Por tanto, «latest» cambia continuamente y la versión que ha probado es la única que puede admitir. Elija una etiqueta, regístrela y use esa misma cadena en el clon, en el nombre del binario y en sus notas.
Cada etiqueta también incluye archivos comprimidos precompilados. Para una VPS x86 que sólo use la CPU, es llama-b10488-bin-ubuntu-x64.tar.gz. Si usa una VPS ARM en lugar de x86, encontrará un archivo arm64 junto a él.
curl -LO https://github.com/ggml-org/llama.cpp/releases/download/b10488/llama-b10488-bin-ubuntu-x64.tar.gz
tar tf llama-b10488-bin-ubuntu-x64.tar.gz | headListe el archivo comprimido antes de extraerlo para saber dónde se ubicarán los archivos. Esos binarios están enlazados con la biblioteca C de la imagen con la que se compilaron. En una distribución más antigua, fallan al iniciarse y muestran un error que indica una versión de GLIBC_ que no está instalada. Compilar desde el código fuente tarda unos minutos en una VPS pequeña y elimina toda esa clase de problemas. Por eso, ese es el procedimiento que se muestra a continuación.
Compilar llama-server desde un tag fijado
sudo apt update
sudo apt install -y build-essential cmake git libssl-dev
git clone --depth 1 --branch b10488 https://github.com/ggml-org/llama.cpp
cd llama.cpp
cmake -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DLLAMA_BUILD_TESTS=OFF -DLLAMA_BUILD_EXAMPLES=OFF
cmake --build build --config Release -t llama-server -j 2--branch b10488 en un --depth 1 clone extrae ese tag y nada más, por lo que la compilación no puede desviarse mientras trabajas.
libssl-dev es importante porque la opción LLAMA_OPENSSL está activada de forma predeterminada. Esta opción permite que el binario descargue modelos mediante HTTPS más adelante. Sin las cabeceras, el paso de configuración falla.
-DBUILD_SHARED_LIBS=OFF genera un binario autocontenido. La compilación predeterminada coloca las bibliotecas compartidas junto al ejecutable. Por eso, copiar sólo el ejecutable a /usr/local/bin falla después con error while loading shared libraries: libllama.so.
-t llama-server compila sólo el destino del servidor. La compilación predeterminada también compila las demás herramientas y las pruebas. En una VPS de dos núcleos, esto añade varios minutos para archivos que nunca ejecutarás.
-j 2 es intencionado. Cada trabajo de compilación en paralelo mantiene su propio conjunto de trabajo. Por eso, -j $(nproc) en un plan pequeño termina con c++: fatal error: Killed signal terminated program cc1plus, cuando el asesino de procesos por falta de memoria del kernel detiene el compilador. Reduce el número de trabajos o añade swap para la compilación.
Es posible que quieras cambiar una opción: GGML_NATIVE está activada de forma predeterminada, por lo que el compilador genera código para la CPU exacta donde se realiza la compilación. Esto es lo que necesitas cuando compilas en la máquina que ejecutará el binario. Si compilas una vez y copias el binario a otro host, añade -DGGML_NATIVE=OFF, porque un binario que usa instrucciones que la otra CPU no tiene termina con Illegal instruction (core dumped) en la primera inferencia.
Instálalo con un nombre que incluya el tag.
./build/bin/llama-server --version
sudo install -m 755 build/bin/llama-server /usr/local/bin/llama-server-b10488
sudo ln -sfn /usr/local/bin/llama-server-b10488 /usr/local/bin/llama-server--version muestra el número de compilación y el commit. Debe coincidir con el tag que extrajiste. Si no coincide, compilaste otra cosa. Mantener el número en el nombre de archivo y apuntar un enlace simbólico a él permite hacer una actualización con un ln -sfn y un reinicio. Para volver a una versión anterior, se usa el mismo comando con el número antiguo.
Obtenga un modelo GGUF y compruebe primero el disco
GGUF es el formato de un único archivo que carga llama.cpp. Un archivo contiene los pesos, el tokenizador y los metadatos, por lo que no hay nada más que instalar. El sufijo del nombre de archivo indica la cuantización, es decir, la precisión con la que se almacenan los pesos: Q4_K_M es una combinación de 4 bits, Q8_0 usa 8 bits y f16 es el archivo de precisión media sin cuantizar.
Cree una cuenta de servicio y un directorio para el modelo antes de descargar nada.
sudo useradd --system --home /srv/llama --create-home --shell /usr/sbin/nologin llama
sudo install -d -o llama -g llama /srv/models
df -h /srvEl servidor puede obtener un modelo por sí mismo con -hf. Es la forma más rápida de comprobar que la compilación funciona.
sudo -u llama env LLAMA_CACHE=/srv/models /usr/local/bin/llama-server \
-hf ggml-org/gemma-3-1b-it-GGUF:Q4_K_M --host 127.0.0.1 --port 8080LLAMA_CACHE establece el directorio de descarga. Sin esta opción, el archivo se guarda en ~/.cache/llama.cpp dentro de la cuenta que ejecutó el comando. Esa es la ubicación incorrecta para un servicio cuyo directorio personal está a punto de dejar de ser accesible. Ejecute ls -lh /srv/models después, porque el nombre de archivo almacenado en caché se genera a partir del nombre del repositorio y no del nombre de archivo simple.
Para un servicio, descargue el modelo en una ruta elegida por usted. Así, el archivo de unidad puede apuntar a una ubicación estable.
sudo -u llama curl -L --output-dir /srv/models -O \
https://huggingface.co/ggml-org/gemma-3-1b-it-GGUF/resolve/main/gemma-3-1b-it-Q4_K_M.ggufEl disco suele ser el primer límite que encuentran los usuarios. Estos son los tamaños de archivo publicados para dos modelos, comprobados el 18 de agosto de 2026.
The data behind this chart
[
{
"label": "gemma-3-1b-it Q4_K_M",
"size_gb": 0.81
},
{
"label": "gemma-3-1b-it Q8_0",
"size_gb": 1.07
},
{
"label": "gemma-3-1b-it f16",
"size_gb": 2.01
},
{
"label": "gpt-oss-20b MXFP4",
"size_gb": 12.11
}
]El archivo de 4 bits del modelo 1B ocupa 0.81 GB. El mismo modelo sin cuantización ocupa 2.01 GB, por lo que la elección del formato cambia el tamaño en más del doble. Un modelo 20B en MXFP4 ocupa 12.11 GB. No cabe en muchos planes básicos y, además, después debe cargarse en memoria. Si está considerando una familia concreta, el mismo cálculo de tamaños para GLM muestra con qué rapidez el modelo principal deja de ser viable en un VPS, mientras que una variante menor sí cabe.
Compruebe df -h antes de cada descarga. Un sistema de archivos raíz que se llene durante una transferencia de 12 GB impedirá escribir a todo lo demás, incluido el journal.
Ejecútelo una vez manualmente y compruébelo
sudo -u llama /usr/local/bin/llama-server \
--model /srv/models/gemma-3-1b-it-Q4_K_M.gguf \
--host 127.0.0.1 --port 8080 \
--ctx-size 4096 --parallel 1 --threads 2 --no-webuiEn una segunda sesión, pregunte al servidor si está listo.
curl -s http://127.0.0.1:8080/healthMientras se carga el archivo, recibirá HTTP 503 con este cuerpo:
{"error":{"code":503,"message":"Loading model","type":"unavailable_error"}}Cuando esté listo, el cuerpo será {"status": "ok" }. Después, envíe una solicitud real.
curl -s http://127.0.0.1:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"local","messages":[{"role":"user","content":"Say hello in five words."}]}'Un objeto JSON con un arreglo choices indica que el servidor funciona. El campo model existe porque los clientes de OpenAI siempre lo envían. Este servidor tiene un solo modelo cargado, por lo que el valor no se usa para seleccionar ninguno.
La API compatible con OpenAI y qué más hay en el puerto
POST /v1/chat/completions, POST /v1/completions y POST /v1/embeddings son las rutas compatibles con OpenAI, y GET /v1/models informa del modelo cargado. GET /health es la comprobación de disponibilidad anterior, GET /props devuelve la configuración actual del servidor y GET /metrics expone contadores de Prometheus cuando se inicia con --metrics.
Cualquier SDK de OpenAI funciona cuando se configura la URL base como http://127.0.0.1:8080/v1 y se pasa una cadena de clave de API no vacía. Esa clave no se comprueba hasta que se configura --api-key manualmente.
No tomes como referencia para tu propio plan ninguna afirmación ajena sobre el rendimiento. La velocidad de inferencia en la CPU depende del número de núcleos, del ancho de banda de memoria y de los vecinos con los que compartas el host. Por eso, mide los tokens por segundo en tu propio equipo y considera ese resultado como la referencia real. El tiempo de CPU robado por un vecino ruidoso se manifiesta aquí como una velocidad de generación que cambia de una hora a otra.
Manténgalo en 127.0.0.1 y coloque un proxy delante
--host ya usa 127.0.0.1 de forma predeterminada, por lo que el servidor no es accesible desde el exterior hasta que lo cambie. Déjelo así. llama-server no tiene un modelo de usuarios, límite de tasa ni un registro de auditoría útil, y el único control integrado es --api-key, que compara una cadena. Un puerto de inferencia abierto proporciona capacidad de cómputo gratuita a cualquiera que lo encuentre, y el mismo error con Ollama tiene la misma causa: proteger una API de modelos autoalojada se aplica aquí de principio a fin.
Termine TLS (transport layer security) en nginx y redirija las peticiones al puerto de loopback.
server {
listen 443 ssl;
server_name llm.example.com;
location /v1/ {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_buffering off;
proxy_read_timeout 600s;
}
}proxy_buffering off es necesario para la transmisión progresiva. Con el almacenamiento en búfer activado, nginx retiene los eventos enviados por el servidor (SSE) hasta que finaliza la respuesta. El cliente permanece esperando sin recibir datos y después obtiene toda la respuesta de una vez. proxy_read_timeout 600s cubre las generaciones largas, porque el valor predeterminado de 60 segundos convierte una respuesta lenta en 504 Gateway Time-out. Obtenga el certificado con Certbot y Let's Encrypt en nginx.
La unidad de systemd
Escriba /etc/systemd/system/llama-server.service.
[Unit]
Description=llama.cpp server
After=network-online.target
Wants=network-online.target
[Service]
User=llama
Group=llama
Environment=LLAMA_ARG_MODEL=/srv/models/gemma-3-1b-it-Q4_K_M.gguf
Environment=LLAMA_ARG_HOST=127.0.0.1
Environment=LLAMA_ARG_PORT=8080
Environment=LLAMA_ARG_CTX_SIZE=4096
Environment=LLAMA_ARG_N_PARALLEL=1
Environment=LLAMA_ARG_THREADS=2
ExecStart=/usr/local/bin/llama-server --no-webui
Restart=on-failure
RestartSec=5
TimeoutStopSec=30
MemoryHigh=3G
MemoryMax=3500M
OOMPolicy=stop
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
[Install]
WantedBy=multi-user.targetLa configuración se encuentra en Environment= porque llama-server lee variables de LLAMA_ARG_* para la mayoría de las opciones, y un argumento de la línea de comandos sobrescribe la variable correspondiente. Así tiene un único lugar donde cambiar el tamaño del contexto y ExecStart se mantiene lo bastante corto para leerlo de un vistazo.
ProtectSystem=strict hace que todo el sistema de archivos sea de solo lectura para esta unidad, lo que es correcto porque el servidor solo lee el modelo. Añada ReadWritePaths=/srv/models si quiere que el propio servicio descargue modelos con -hf. ProtectHome=yes oculta /home y /root, y ese es el segundo motivo para mantener los modelos en /srv: con ProtectHome activado, la ruta ~/.cache/llama.cpp predeterminada no es visible para el proceso.
sudo systemctl daemon-reload
sudo systemctl enable --now llama-server
systemctl status llama-server
curl -s http://127.0.0.1:8080/health
journalctl -u llama-server -n 50 --no-pagerenable --now es la parte que muchos omiten. Sin enable, el servidor dejará de estar disponible después del siguiente reinicio. Si quiere programar tareas relacionadas con el servicio, como comprobar cada noche si existe una versión nueva, un servicio de systemd más un temporizador es el mecanismo adecuado.
Decida qué ocurre cuando se agota la memoria antes de que suceda
El uso de memoria tiene dos componentes y se comportan de forma distinta bajo un límite. De forma predeterminada, el archivo del modelo se asigna en memoria, por lo que sus páginas están respaldadas por el archivo: el kernel puede descartarlas y volver a leerlas del disco. La caché KV, que contiene el estado por token que el servidor mantiene para cada conversación activa, es memoria anónima. No se puede descartar, por lo que es la que provoca la terminación del proceso.
Por eso los dos límites de la unidad tienen funciones distintas. MemoryHigh=3G es un límite flexible: cuando se supera, el kernel somete el cgroup a presión de recuperación, de modo que las páginas asignadas del modelo se expulsan y se vuelven a leer del disco para el token siguiente. El servicio sigue funcionando, pero se ralentiza. MemoryMax=3500M es un límite estricto: cuando se supera, el proceso termina y el journal lo indica claramente.
llama-server.service: A process of this unit has been killed by the OOM killer.Establezca --ctx-size manualmente. El valor predeterminado es 0, que representa el contexto con el que se entrenó el modelo y que, en un modelo moderno con un contexto largo, asigna una caché KV muy grande durante el arranque. El servicio muere antes de atender una sola petición. --parallel multiplica el mismo coste, porque cada slot contiene su propio estado de conversación. Déjelo en 1 hasta que necesite concurrencia.
Con Restart=on-failure, un servicio terminado vuelve a iniciarse. Si muere en cada arranque, systemd deja de intentarlo y systemctl status muestra start request repeated too quickly. Es el comportamiento correcto: un bucle de reinicios que vuelve a leer un archivo de 12 GB cada cinco segundos es peor que una interrupción del servicio. Corrija el límite o el tamaño del contexto y borre después el estado con sudo systemctl reset-failed llama-server.
Observe el valor real con systemctl show llama-server -p MemoryCurrent mientras se procesa una petición. Limitar la memoria y la CPU de un proceso con systemd explica estas directivas con más detalle.
Evite usar swap con esta carga de trabajo. Un modelo expulsado a swap convierte cada token en lecturas del disco en desplazamientos aleatorios. La asignación del archivo del modelo en memoria consigue el mismo efecto con menos impacto, porque el kernel lee directamente del archivo las páginas que necesita.
Cuándo Ollama es la mejor opción
Este es un punto de decisión. Elija llama-server cuando quiera un solo proceso con las opciones que haya definido, una compilación fijada y el archivo que haya elegido. Nada cambia por debajo porque no se está ejecutando ningún otro componente.
Elija Ollama cuando necesite administrar modelos: descargar modelos por nombre, mantener varios en el disco, descargar de la memoria uno que esté inactivo y actualizar con un solo comando en lugar de volver a compilar. Es trabajo real que, de otro modo, tendría que automatizar con scripts. Ejecutar Ollama en un VPS realiza este mismo trabajo, pero con la decisión tomada en el sentido contrario. Ambos exponen una API compatible con OpenAI, por lo que el código cliente sigue funcionando al cambiar de una opción a otra.
Actualizar una compilación fijada
Sustituya bNNNNN por la etiqueta a la que va a cambiar.
cd llama.cpp
git fetch --tags
git checkout bNNNNN
cmake -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DLLAMA_BUILD_TESTS=OFF -DLLAMA_BUILD_EXAMPLES=OFF
cmake --build build --config Release -t llama-server -j 2
sudo install -m 755 build/bin/llama-server /usr/local/bin/llama-server-bNNNNN
sudo ln -sfn /usr/local/bin/llama-server-bNNNNN /usr/local/bin/llama-server
sudo systemctl restart llama-serverEl binario antiguo permanece en el disco, por lo que una reversión consiste en ejecutar ln -sfn para volver a llama-server-b10488 y reiniciar. Lea las notas de la versión antes de cambiar. Los archivos GGUF tienen versiones y los antiguos siguen cargándose, pero los flags cambian de nombre: --mlock y --no-mmap ya están obsoletos en favor de --load-mode, y un archivo de unidad que pase un flag eliminado falla al iniciar con un mensaje de argumento no reconocido.
Modos de fallo y mensajes que verá
error while loading shared libraries: libllama.so después de copiar el binario a otra ubicación. La compilación predeterminada genera bibliotecas compartidas junto al binario. Vuelva a compilar con -DBUILD_SHARED_LIBS=OFF o copie todo el directorio build/bin.
Illegal instruction (core dumped) durante el arranque o en la primera solicitud. El binario se compiló con GGML_NATIVE activado para una CPU distinta de la que lo ejecuta. Vuelva a compilar en esta máquina o configure con -DGGML_NATIVE=OFF.
c++: fatal error: Killed signal terminated program cc1plus durante la compilación. El compilador terminó porque consumía demasiada memoria. Reduzca -j o añada swap para la compilación y elimínelo después.
curl: (7) Failed to connect ... Connection refused desde el portátil. Es correcto: el servidor escucha en la dirección de loopback del VPS. Haga la prueba en el propio VPS o abra un túnel con ssh -L 8080:127.0.0.1:8080 user@your-vps y use http://127.0.0.1:8080 localmente.
HTTP 503 con "message":"Loading model" durante los primeros segundos o minutos después de reiniciar. Leer un archivo de varios gigabytes requiere tiempo, y systemd informa de que la unidad está activa en cuanto se inicia el proceso, mucho antes de que el modelo esté en memoria.
Las solicitudes se bloquean y después devuelven 504 Gateway Time-out. El proxy agotó el tiempo de espera antes de que terminara el modelo. Aumente proxy_read_timeout y desactive proxy_buffering para que los tokens lleguen al cliente a medida que se generan.
La unidad entra en un ciclo de arranque y parada y después se detiene con start request repeated too quickly. Algo la termina en cada inicio. Compruebe journalctl -u llama-server para buscar la línea del OOM killer. Después, reduzca --ctx-size, reduzca --parallel o aumente MemoryMax.
FAQ
¿Debo ejecutar el servidor de llama.cpp o Ollama en mi VPS?
Ejecute llama-server cuando necesite fijar una compilación exacta, pasar flags exactos y mantener un modelo en un único archivo que nada actualice sin su conocimiento. Ejecute Ollama cuando necesite gestionar modelos y actualizarlos con un solo comando, porque descargar modelos por nombre, mantener varios en disco y descargar de memoria los que están inactivos son tareas que, de otro modo, tendría que automatizar mediante scripts. Ambos exponen una API compatible con OpenAI, por lo que el código del cliente no cambia si cambia más adelante.
¿Qué versión de llama.cpp debo fijar?
Cualquier tag que haya compilado y probado realmente. llama.cpp crea un tag para casi cada merge y los nombres son números de compilación, como b10488, que era el más reciente el 18 August 2026. No existe una rama estable independiente, por lo que la versión «actual» cambia varias veces al día. Clone el repositorio con --branch <tag>, instale el binario con un nombre que contenga ese tag y apunte un enlace simbólico a él, de modo que la actualización y la reversión requieran un solo comando cada una.
¿Cuánta RAM necesita llama-server?
Parta del tamaño del archivo GGUF y añada la caché KV, que crece con --ctx-size y con el número de slots --parallel. Las cifras publicadas no sustituyen las mediciones de su propia configuración, porque el total depende del modelo, la cuantización y el contexto permitido. Ejecute systemctl show llama-server -p MemoryCurrent mientras haya una petición en curso y use el número que muestre.
¿Por qué /health devuelve 503 con «Loading model»?
El proceso se ha iniciado, pero el archivo del modelo aún no está en memoria, por lo que el servidor responde {"error":{"code":503,"message":"Loading model","type":"unavailable_error"}}. Esto es normal después de cada reinicio y dura lo que tarde en leerse el archivo. Sólo se convierte en un problema cuando un cliente o un proxy trata ese primer 503 como un fallo definitivo. Consulte /health periódicamente hasta que devuelva {"status": "ok" }.
¿Puedo exponer llama-server directamente a Internet?
No lo vincule a 0.0.0.0 ni abra el puerto. No tiene cuentas, limitación de tasa ni un registro de peticiones que permita realizar una auditoría útil, y la única comprobación integrada es --api-key, que compara una sola cadena. Mantenga el bind predeterminado 127.0.0.1, coloque nginx delante con TLS y establezca también --api-key, de modo que un error en la configuración del proxy no deje el modelo abierto a cualquiera.