SSD Nodes Learn 🎉 VPS da $5.50/mese
Guide Matt ConnorDi Matt Connor · Aggiornato 2026-08-21

Come eseguire llama.cpp su un VPS con systemd

Compila llama-server da un tag fissato, servi modelli GGUF con API compatibile OpenAI, limita l’ascolto a localhost e gestisci la memoria con systemd.

Cosa stai creando

Eseguire il server llama.cpp su un VPS significa usare un solo 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 del lavoro riguarda la gestione operativa: fissare una versione, mantenere la porta in ascolto solo su localhost, creare un'unità systemd e decidere cosa accade quando il server esaurisce la memoria. Questa guida tratta questi aspetti. Se non hai ancora scelto tra le due opzioni più comuni, leggi prima il confronto tra Ollama e llama.cpp, perché questa guida descrive volutamente gli aspetti operativi che quel confronto non tratta.

Scegli un tag di release e annotalo

llama.cpp assegna un tag a quasi ogni merge, quindi i tag corrispondono ai numeri di build. b10488 è il più recente al 18 agosto 2026. Non esiste un branch stabile mantenuto a lungo, quindi «latest» è un riferimento variabile e la versione testata è l’unica che puoi supportare. Scegli un tag, annotalo e usa la stessa stringa nel clone, nel nome del binario e nella documentazione.

Ogni tag include anche archivi precompilati. Per una VPS x86 che esegue soltanto carichi CPU, l’archivio è llama-b10488-bin-ubuntu-x64.tar.gz; se usi una VPS ARM invece di una VPS x86, trovi accanto anche 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 | head

Elenca il contenuto dell’archivio prima di estrarlo, così puoi verificare dove verranno collocati i file. Questi binari sono collegati alla libreria C dell’immagine usata per compilarli, quindi su una distribuzione meno recente non si avviano e mostrano 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 userai questo metodo.

Compilare llama-server da un tag fissato

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 clone --depth 1 estrae quel tag e nessun altro contenuto, quindi la build non può cambiare mentre si lavora.

libssl-dev è importante perché l'opzione LLAMA_OPENSSL è attiva per impostazione predefinita. È questa opzione che consente al binario di scaricare in seguito i modelli tramite HTTPS. Senza gli header, il passaggio di configurazione fallisce.

-DBUILD_SHARED_LIBS=OFF crea un unico binario autonomo. La build predefinita colloca le librerie condivise accanto all'eseguibile. Di conseguenza, copiare solo l'eseguibile in /usr/local/bin causa 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. Di conseguenza, -j $(nproc) su un piano di piccole dimensioni può terminare con c++: fatal error: Killed signal terminated program cc1plus: l'out-of-memory killer del kernel arresta il compilatore. Ridurre il numero di processi oppure aggiungere swap per la build.

Un flag che potresti voler modificare è GGML_NATIVE. Per impostazione predefinita è attivo, quindi il compilatore genera il codice per la CPU esatta utilizzata durante la build. È la scelta corretta quando compili sulla macchina che eseguirà il binario. Se compili una volta e copi il binario su un host diverso, aggiungi -DGGML_NATIVE=OFF. In caso contrario, un binario che usa istruzioni non disponibili 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 di 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 permette di eseguire un aggiornamento con un ln -sfn e un riavvio. Il rollback usa lo stesso comando, sostituendo il numero con quello precedente.

Scaricare un modello GGUF e controllare 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 archiviati i pesi: Q4_K_M è una combinazione a 4 bit, Q8_0 è a 8 bit e f16 è il file non quantizzato a precisione dimezzata.

Creare 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 /srv

Il 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 8080

LLAMA_CACHE imposta la directory di download. Senza questa opzione, il file viene salvato in ~/.cache/llama.cpp dell'account che ha eseguito il comando. È la posizione sbagliata per un servizio di cui si sta per rendere non leggibile la directory home. Eseguire ls -lh /srv/models subito dopo, perché il nome del file nella cache deriva dal nome del repository e non dal nome file semplice.

Per un servizio, scaricare il modello in un percorso scelto, in modo che il file unit abbia 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.gguf

Lo spazio su disco è il primo limite che si incontra. Queste sono le dimensioni pubblicate di due modelli, verificate il 18 August 2026.

ChartGGUF file size on disk, published figures, 18 August 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 del modello 1B è di 0.81 GB. Lo stesso modello senza quantizzazione occupa 2.01 GB, quindi la scelta del formato porta il valore a più del doppio. Un modello 20B in MXFP4 occupa 12.11 GB. Non entra nei dischi di molti piani entry-level e, dopo il download, deve comunque essere caricato in memoria.

Controllare df -h prima di ogni download. Se il filesystem root si riempie durante un trasferimento di 12 GB, tutti gli altri processi che devono scrivere si interrompono, incluso il journal.

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-webui

In una seconda sessione, chiedi al server se è pronto.

curl -s http://127.0.0.1:8080/health

Durante il caricamento del file ricevi HTTP 503 e 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 è operativo. 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 cos’altro è disponibile sulla porta

