Come eseguire llama.cpp su un VPS con systemd
Compila llama-server da un tag fissato, servi modelli GGUF tramite API compatibile con OpenAI, limita localhost e gestisci la memoria con systemd.
Cosa stai configurando
Eseguire il server llama.cpp su un VPS significa usare un unico binario, llama-server, che carica un singolo file modello GGUF e risponde alle richieste HTTP tramite un'API compatibile con OpenAI. È sufficiente configurare qualsiasi client OpenAI per usare http://127.0.0.1:8080/v1. L'installazione è la parte più semplice.
Il resto riguarda la gestione operativa: fissare una versione, mantenere la porta accessibile solo da localhost, creare un'unità systemd e decidere cosa deve accadere quando il server esaurisce la memoria. È questo l'argomento della guida. Se non hai ancora scelto tra le due opzioni principali, leggi prima il confronto tra Ollama e llama.cpp, perché questa guida descrive la procedura che quel confronto lascia volutamente in secondo piano.
Scegliere un tag di release e annotarlo
llama.cpp assegna un tag a quasi ogni merge, quindi i tag corrispondono ai numeri di build. b10488 è il più recente al 18 August 2026. Non esiste un ramo stabile a lungo termine. Di conseguenza, "latest" è un riferimento variabile e l'unica versione supportabile è quella che avete testato. Scegliete un tag, annotatelo e usate la stessa stringa nel clone, nel nome del binario e nelle note.
Ogni tag include anche archivi precompilati. Per una VPS x86 con solo CPU, l'archivio è llama-b10488-bin-ubuntu-x64.tar.gz. Se usate una VPS ARM invece di una VPS x86, accanto trovate un archivio arm64.
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 | headElencate l'archivio prima di estrarlo, per verificare dove verranno collocati i file. Questi binari sono collegati alla libreria C dell'immagine usata per compilarli. Su una distribuzione più vecchia, l'avvio non riesce e viene visualizzato un errore che indica una versione di GLIBC_ non installata. La compilazione dai sorgenti richiede pochi minuti su una VPS di piccole dimensioni ed elimina questa intera classe di problemi. Per questo, nei passaggi seguenti useremo questo metodo.
Compilare llama-server da un tag bloccato
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 su un --depth 1 clone estrae quel tag e nient’altro, quindi la build non può cambiare mentre si lavora.
libssl-dev è importante perché l’opzione LLAMA_OPENSSL è attiva per impostazione predefinita. È ciò che consente al binario di scaricare in seguito i modelli tramite HTTPS. Senza gli header, il passaggio di configurazione non riesce.
-DBUILD_SHARED_LIBS=OFF produce un unico binario autonomo. La build predefinita colloca le librerie condivise accanto all’eseguibile, quindi copiare soltanto l’eseguibile in /usr/local/bin provoca l’errore error while loading shared libraries: libllama.so.
-t llama-server compila soltanto il target server. La build predefinita compila anche gli altri strumenti e i test. Su una VPS con due core, questo richiede diversi minuti aggiuntivi per file che non verranno mai eseguiti.
-j 2 è una scelta intenzionale. Ogni processo di compilazione parallelo mantiene il proprio working set, quindi -j $(nproc) su un piano di piccole dimensioni termina con c++: fatal error: Killed signal terminated program cc1plus: il killer OOM del kernel interrompe il compilatore. Ridurre il numero di job oppure aggiungere swap per la build.
Un flag che potresti voler modificare è GGML_NATIVE, attivo per impostazione predefinita. In questo modo il compilatore usa come target esattamente la CPU sulla quale viene eseguita la build. È la scelta corretta se compili sulla macchina che eseguirà il binario. Se compili una sola volta e copi il binario su un host diverso, aggiungi -DGGML_NATIVE=OFF, perché un binario che usa istruzioni assenti sull’altra CPU termina con Illegal instruction (core dumped) alla prima inferenza.
Installalo con un nome che includa il 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 stampa il numero della build e il commit. Devono corrispondere al tag estratto. In caso contrario, hai compilato qualcos’altro. Mantenere il numero nel nome del file e puntare a quel file con un symlink significa che un aggiornamento richiede un ln -sfn e un riavvio, mentre il rollback usa lo stesso comando con il numero precedente.
Scarica un modello GGUF e controlla prima lo spazio su disco
GGUF è il formato a file singolo caricato da llama.cpp. Un unico file contiene i pesi, il tokenizer e i metadati, quindi non è necessario installare altro. Il suffisso del nome file indica la quantizzazione, cioè la precisione con cui sono memorizzati i pesi: Q4_K_M è un formato misto a 4 bit, Q8_0 è a 8 bit e f16 è il file non quantizzato a mezza precisione.
Crea un account di servizio e una directory per il modello prima di scaricare qualsiasi file.
sudo useradd --system --home /srv/llama --create-home --shell /usr/sbin/nologin llama
sudo install -d -o llama -g llama /srv/models
df -h /srvIl server può scaricare direttamente un modello con -hf. È il modo più rapido per verificare che la build funzioni.
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 imposta la directory di download. Senza questa opzione, il file viene salvato in ~/.cache/llama.cpp sotto l'account che ha eseguito il comando, cioè nella posizione sbagliata per un servizio la cui home directory stai per rendere non accessibile. Esegui poi ls -lh /srv/models, perché il nome del file nella cache deriva dal nome del repository e non dal nome file semplice.
Per un servizio, scarica il modello in un percorso scelto da te, così il file dell'unità può usare un riferimento stabile.
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.ggufLo spazio su disco è il primo limite che si incontra. Queste sono le dimensioni pubblicate di due modelli, verificate il 18 agosto 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
}
]Il file a 4 bit per il modello 1B è di 0.81 GB. Lo stesso modello senza quantizzazione occupa 2.01 GB, quindi la scelta del formato modifica il valore di oltre un fattore due. Un modello 20B in MXFP4 occupa 12.11 GB. Questo spazio non è disponibile su molti piani entry-level e, successivamente, il modello deve essere caricato anche in memoria. Se stai valutando una famiglia specifica, lo stesso confronto delle dimensioni per GLM mostra quanto rapidamente il modello principale diventi troppo costoso per una VPS, mentre una variante più piccola rientra nelle risorse disponibili.
Controlla df -h prima di ogni download. Se il filesystem root si riempie durante un trasferimento da 12 GB, tutti gli altri processi che devono scrivere, incluso il journal, possono smettere di funzionare.
Eseguilo una volta manualmente e controllalo
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-webuiIn una seconda sessione, chiedi al server se è pronto.
curl -s http://127.0.0.1:8080/healthMentre il file viene caricato, ricevi HTTP 503 con questo corpo:
{"error":{"code":503,"message":"Loading model","type":"unavailable_error"}}Quando è pronto, il corpo è {"status": "ok" }. Invia quindi una richiesta reale.
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 oggetto JSON con un array choices indica che il server funziona. Il campo model è presente perché i client OpenAI lo inviano sempre. Questo server ha un solo modello caricato, quindi il valore non viene usato per selezionare alcun modello.
L’API compatibile con OpenAI e gli altri endpoint disponibili sulla porta
POST /v1/chat/completions, POST /v1/completions e POST /v1/embeddings sono le route compatibili con OpenAI, mentre GET /v1/models indica il modello caricato. GET /health è il controllo di disponibilità descritto sopra, GET /props restituisce le impostazioni correnti del server e GET /metrics espone i contatori Prometheus quando si avvia il server con --metrics.
Qualsiasi SDK OpenAI funziona dopo aver impostato l’URL di base su http://127.0.0.1:8080/v1 e aver passato una stringa non vuota come chiave API. La chiave non viene verificata finché non si imposta manualmente --api-key.
Non usare le dichiarazioni sulla velocità di elaborazione di altri come riferimento per il proprio piano. La velocità dell’inferenza sulla CPU dipende dal numero di core, dalla larghezza di banda della memoria e dagli altri utenti con cui si condivide l’host. Misura i token al secondo sul tuo server e considera quel risultato come il dato di riferimento. Il tempo sottratto da un vicino rumoroso si manifesta con una velocità di generazione che cambia da un’ora all’altra.
Mantienilo su 127.0.0.1 e configura un proxy davanti
--host è già impostato su 127.0.0.1 per impostazione predefinita, quindi il server non è raggiungibile dall'esterno finché non lo modifichi. Non cambiarlo. In llama-server non sono disponibili un modello di utenti, il rate limiting o un audit log utile, e l'unico controllo integrato è --api-key, che confronta una stringa. Una porta di inferenza esposta offre risorse di calcolo gratuite a chiunque la individui, e lo stesso errore commesso con Ollama ha la stessa struttura: proteggere un'API di modelli self-hosted si applica qui senza modifiche.
Termina TLS (transport layer security) in nginx e inoltra le richieste alla porta di 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 è necessario per lo streaming. Se il buffering è attivo, nginx trattiene gli eventi inviati dal server (SSE) finché la risposta non è completa; il client rimane in attesa senza ricevere dati e poi ottiene l'intera risposta in una volta sola. proxy_read_timeout 600s gestisce le generazioni lunghe, perché il valore predefinito di 60 secondi trasforma una risposta lenta in 504 Gateway Time-out. Ottieni il certificato con Certbot e Let's Encrypt su nginx.
L’unit systemd
Scrivete /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.targetLe impostazioni si trovano nelle righe Environment= perché llama-server legge le variabili LLAMA_ARG_* per la maggior parte dei flag e un argomento della riga di comando ha la precedenza sulla variabile corrispondente. In questo modo avete un unico punto in cui modificare la dimensione del contesto e mantenete ExecStart abbastanza breve da poterlo leggere rapidamente.
ProtectSystem=strict rende l’intero filesystem di sola lettura per questa unità. È adeguato perché il server legge soltanto il modello. Aggiungete ReadWritePaths=/srv/models se volete che il servizio scarichi i modelli con -hf. ProtectHome=yes nasconde /home e /root. Questo è il secondo motivo per conservare i modelli in /srv: con ProtectHome attivo, il percorso ~/.cache/llama.cpp predefinito non è visibile al processo.
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 è la parte che spesso viene omessa. Senza enable, il server non sarà più disponibile dopo il riavvio successivo. Se volete pianificare attività relative al servizio, ad esempio un controllo notturno per verificare la disponibilità di una nuova release, un servizio systemd con relativo timer è il meccanismo da usare.
Decidi cosa deve accadere in caso di OOM prima che si verifichi
L'uso della memoria ha due componenti, che si comportano in modo diverso quando sono soggette a un limite. Per impostazione predefinita, il file del modello viene mappato in memoria, quindi le relative pagine sono supportate da file: il kernel può rilasciarle e rileggerle dal disco. La cache KV, cioè lo stato per token che il server mantiene per ogni conversazione attiva, è memoria anonima. Non può essere rilasciata, quindi è la memoria che provoca la terminazione del processo.
Per questo i due limiti nell'unità svolgono funzioni diverse. MemoryHigh=3G è un limite soft: oltre questa soglia il kernel sottopone il cgroup a pressione di reclaim, quindi le pagine mappate del modello vengono espulse e rilette dal disco al token successivo. Il servizio continua a funzionare, ma diventa più lento. MemoryMax=3500M è un limite hard: oltre questa soglia il processo viene terminato e il journal lo indica chiaramente.
llama-server.service: A process of this unit has been killed by the OOM killer.Imposta personalmente --ctx-size. Il valore predefinito è 0, che corrisponde al contesto con cui il modello è stato addestrato; nei modelli moderni con contesto lungo, questo valore alloca una cache KV molto grande all'avvio. Il servizio termina quindi prima di gestire una singola richiesta. --parallel moltiplica lo stesso costo, perché ogni slot contiene il proprio stato della conversazione; lascialo quindi a 1 finché non ti serve la concorrenza.
Con Restart=on-failure un servizio terminato viene riavviato. Se viene terminato a ogni avvio, systemd rinuncia e systemctl status stampa start request repeated too quickly. È il comportamento corretto: un ciclo di riavvii che rilegge un file da 12 GB ogni cinque secondi è peggiore di un'interruzione del servizio. Correggi il limite o la dimensione del contesto, quindi azzera lo stato con sudo systemctl reset-failed llama-server.
Controlla il valore effettivo con systemctl show llama-server -p MemoryCurrent mentre è in esecuzione una richiesta. Limitare memoria e CPU del processo con systemd descrive queste direttive in maggiore dettaglio.
Evita lo swap per questo carico di lavoro. Un modello trasferito nello swap trasforma ogni token in letture dal disco a offset casuali. Mappare in memoria il file del modello produce lo stesso effetto con un impatto minore, perché il kernel legge le pagine necessarie direttamente dal file.
Quando Ollama è la scelta migliore
Questo è un punto di scelta. Scegli llama-server quando vuoi un solo processo con i flag che hai impostato, una build fissata e il file che hai scelto. Nulla cambia sotto il processo perché non è in esecuzione nient’altro.
Scegli Ollama quando vuoi la gestione dei modelli: scaricare i modelli per nome, mantenerne diversi sul disco, scaricarne dalla memoria uno inattivo e aggiornarli con un singolo comando invece di eseguire una nuova build. Sono attività reali che altrimenti dovresti gestire con script. Eseguire Ollama su un VPS svolge lo stesso compito, ma con un compromesso diverso. Entrambe le soluzioni espongono un’API compatibile con OpenAI, quindi il codice client continua a funzionare in entrambe le direzioni.
Aggiornamento di una build fissata
Sostituisci bNNNNN con il tag a cui vuoi passare.
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-serverIl vecchio binario resta sul disco, quindi per eseguire il rollback basta un ln -sfn a llama-server-b10488 e un riavvio. Leggi le note di rilascio prima di procedere. I file GGUF sono versionati e quelli precedenti continuano a essere caricati, ma i flag possono essere rinominati: --mlock e --no-mmap sono già deprecati a favore di --load-mode, mentre un file di unità che passa un flag rimosso non riesce ad avviarsi e mostra un messaggio relativo a un argomento non riconosciuto.
Modalità di errore e messaggi visualizzati
error while loading shared libraries: libllama.so dopo avere copiato il binario in un'altra posizione. La build predefinita produce anche le librerie condivise. Ricompilare con -DBUILD_SHARED_LIBS=OFF oppure copiare l'intera directory build/bin.
Illegal instruction (core dumped) all'avvio o alla prima richiesta. Il binario è stato compilato con GGML_NATIVE attivo per una CPU diversa da quella della macchina in uso. Ricompilare su questa macchina oppure configurare con -DGGML_NATIVE=OFF.
c++: fatal error: Killed signal terminated program cc1plus durante la build. Il compilatore è stato terminato perché utilizzava troppa memoria. Ridurre -j oppure aggiungere lo swap durante la build e rimuoverlo al termine.
curl: (7) Failed to connect ... Connection refused dal laptop. È il comportamento corretto: il server è in ascolto sull'indirizzo di loopback del VPS. Eseguire il test direttamente sul VPS oppure aprire un tunnel con ssh -L 8080:127.0.0.1:8080 user@your-vps e usare http://127.0.0.1:8080 localmente.
HTTP 503 con "message":"Loading model" per i primi secondi o minuti dopo un riavvio. La lettura di un file di diversi gigabyte richiede tempo e systemd segnala l'unità come attiva non appena il processo viene avviato, molto prima che il modello sia stato caricato in memoria.
Le richieste restano in attesa e poi restituiscono 504 Gateway Time-out. Il proxy ha interrotto l'attesa prima che il modello terminasse. Aumentare proxy_read_timeout e disattivare proxy_buffering in modo che i token vengano inviati al client man mano che vengono prodotti.
L'unità entra in un ciclo di riavvii e poi si arresta con start request repeated too quickly. Qualcosa la termina a ogni avvio. Controllare journalctl -u llama-server per individuare la riga relativa all'OOM killer, quindi ridurre --ctx-size, ridurre --parallel oppure aumentare MemoryMax.
FAQ
Devo eseguire il server di llama.cpp oppure Ollama sul mio VPS?
Esegui llama-server quando vuoi fissare una build precisa, passare flag precisi e mantenere un modello in un unico file che nessun processo aggiorni senza il tuo controllo. Usa Ollama quando vuoi la gestione dei modelli e gli aggiornamenti con un solo comando, perché scaricare i modelli per nome, conservarne diversi sul disco e scaricare dalla memoria quelli inattivi sono attività che altrimenti dovresti automatizzare con script. Entrambi espongono un'API compatibile con OpenAI, quindi il codice client non cambia se in seguito effettui il passaggio.
Quale versione di llama.cpp devo fissare?
Qualsiasi tag che hai effettivamente compilato e testato. llama.cpp assegna un tag a quasi ogni merge e i nomi sono numeri di build come b10488, che era il più recente il 18 agosto 2026. Non esiste un branch stable separato, quindi la versione "current" cambia diverse volte al giorno. Clona il repository con --branch <tag>, installa il binario in un file il cui nome contiene quel tag e punta a quel file con un symlink, così l'aggiornamento e il rollback richiedono un solo comando ciascuno.
Quanta RAM richiede llama-server?
Parti dalla dimensione del file GGUF, quindi aggiungi la KV cache, che cresce con --ctx-size e con il numero di slot --parallel. I valori pubblicati non sostituiscono le misurazioni sul tuo ambiente, perché il totale dipende dal modello, dalla quantizzazione e dal contesto consentito. Esegui systemctl show llama-server -p MemoryCurrent mentre è in corso una richiesta e usa il valore visualizzato.
Perché /health restituisce 503 con "Loading model"?
Il processo è stato avviato, ma il file del modello non è ancora in memoria, quindi il server risponde con {"error":{"code":503,"message":"Loading model","type":"unavailable_error"}}. È normale dopo ogni riavvio e dura per tutto il tempo necessario a leggere il file. Diventa un problema solo quando un client o un proxy considera quel primo 503 un errore definitivo. Esegui il polling di /health finché non restituisce {"status": "ok" }.
Posso esporre direttamente llama-server su Internet?
Non associarlo a 0.0.0.0 e non aprire la porta. Non dispone di account, rate limiting o un log delle richieste adeguato per l'audit; l'unico controllo integrato è --api-key, che confronta una singola stringa. Mantieni il bind predefinito 127.0.0.1, configura nginx davanti al servizio con TLS e imposta anche --api-key, così un errore nella configurazione del proxy non lascia il modello accessibile a chiunque.