SSD Nodes Learn Hosting plans →
Guides Matt ConnorPar Matt Connor · Mis à jour le 2026-08-23

Installer llama.cpp server sur un VPS avec systemd

Compilez llama-server depuis un tag figé, servez un modèle GGUF via l’API OpenAI, limitez l’accès à localhost et gérez 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 unique 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 et il fonctionnera. L’installation est la partie la plus simple.

Le reste concerne l’exploitation : figer une version, garder 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, lisez d’abord 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 version et notez-le

llama.cpp associe un tag de version à presque chaque fusion. 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 à long terme. « Latest » désigne donc une cible qui évolue, et la seule version que vous pouvez prendre en charge est celle que vous avez testée. Choisissez un tag, notez-le et utilisez exactement cette chaîne dans votre clonage, dans le nom de votre binaire et dans vos notes.

Chaque tag fournit également des archives précompilées. Pour un VPS x86 utilisant uniquement le CPU, 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 | head

Listez l’archive avant de l’extraire afin de savoir où les fichiers seront placés. Ces binaires sont liés à la bibliothèque C de l’image qui les a compilés. Sur une distribution plus ancienne, ils échouent au démarrage avec une erreur mentionnant une version de GLIBC_ qui n’est pas installée. La compilation 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 clone --depth 1 extrait ce tag et rien d’autre. La compilation ne peut donc pas dériver pendant vos modifications.

libssl-dev est nécessaire, car l’option LLAMA_OPENSSL est activée par défaut. Elle permet ensuite au binaire de télécharger des modèles via HTTPS. Sans ces headers, l’étape de configuration échoue.

-DBUILD_SHARED_LIBS=OFF produit un binaire autonome. La compilation 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 compile uniquement la cible du serveur. La compilation par défaut construit aussi les autres outils et les tests. Sur un VPS à deux cœurs, cela ajoute plusieurs minutes pour des fichiers que vous n’exécuterez jamais.

-j 2 est volontaire. Chaque tâche de compilation parallèle utilise son propre jeu de travail. Ainsi, -j $(nproc) sur une petite offre finit par c++: fatal error: Killed signal terminated program cc1plus : le kernel out-of-memory killer arrête le compilateur. Réduisez le nombre de tâches ou ajoutez du swap pour la compilation.

Vous pouvez modifier un flag : GGML_NATIVE est activé par défaut. Le compilateur cible donc précisément le CPU qui effectue la compilation. C’est le comportement souhaité si vous compilez sur la machine qui exécutera le binaire. Si vous compilez une fois puis copiez le binaire vers un autre hôte, ajoutez -DGGML_NATIVE=OFF. Sinon, un binaire utilisant des instructions absentes de l’autre CPU s’arrête avec Illegal instruction (core dumped) dès 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. Si ce n’est pas le cas, vous avez compilé autre chose. Conserver le numéro dans le nom du fichier et pointer un symlink 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.

Récupérez 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 utilisée pour stocker les poids : Q4_K_M correspond à un mélange en 4 bits, Q8_0 à du 8 bits et f16 au 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 /srv

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

LLAMA_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 allez bientôt rendre le répertoire personnel inaccessible. 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 un chemin que vous avez choisi. Le fichier d’unité pourra ainsi utiliser un chemin 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.gguf

L’espace disque est généralement la première limite rencontrée. Voici les tailles publiées de deux modèles, vérifiées le 18 août 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
  }
]

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 deux. Un modèle 20B en MXFP4 fait 12.11 Go. Il ne tient pas sur le disque de nombreuses offres d’entrée de gamme et doit ensuite être chargé en mémoire. Si vous envisagez une famille précise, le même calcul de taille pour GLM montre à quelle vitesse le modèle principal devient trop coûteux pour un VPS, alors qu’un modèle plus petit reste adapté.

Vérifiez df -h avant chaque téléchargement. Un système de fichiers racine qui se remplit pendant le transfert de 12 Go empêche toute autre écriture, notamment celle du journal.

