Installer llama.cpp sur un VPS avec systemd
Compilez llama-server depuis un tag figé, servez un modèle GGUF via l’API compatible OpenAI, puis sécurisez localhost et la mémoire avec systemd.
Ce que vous allez mettre en place
Exécuter le serveur llama.cpp sur un VPS revient à utiliser un seul binaire, llama-server, qui charge un fichier de modèle GGUF et répond aux requêtes HTTP via une API compatible avec OpenAI. Dirigez n’importe quel client OpenAI vers http://127.0.0.1:8080/v1 : il fonctionnera. L’installation est la partie la plus simple.
Le reste relève de l’exploitation : figer une version, laisser le port accessible uniquement sur localhost, créer une unité systemd et décider du comportement lorsque le serveur manque de mémoire. C’est ce que couvre ce guide. Si vous n’avez pas encore choisi entre les deux options évidentes, commencez par lire la comparaison entre Ollama et llama.cpp, car ce guide explique précisément la mise en œuvre que cette comparaison laisse volontairement de côté.
Choisissez un tag de release et notez-le
llama.cpp associe un tag de release à presque chaque merge. Les tags correspondent donc aux numéros de build. b10488 est le plus récent au 18 août 2026. Il n’existe pas de branche stable maintenue sur le long terme. « latest » désigne donc une version qui évolue, et la version que vous avez testée est la seule que vous pouvez prendre en charge. Choisissez un tag, notez-le et utilisez exactement cette chaîne dans votre clone, dans le nom de votre binaire et dans vos notes.
Chaque tag fournit aussi des archives précompilées. Pour un VPS x86 sans prise en charge du CPU uniquement, il s’agit de llama-b10488-bin-ubuntu-x64.tar.gz. Une archive arm64 se trouve à côté si vous utilisez un VPS ARM plutôt qu’un VPS x86.
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 | headListez l’archive avant de l’extraire afin de connaître l’emplacement des fichiers. Ces binaires sont liés à la bibliothèque C de l’image qui les a compilés. Sur une distribution plus ancienne, ils échouent donc au démarrage avec une erreur indiquant une version de GLIBC_ qui n’est pas installée. Compiler depuis les sources prend quelques minutes sur un petit VPS et élimine toute cette catégorie de problèmes. C’est donc la méthode utilisée ci-dessous.
Compiler llama-server à partir d’un tag épinglé
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 sur un --depth 1 clone ce tag et rien d’autre. La build ne peut donc pas dériver pendant que vous travaillez.
libssl-dev est nécessaire, car l’option LLAMA_OPENSSL est activée par défaut. Elle permet au binaire de télécharger ensuite des modèles via HTTPS. Sans les headers, l’étape de configuration échoue.
-DBUILD_SHARED_LIBS=OFF produit un binaire autonome. La build par défaut place les bibliothèques partagées à côté de l’exécutable. Copier uniquement l’exécutable vers /usr/local/bin échoue donc avec error while loading shared libraries: libllama.so.
-t llama-server ne compile que la cible du serveur. La build par défaut compile aussi les autres outils et les tests. Sur un VPS à deux cœurs, cela prend plusieurs minutes supplémentaires pour des fichiers que vous n’exécuterez jamais.
-j 2 est intentionnel. Chaque tâche de compilation parallèle conserve son propre jeu de travail. Ainsi, -j $(nproc) sur une petite offre se termine par c++: fatal error: Killed signal terminated program cc1plus : le kernel OOM killer arrête le compilateur. Réduisez le nombre de tâches ou ajoutez du swap pour la build.
Vous pouvez modifier un flag : GGML_NATIVE est activé par défaut. Le compilateur cible donc exactement le CPU qui effectue la build. C’est ce que vous voulez lorsque vous compilez sur la machine qui exécutera le binaire. Si vous compilez une seule fois puis copiez le binaire vers un autre hôte, ajoutez -DGGML_NATIVE=OFF, car un binaire utilisant des instructions absentes de l’autre CPU s’arrête avec Illegal instruction (core dumped) lors de la première inférence.
Installez-le sous un nom qui contient le 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 affiche le numéro de build et le commit. Ils doivent correspondre au tag extrait. Sinon, vous avez compilé autre chose. Conserver le numéro dans le nom du fichier et pointer un lien symbolique vers celui-ci permet d’effectuer une mise à niveau avec un ln -sfn et un redémarrage. Un rollback utilise la même commande avec l’ancien numéro.
Téléchargez un modèle GGUF et vérifiez d’abord l’espace disque
GGUF est le format à fichier unique chargé par llama.cpp. Un seul fichier contient les poids, le tokenizer et les métadonnées. Il n’y a donc rien d’autre à installer. Le suffixe du nom de fichier indique la quantification, c’est-à-dire la précision avec laquelle les poids sont stockés : Q4_K_M est un mélange en 4 bits, Q8_0 utilise 8 bits et f16 est le fichier non quantifié en demi-précision.
Créez un compte de service et un répertoire pour le modèle avant tout téléchargement.
sudo useradd --system --home /srv/llama --create-home --shell /usr/sbin/nologin llama
sudo install -d -o llama -g llama /srv/models
df -h /srvLe serveur peut télécharger lui-même un modèle avec -hf. C’est le moyen le plus rapide de vérifier que votre build fonctionne.
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 définit le répertoire de téléchargement. Sans cette option, le fichier est placé dans ~/.cache/llama.cpp sous le compte qui a exécuté la commande. Ce n’est pas le bon emplacement pour un service dont vous vous apprêtez à rendre le répertoire personnel illisible. Exécutez ensuite ls -lh /srv/models, car le nom du fichier en cache est dérivé du nom du dépôt et non du nom de fichier simple.
Pour un service, téléchargez le modèle vers le chemin que vous avez choisi. Le fichier d’unité pourra ainsi pointer vers un emplacement stable.
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.ggufL’espace disque est la première limite rencontrée. Voici les tailles de fichiers publiées pour deux modèles, vérifiées le 18 août 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
}
]Le fichier en 4 bits du modèle 1B fait 0.81 Go. Le même modèle sans quantification fait 2.01 Go. Le choix du format fait donc varier la taille de plus d’un facteur de deux. Un modèle 20B en MXFP4 fait 12.11 Go. Cette taille dépasse celle du disque de nombreuses offres d’entrée de gamme, et le fichier doit ensuite être chargé en mémoire.
Vérifiez df -h avant chaque téléchargement. Un système de fichiers racine qui se remplit pendant un transfert de 12 Go empêche tout ce qui doit encore écrire, notamment le journal.
Exécutez-le une fois manuellement, puis vérifiez son état
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-webuiDans une seconde session, demandez au serveur s’il est prêt.
curl -s http://127.0.0.1:8080/healthPendant le chargement du fichier, vous obtenez une réponse HTTP 503 avec ce corps :
{"error":{"code":503,"message":"Loading model","type":"unavailable_error"}}Lorsqu’il est prêt, le corps est {"status": "ok" }. Envoyez ensuite une requête réelle.
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 objet JSON contenant un tableau choices indique que le serveur fonctionne. Le champ model est présent parce que les clients OpenAI en envoient toujours un. Ce serveur ne charge qu’un seul modèle. Sa valeur ne sert donc pas à sélectionner un modèle.
L’API compatible avec OpenAI et les autres services disponibles sur le port
POST /v1/chat/completions, POST /v1/completions et POST /v1/embeddings sont les routes compatibles avec OpenAI, et GET /v1/models indique le modèle chargé. GET /health correspond au contrôle de disponibilité présenté plus haut, GET /props renvoie les paramètres actuels du serveur et GET /metrics expose les compteurs Prometheus lorsque vous démarrez avec --metrics.
N’importe quel SDK OpenAI fonctionne après avoir défini l’URL de base sur http://127.0.0.1:8080/v1 et fourni une chaîne non vide pour la clé API. Cette clé n’est vérifiée que si vous définissez vous-même --api-key.
Ne prenez pas le débit annoncé par quelqu’un d’autre comme référence pour votre propre plan. La vitesse d’inférence sur CPU dépend du nombre de cœurs, de la bande passante mémoire et des autres utilisateurs avec lesquels vous partagez l’hôte. Mesurez donc le nombre de tokens par seconde sur votre propre machine et considérez ce résultat comme la référence. Le vol de temps CPU par un voisin bruyant se manifeste ici par une vitesse de génération qui varie d’une heure à l’autre.
Conservez-le sur 127.0.0.1 et placez un proxy devant
--host utilise déjà 127.0.0.1 par défaut. Le serveur est donc inaccessible depuis l’extérieur tant que vous ne modifiez pas ce paramètre. Laissez-le ainsi. llama-server ne fournit ni modèle utilisateur, ni limitation de débit, ni journal d’audit exploitable. Son seul mécanisme de contrôle intégré est --api-key, qui compare une chaîne. Un port d’inférence ouvert fournit gratuitement des ressources de calcul à quiconque le découvre. La même erreur avec Ollama présente la même structure : sécuriser une API de modèle auto-hébergée s’applique ici à l’identique.
Terminez TLS (transport layer security) dans nginx, puis faites suivre les requêtes vers le port 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 est nécessaire pour le streaming. Lorsque la mise en tampon est activée, nginx conserve les événements envoyés par le serveur (SSE) jusqu’à la fin de la réponse. Le client reste donc sans réponse, puis reçoit toute la réponse en une seule fois. proxy_read_timeout 600s couvre les générations longues, car la valeur par défaut de 60 secondes transforme une réponse lente en 504 Gateway Time-out. Obtenez le certificat avec Certbot et Let's Encrypt avec nginx.
L’unité systemd
Écrivez /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.targetLes paramètres se trouvent dans les lignes Environment=, car llama-server lit les variables LLAMA_ARG_* pour la plupart des options, et un argument de ligne de commande remplace la variable correspondante. Vous disposez ainsi d’un emplacement unique pour modifier la taille du contexte, et ExecStart reste suffisamment court pour être lu d’un coup d’œil.
ProtectSystem=strict rend l’ensemble du système de fichiers accessible en lecture seule pour cette unité, ce qui convient puisque le serveur lit uniquement le modèle. Ajoutez ReadWritePaths=/srv/models si vous voulez que le service télécharge lui-même les modèles avec -hf. ProtectHome=yes masque /home et /root. C’est la deuxième raison de conserver les modèles dans /srv : lorsque ProtectHome est activé, le chemin ~/.cache/llama.cpp par défaut n’est pas du tout visible par le processus.
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 est la moitié que beaucoup ignorent. Sans enable, le serveur disparaît au prochain redémarrage. Si vous voulez planifier des tâches autour du service, par exemple vérifier chaque nuit la disponibilité d’une nouvelle release, un service systemd avec un timer est le mécanisme prévu à cet effet.
Décidez du comportement en cas d’OOM avant qu’il ne survienne
L’utilisation de la mémoire comporte deux parties, qui se comportent différemment lorsqu’une limite est appliquée. Par défaut, le fichier du modèle est memory-mapped : ses pages sont donc adossées à un fichier. Le kernel peut les libérer, puis les relire depuis le disque. Le cache KV, qui contient l’état par token conservé par le serveur pour chaque conversation active, utilise de la mémoire anonyme. Il ne peut pas être libéré. C’est donc lui qui provoque le kill du processus.
C’est pourquoi les deux limites de l’unité ont des rôles différents. MemoryHigh=3G est une limite souple : au-delà, le kernel soumet le cgroup à une pression de reclaim. Les pages mappées du modèle sont alors évincées, puis relues depuis le disque au token suivant. Le service continue de fonctionner, mais il ralentit. MemoryMax=3500M est une limite stricte : au-delà, le processus est tué, et le journal l’indique clairement.
llama-server.service: A process of this unit has been killed by the OOM killer.Définissez vous-même --ctx-size. La valeur par défaut est 0. Elle correspond à la longueur de contexte avec laquelle le modèle a été entraîné. Sur un modèle moderne avec un long contexte, cette valeur alloue un cache KV très volumineux au démarrage. Le service s’arrête alors avant de traiter une seule requête. --parallel multiplie le même coût, car chaque slot conserve son propre état de conversation. Laissez donc cette valeur à 1 jusqu’à ce que la concurrence soit nécessaire.
Avec Restart=on-failure, un service tué redémarre. S’il est tué à chaque démarrage, systemd abandonne et systemctl status affiche start request repeated too quickly. C’est le comportement attendu : une boucle de redémarrage qui relit un fichier de 12 GB toutes les cinq secondes est pire qu’une interruption de service. Corrigez la limite ou la taille du contexte, puis réinitialisez l’état avec sudo systemctl reset-failed llama-server.
Surveillez la valeur réelle avec systemctl show llama-server -p MemoryCurrent pendant le traitement d’une requête. La page Limiter la mémoire et le CPU d’un processus avec systemd détaille davantage ces directives.
Évitez le swap pour cette charge de travail. Un modèle sorti vers le swap transforme chaque token en lectures aléatoires sur le disque. Le memory mapping du fichier du modèle produit un effet similaire avec moins de conséquences, car le kernel lit directement dans le fichier les pages dont il a besoin.
Dans quels cas Ollama est le meilleur choix
C’est un choix d’architecture. Choisissez llama-server si vous voulez un seul processus avec les options que vous définissez, une version figée et le fichier que vous avez choisi. Rien ne change en arrière-plan, car aucun autre processus ne s’exécute.
Choisissez Ollama si vous voulez gérer les modèles : télécharger les modèles par leur nom, en conserver plusieurs sur le disque, décharger un modèle inactif et effectuer une mise à niveau avec une seule commande au lieu de reconstruire l’installation. Ce sont des tâches réelles que vous devriez sinon automatiser vous-même. Exécuter Ollama sur un VPS revient à faire la même chose sur un VPS, avec un compromis différent. Les deux exposent une API compatible avec OpenAI. Le code client reste donc compatible, quel que soit le sens de la migration.
Mise à niveau d’une build épinglée
Remplacez bNNNNN par le tag vers lequel vous migrez.
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-serverL’ancien binaire reste présent sur le disque. Pour effectuer un rollback, il suffit de revenir à llama-server-b10488 avec une seule ln -sfn, puis de redémarrer le service. Consultez les notes de version avant la migration. Les fichiers GGUF sont versionnés et les anciennes versions continuent de se charger, mais certains flags sont renommés : --mlock et --no-mmap sont déjà obsolètes au profit de --load-mode. Un fichier d’unité qui transmet un flag supprimé échoue au démarrage avec un message indiquant que l’argument n’est pas reconnu.
Modes de défaillance et messages affichés
error while loading shared libraries: libllama.so après avoir copié le binaire ailleurs. La compilation par défaut produit des bibliothèques partagées à côté du binaire. Recompilez avec -DBUILD_SHARED_LIBS=OFF ou copiez tout le répertoire build/bin.
Illegal instruction (core dumped) au démarrage ou lors de la première requête. Le binaire a été compilé avec GGML_NATIVE activé, pour un CPU différent de celui qui l’exécute. Recompilez sur cette machine ou configurez avec -DGGML_NATIVE=OFF.
c++: fatal error: Killed signal terminated program cc1plus pendant la compilation. Le compilateur a été tué parce qu’il utilisait trop de mémoire. Réduisez -j ou ajoutez du swap pour la compilation, puis supprimez-le.
curl: (7) Failed to connect ... Connection refused depuis votre ordinateur portable. C’est normal : le serveur écoute sur l’adresse loopback du VPS. Testez depuis le VPS lui-même ou ouvrez un tunnel avec ssh -L 8080:127.0.0.1:8080 user@your-vps, puis utilisez http://127.0.0.1:8080 localement.
HTTP 503 avec "message":"Loading model" pendant les premières secondes ou minutes après un redémarrage. La lecture d’un fichier de plusieurs gigaoctets prend du temps. systemd indique que l’unité est active dès le démarrage du processus, bien avant le chargement du modèle en mémoire.
Les requêtes restent bloquées, puis renvoient 504 Gateway Time-out. Le proxy a abandonné avant la fin du chargement du modèle. Augmentez proxy_read_timeout et désactivez proxy_buffering afin que les tokens soient transmis au client au fur et à mesure de leur production.
L’unité redémarre en boucle, puis s’arrête avec start request repeated too quickly. Un processus la tue à chaque démarrage. Vérifiez journalctl -u llama-server pour trouver la ligne de l’OOM killer, puis réduisez --ctx-size, réduisez --parallel ou augmentez MemoryMax.
FAQ
Dois-je exécuter le serveur de llama.cpp ou Ollama sur mon VPS ?
Exécutez llama-server si vous voulez utiliser une build précise, transmettre des flags exacts et conserver un modèle dans un fichier unique que rien ne met à jour à votre insu. Utilisez Ollama si vous voulez gérer les modèles et les mettre à niveau avec une seule commande, car télécharger les modèles par leur nom, en conserver plusieurs sur le disque et décharger ceux qui sont inactifs sont des tâches que vous devriez sinon automatiser vous-même. Les deux exposent une API compatible avec OpenAI. Le code client ne change donc pas si vous migrez plus tard.
Quelle version de llama.cpp dois-je figer ?
Utilisez n’importe quel tag que vous avez réellement compilé et testé. llama.cpp crée un tag pour presque chaque merge. Les noms sont des numéros de build, comme b10488, qui était le plus récent le 18 August 2026. Il n’existe pas de branche stable distincte. La version « actuelle » change donc plusieurs fois par jour. Clonez le dépôt avec --branch <tag>, installez le binaire sous un nom contenant ce tag, puis pointez un lien symbolique vers ce fichier. Vous pourrez ainsi effectuer une mise à niveau ou un retour en arrière avec une seule commande.
De combien de RAM llama-server a-t-il besoin ?
Commencez par la taille du fichier GGUF, puis ajoutez le cache KV, qui augmente avec --ctx-size et avec le nombre de slots --parallel. Les chiffres publiés ne remplacent pas une mesure sur votre propre configuration, car le total dépend du modèle, de la quantisation et de la taille de contexte autorisée. Exécutez systemctl show llama-server -p MemoryCurrent pendant qu’une requête est en cours et utilisez la valeur affichée.
Pourquoi /health renvoie-t-il 503 avec « Loading model » ?
Le processus a démarré, mais le fichier du modèle n’est pas encore chargé en mémoire. Le serveur renvoie donc {"error":{"code":503,"message":"Loading model","type":"unavailable_error"}}. Ce comportement est normal après chaque redémarrage et dure le temps nécessaire à la lecture du fichier. Il devient problématique uniquement lorsqu’un client ou un proxy traite ce premier 503 comme un échec définitif. Interrogez /health jusqu’à ce qu’il renvoie {"status": "ok" }.
Puis-je exposer llama-server directement sur Internet ?
Ne le liez pas à 0.0.0.0 et n’ouvrez pas le port. Il ne dispose ni de comptes, ni de limitation de débit, ni de journal des requêtes suffisamment exploitable pour un audit. Le seul contrôle intégré est --api-key, qui compare une seule chaîne. Conservez la liaison 127.0.0.1 par défaut, placez nginx devant avec TLS et définissez également --api-key. Ainsi, une erreur de configuration du proxy ne laissera pas le modèle accessible à tout le monde.