POST /v1/chat/completions, POST /v1/completions e POST /v1/embeddings sono gli endpoint compatibili con OpenAI; GET /v1/models restituisce 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 altrui come riferimento per il tuo piano. La velocità dell’inferenza sulla CPU dipende dal numero di core, dalla larghezza di banda della memoria e dagli altri utenti con cui condividi l’host. Misura i token al secondo sul tuo server e considera quel risultato il riferimento corretto. Il tempo CPU sottratto da un vicino rumoroso si manifesta come una velocità di generazione che varia 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 si modifica. Lascialo invariato. In llama-server non esistono un modello di utenti, un 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 del modello 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. Con il buffering attivo, nginx trattiene gli eventi inviati dal server (SSE) finché la risposta non termina; il client resta quindi in attesa senza ricevere dati e poi riceve l'intera risposta in una sola volta. 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

Scrivi /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.target

Le 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 prevale sulla variabile corrispondente. In questo modo hai un unico punto in cui modificare la dimensione del contesto e ExecStart resta abbastanza breve da poter essere letto rapidamente.

ProtectSystem=strict rende l’intero filesystem di sola lettura per questa unità. È corretto, perché il server legge soltanto il modello. Aggiungi ReadWritePaths=/srv/models se vuoi che il servizio scarichi i modelli con -hf. ProtectHome=yes nasconde /home e /root. Questo è il secondo motivo per conservare i modelli in /srv: quando ProtectHome è attivo, il percorso predefinito ~/.cache/llama.cpp 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-pager

enable --now è la parte che molti saltano. Senza enable, il server non sarà più disponibile dopo il riavvio successivo. Se vuoi pianificare attività correlate al servizio, ad esempio un controllo notturno per verificare la disponibilità di una nuova release, un servizio systemd con timer è il meccanismo da usare.

Decidi cosa deve accadere in caso di OOM prima che si verifichi

L’utilizzo della memoria ha due componenti, che si comportano in modo diverso quando viene applicato 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 leggerle nuovamente dal disco. La cache KV, ovvero lo stato per token che il server conserva per ogni conversazione attiva, risiede invece nella memoria anonima. Non può essere rilasciata, quindi è questa la memoria che causa 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 alla finestra di contesto con cui il modello è stato addestrato e, nei modelli moderni con contesto esteso, alloca una cache KV molto grande all’avvio. Il servizio termina quindi prima di gestire una sola richiesta. --parallel moltiplica lo stesso costo, perché ogni slot conserva il proprio stato della conversazione; lascialo quindi a 1 finché non hai la necessità di usare 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 cancella 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 dei processi con systemd descrive queste direttive in maggiore dettaglio.

Evita lo swap per questo carico di lavoro. Un modello spostato 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.

Dove Ollama è la scelta migliore

Questo è un punto decisionale. Scegli llama-server quando vuoi un unico processo con i flag impostati da te, una build bloccata a una versione specifica e un file scelto da te, senza modifiche impreviste perché non è in esecuzione nient’altro.

Scegli Ollama quando vuoi gestire i modelli: scaricarli in base al nome, mantenerne diversi sul disco, rimuovere dalla memoria quello inattivo e aggiornarli con un singolo comando invece di eseguire una nuova build. Sono attività reali che altrimenti dovresti automatizzare con uno script. Eseguire Ollama su un VPS significa svolgere lo stesso compito effettuando la scelta opposta. Entrambe le soluzioni espongono un’API compatibile con OpenAI, quindi il codice client continua a funzionare in entrambi i sensi del passaggio.

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-server

Il 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 vengono rinominati: --mlock e --no-mmap sono già deprecati a favore di --load-mode, e un file 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 aver copiato il binario in un'altra posizione. La build predefinita produce anche 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 del sistema in esecuzione. 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 swap per la build e rimuoverla 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 in locale.

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 caricato in memoria.

Le richieste rimangono in attesa e restituiscono quindi 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à si riavvia ripetutamente 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 esatti e mantenere un modello in un unico file che nessun processo aggiorna automaticamente. 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 rimuovere quelli inattivi è un lavoro che altrimenti dovresti automatizzare con uno script. Entrambi espongono un'API compatibile con OpenAI, quindi il codice client non cambia se in seguito passi da una soluzione all'altra.

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 August 2026. Non esiste un ramo stable separato, quindi la versione "current" cambia più volte al giorno. Clona il repository con --branch <tag>, installa il binario usando un nome che contiene quel tag e punta a quel file con un symlink, in modo che aggiornamento e rollback richiedano un solo comando ciascuno.

Quanta RAM richiede llama-server?

Parti dalla dimensione del file GGUF, quindi aggiungi la cache KV, che aumenta 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 sufficientemente completo per un audit, e l'unico controllo integrato è --api-key, che confronta una singola stringa. Mantieni il bind predefinito 127.0.0.1, configura nginx davanti al server con TLS e imposta anche --api-key, così un errore nella configurazione del proxy non lascia il modello accessibile a chiunque.