Exécutez-le une fois manuellement, puis vérifiez-le

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

Dans une deuxième session, demandez au serveur s’il est prêt.

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

Pendant 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 l’envoient toujours. Ce serveur n’a chargé qu’un seul modèle ; cette valeur ne sert donc pas à en sélectionner un.

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 si vous définissez l’URL de base sur http://127.0.0.1:8080/v1 et transmettez une chaîne non vide comme clé d’API. Cette clé n’est vérifiée qu’après avoir défini vous-même --api-key.

Ne prenez pas le débit annoncé par quelqu’un d’autre comme référence pour votre propre configuration. 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 temps volé 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 cette valeur. Ne la modifiez pas. llama-server ne fournit ni modèle utilisateur, ni limite de débit, ni journal d’audit exploitable. Son seul mécanisme de contrôle intégré est --api-key, qui compare une seule chaîne. Un port d’inférence ouvert fournit gratuitement de la capacité 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 requis pour le streaming. Lorsque la mise en tampon est activée, nginx conserve les server-sent events (SSE) jusqu’à la fin de la réponse. Le client attend donc sans recevoir de données, puis obtient 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 sur 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.target

Les paramètres se trouvent dans des 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 ne fait que lire 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-pager

enable --now est l’étape 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 associé à un timer est le mécanisme prévu à cet effet.

Décidez du comportement en cas d’OOM avant que cela se produise

L’utilisation de la mémoire comporte deux parties, qui ne réagissent pas de la même façon à une limite. 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. La KV cache, qui contient l’état par token conservé par le serveur pour chaque conversation active, utilise de la mémoire anonyme. Elle ne peut pas être libérée. C’est donc elle qui entraîne 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 au contexte avec lequel le modèle a été entraîné. Sur un modèle moderne doté d’un long contexte, cette valeur alloue une KV cache très volumineuse au démarrage. Le service meurt 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 tant que vous n’avez pas besoin de concurrence.

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 effacez l’état avec sudo systemctl reset-failed llama-server.

Observez la valeur réelle avec systemctl show llama-server -p MemoryCurrent pendant le traitement d’une requête. Limiter la mémoire et le CPU d’un processus avec systemd présente ces directives plus en détail.

Évitez le swap pour cette charge de travail. Un modèle sorti vers le swap transforme chaque token en lectures disque à des offsets aléatoires. 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.

Quand Ollama constitue le meilleur choix

Vous devez faire un choix. Choisissez llama-server si vous voulez un seul processus avec les options que vous définissez, une version de build que vous avez figée et un 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 : les télécharger 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 relancer un build. Ce sont des opérations que vous devriez sinon automatiser vous-même avec des scripts. Exécuter Ollama sur un VPS revient au même, mais avec un compromis différent. Les deux solutions fournissent une API compatible avec OpenAI. Le code client reste donc compatible dans les deux sens.

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

L’ancien binaire reste sur le disque. Pour effectuer un rollback, il suffit donc de revenir à llama-server-b10488 avec une seule ln -sfn, puis de redémarrer. Lisez 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 un argument non 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 de la machine 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é arrêté 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 laptop. 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 considère l’unité comme active dès le lancement 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 l’arrête à chaque démarrage. Vérifiez journalctl -u llama-server pour trouver la ligne indiquant l’intervention 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 que rien ne met à jour à votre insu. Utilisez Ollama si vous voulez gérer les modèles et effectuer les mises à 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 basculez 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 applique un tag à presque chaque merge, avec des noms qui sont des numéros de build tels que 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 et faites pointer un lien symbolique vers celui-ci. La mise à niveau et le retour arrière se font ainsi chacun 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 la quantité totale dépend du modèle, de la quantification et du contexte que vous autorisez. 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"}}. C’est normal après chaque redémarrage et cela dure le temps nécessaire à la lecture du fichier. Cela devient un problème 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 directement llama-server 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 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 dans la configuration du proxy ne laissera pas le modèle accessible à tout le monde.