VPSでllama-serverを構築・運用する方法
固定したリリースタグからllama-serverをビルドし、GGUFモデルをOpenAI互換APIで提供します。localhostへの限定、systemdのメモリ制限まで解説します。
構築するもの
VPS 上で llama.cpp server を実行すると、1 つのバイナリ llama-server で単一の GGUF モデルファイルを読み込み、OpenAI 互換 API で HTTP リクエストに応答できます。OpenAI クライアントから http://127.0.0.1:8080/v1 を指定すれば動作します。インストールは簡単です。
残りの作業は運用です。バージョンを固定し、ポートを localhost に限定し、systemd unit を作成し、サーバーのメモリが不足した場合の動作を決めます。このガイドでは、これらを扱います。まだ 2 つの明らかな選択肢のどちらにするか決めていない場合は、まずOllama と llama.cpp のトレードオフを読んでください。このガイドは、その比較で意図的に省かれている具体的な手順を説明します。
リリースタグを選び、記録します
llama.cpp では、ほぼすべてのマージに対してリリースタグが付けられるため、タグはビルド番号として扱われます。b10488 は 18 August 2026 時点で最新です。長期運用される安定ブランチはありません。そのため、「latest」は変動し続けます。サポートできるのは、テストしたバージョンだけです。タグを1つ選び、その文字列を記録します。clone、バイナリ名、メモでは、同じ文字列を使用します。
各タグには、ビルド済みアーカイブも含まれています。CPU のみを使用する x86 VPS では llama-b10488-bin-ubuntu-x64.tar.gz です。arm64 を使用している場合は、x86 ではなく ARM VPS 用のアーカイブも隣にあります。
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展開する前にアーカイブの内容を一覧表示し、ファイルの配置先を確認します。これらのバイナリは、ビルドに使用されたイメージの C ライブラリにリンクされています。そのため、古いディストリビューションでは、インストールされていない GLIBC_ バージョンを示すエラーで起動に失敗します。小規模な VPS でもソースからのビルドには数分しかかからず、この種の問題をすべて解消できます。以下では、その方法を使用します。
固定したタグから llama-server をビルドする
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 を --depth 1 の clone で実行すると、そのタグだけが checkout されるため、作業中に内容が変わりません。
libssl-dev が必要なのは、LLAMA_OPENSSL オプションがデフォルトで有効であり、後でバイナリが HTTPS 経由でモデルをダウンロードできるようにするためです。ヘッダーがないと configure の段階で失敗します。
-DBUILD_SHARED_LIBS=OFF を指定すると、依存関係を含む単独で動作するバイナリになります。デフォルトのビルドでは共有ライブラリが実行ファイルの隣に配置されるため、実行ファイルだけを /usr/local/bin にコピーすると error while loading shared libraries: libllama.so で失敗します。
-t llama-server は server ターゲットだけをビルドします。デフォルトのビルドでは他のツールとテストもコンパイルされます。2 コアの VPS では、実行しないファイルのためにさらに数分かかります。
-j 2 は意図的に設定しています。並列コンパイルの各ジョブは独自のワーキングセットを保持するため、小規模プランで -j $(nproc) を指定すると c++: fatal error: Killed signal terminated program cc1plus になります。これは kernel の out-of-memory killer がコンパイラを停止した状態です。ジョブ数を減らすか、ビルド用に swap を追加してください。
変更を検討してよいフラグが 1 つあります。GGML_NATIVE はデフォルトで on のため、コンパイラはビルドを実行している CPU に正確に合わせて最適化します。ビルドしたマシンでそのまま実行する場合は、この設定で問題ありません。1 回ビルドして別のホストへバイナリをコピーする場合は、-DGGML_NATIVE=OFF を追加してください。別の CPU にない命令を使用するバイナリは、最初の推論時に Illegal instruction (core dumped) で終了するためです。
タグを含む名前でインストールします。
./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 は build number と commit を表示します。checkout したタグと一致している必要があります。一致しない場合は、別の内容をビルドしています。ファイル名に build number を含め、そこへ symlink を向けておくと、アップグレードは ln -sfn 1 回と再起動 1 回で済みます。rollback も、古い number を指定して同じコマンドを実行するだけです。
GGUF モデルを取得し、最初にディスク容量を確認する
GGUF は llama.cpp が読み込む単一ファイル形式です。1 つのファイルに重み、tokeniser、メタデータが含まれるため、ほかにインストールするものはありません。ファイル名の拡張子は量子化方式を示します。これは重みを保存する精度です。Q4_K_M は 4-bit の混合方式、Q8_0 は 8-bit、f16 は量子化されていない半精度ファイルです。
ダウンロードする前に、サービスアカウントとモデルディレクトリを作成します。
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サーバー自身に -hf でモデルを取得させることもできます。ビルドが動作することを確認するには、これが最も速い方法です。
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 はダウンロード先のディレクトリを指定します。指定しない場合、ファイルはコマンドを実行したアカウントの ~/.cache/llama.cpp に保存されます。これは、これからホームディレクトリを読み取り不可にするサービスの保存先として適切ではありません。続けて ls -lh /srv/models を実行してください。キャッシュされたファイル名は単純なファイル名ではなく、リポジトリ名から生成されるためです。
サービスで使用する場合は、決めたパスにダウンロードします。これにより、unit ファイルから安定したパスを参照できます。
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最初に問題になるのはディスク容量です。以下は、2 つのモデルについて 2026 年 8 月 18 日に確認した公開ファイルサイズです。
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
}
]1B モデルの 4-bit ファイルは 0.81 GB です。同じモデルを量子化しない場合は 2.01 GB です。そのため、形式の選択だけで容量が 2 倍を超えて変わります。MXFP4 の 20B モデルは 12.11 GB で、多くのエントリーレベルプランではディスクに収まりません。その後、ファイルをメモリに読み込む必要もあります。
ダウンロードする前に毎回 df -h を確認してください。12 GB の転送中にルートファイルシステムが満杯になると、journal を含め、書き込みを必要とするほかの処理もすべて失敗します。
手動で 1 回実行して、確認します
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別のセッションで、サーバーが準備完了かどうかを確認します。
curl -s http://127.0.0.1:8080/healthファイルの読み込み中は、HTTP 503 と次のボディが返ります。
{"error":{"code":503,"message":"Loading model","type":"unavailable_error"}}準備が完了すると、ボディは {"status": "ok" } になります。次に実際のリクエストを送信します。
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."}]}'choices 配列を含む JSON オブジェクトが返れば、サーバーは正常に動作しています。model フィールドがあるのは、OpenAI クライアントが必ず送信するためです。このサーバーではモデルを 1 つだけ読み込むため、この値で選択することはありません。
OpenAI 互換 API と、ポート上で利用できるその他の機能
POST /v1/chat/completions、POST /v1/completions、POST /v1/embeddings は OpenAI 互換のルートで、GET /v1/models は読み込まれているモデルを報告します。GET /health は前述の readiness check で、GET /props はサーバーの現在の設定を返します。GET /metrics は、--metrics を指定して起動した場合に Prometheus のカウンターを公開します。
base URL に http://127.0.0.1:8080/v1 を設定し、空でない API key 文字列を渡せば、どの OpenAI SDK でも動作します。--api-key を自分で設定するまでは、その key は検証されません。
他者が示すスループットを、自分の計画の数値として扱わないでください。CPU 推論の速度は、コア数、メモリ帯域幅、同じホストを共有する他の利用者に左右されます。そのため、自分の環境で 1 秒あたりのトークン数を測定することが重要です。その結果を実際の性能として扱ってください。負荷の高い同居テナントによる steal time は、時間帯によって生成速度が変化する形で現れます。
127.0.0.1 で待ち受け、前段にプロキシを置く
--host はデフォルトで 127.0.0.1 になっているため、変更しない限りサーバーは外部から到達できません。そのままにしてください。llama-server にはユーザーモデル、レート制限、有用な監査ログがありません。組み込みの制御は --api-key だけで、1 つの文字列を比較します。推論ポートを公開すると、見つけた相手に計算資源を無償で使われます。Ollama で同じ誤りを犯した場合も構造は同じです。セルフホスト型モデル API を保護する の対策をここにもそのまま適用してください。
nginx で TLS (transport layer security) を終端し、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 が必要です。バッファリングを有効にすると、nginx は server-sent events (SSE) をレスポンスが完了するまで保持します。そのため、クライアントは無応答のまま待ち続け、最後に回答全体を一度に受け取ります。長時間の生成には proxy_read_timeout 600s が必要です。デフォルトの 60 秒では、遅い回答が 504 Gateway Time-out になるためです。証明書は nginx で Certbot と Let's Encrypt を使う で取得してください。
systemd unit
/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設定はEnvironment=の行に記述します。llama-serverはほとんどのフラグについてLLAMA_ARG_*変数を読み込み、コマンドライン引数が対応する変数を上書きするためです。これにより、コンテキストサイズを変更する場所を1か所に集約できます。また、ExecStartも一目で読める長さに保てます。
ProtectSystem=strictを指定すると、このunitから見えるファイルシステム全体が読み取り専用になります。このサーバーはモデルを読み取るだけなので問題ありません。サービス自体に-hfでモデルをダウンロードさせる場合は、ReadWritePaths=/srv/modelsを追加します。ProtectHome=yesは/homeと/rootを隠します。これが、モデルを/srvに置く2つ目の理由です。ProtectHomeを有効にすると、デフォルトの~/.cache/llama.cppパスはプロセスからまったく見えなくなります。
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は省略されがちな部分です。enableがないと、次回の再起動後にサーバーが起動しません。サービスに関連する定期処理を設定する場合は、夜間に新しいリリースを確認する処理などに、systemd serviceとtimerを使用します。
OOM が発生する前に動作を決める
メモリ使用量には 2 つの部分があり、制限下では異なる動作をします。モデルファイルはデフォルトでメモリマップされるため、そのページはファイルバックドです。カーネルはページを破棄し、必要になったときにディスクから再読み込みできます。KV キャッシュは、サーバーがアクティブな各会話について保持するトークンごとの状態であり、匿名メモリです。破棄できないため、プロセスが kill される原因になります。
そのため、unit 内の 2 つの制限は異なる役割を持ちます。MemoryHigh=3G はソフト制限です。これを超えると、カーネルは cgroup に reclaim 圧力をかけるため、マップされたモデルのページが退避され、次のトークン処理時にディスクから再読み込みされます。サービスは動作を続けますが、処理は遅くなります。MemoryMax=3500M はハード制限です。これを超えるとプロセスが kill され、journal にその内容が明確に記録されます。
llama-server.service: A process of this unit has been killed by the OOM killer.--ctx-size は自分で設定してください。デフォルトは 0 です。これはモデルの学習時に使用されたコンテキスト長を意味し、現行のロングコンテキストモデルでは、起動時に非常に大きな KV キャッシュが割り当てられます。その結果、サービスは 1 件もリクエストを処理する前に終了します。--parallel は同じコストを乗算します。各 slot が独自の会話状態を保持するためです。同時実行が必要だと確認するまでは、1 のままにしてください。
Restart=on-failure を設定すると、kill されたサービスは復旧します。起動するたびに kill されると、systemd は再起動をあきらめ、systemctl status は start request repeated too quickly を出力します。これは正しい動作です。5 秒ごとに 12 GB のファイルを再読み込みする再起動ループは、停止状態より悪影響が大きいためです。制限またはコンテキストサイズを修正し、sudo systemctl reset-failed llama-server で状態を消去してください。
リクエストの処理中に、systemctl show llama-server -p MemoryCurrent で実際の値を確認してください。systemd でプロセスのメモリと CPU を制限する では、これらのディレクティブについて詳しく説明しています。
このワークロードでは swap を避けてください。swap に退避されたモデルでは、すべてのトークン処理がランダムなオフセットへのディスク読み取りになります。モデルファイルをメモリマップすれば同様の動作をより小さな影響で実現できます。カーネルが必要なページをファイルから直接読み取るためです。
Ollama のほうが適している場合
これは選択の分岐点です。設定したフラグ、固定したビルド、選択したファイルを使って 1 つのプロセスだけを実行したい場合は llama-server を選択します。他のプロセスが動作していないため、環境が勝手に変わることもありません。
モデル管理が必要な場合は Ollama を選択します。名前でモデルを取得し、複数のモデルをディスクに保持し、アイドル状態のモデルをアンロードし、再ビルドせずに 1 つのコマンドでアップグレードできます。これらは、通常なら自分でスクリプト化する必要がある実際の作業です。VPS 上で Ollama を実行する は、同じ作業について別の選択をした構成です。どちらも OpenAI 互換 API を提供するため、どちらの方向に切り替えてもクライアントコードをそのまま利用できます。
固定したビルドをアップグレードする
bNNNNNを移行先のタグに置き換えます。
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古いバイナリはディスク上に残るため、ロールバックはllama-server-b10488へln -sfnするだけで済み、その後に再起動します。移行前にリリースノートを確認してください。GGUFファイルにはバージョンが付いており、古いファイルも引き続き読み込めます。ただし、フラグ名は変更されることがあります。--mlockと--no-mmapはすでに非推奨で、--load-modeに置き換えられています。削除されたフラグを渡すunitファイルは、認識されない引数のメッセージを出して起動に失敗します。
失敗するケースと表示される文字列
error while loading shared libraries: libllama.so は、バイナリを別の場所にコピーした後に表示されます。デフォルトのビルドでは、バイナリと同じ場所に共有ライブラリも生成されます。-DBUILD_SHARED_LIBS=OFF を指定して再ビルドするか、build/bin ディレクトリ全体をコピーしてください。
Illegal instruction (core dumped) は、起動時または最初のリクエスト時に表示されます。バイナリは GGML_NATIVE を有効にして、実行中の CPU とは異なる CPU 向けにコンパイルされています。このマシン上で再ビルドするか、-DGGML_NATIVE=OFF を指定して設定してください。
c++: fatal error: Killed signal terminated program cc1plus は、ビルド中に表示されます。コンパイラがメモリを使いすぎたため、強制終了されました。-j を下げるか、ビルド用に swap を追加し、完了後に削除してください。
curl: (7) Failed to connect ... Connection refused は、ノート PC から接続した場合に表示されます。これは正常です。サーバーは VPS の loopback アドレスで待ち受けています。VPS 自体でテストするか、ssh -L 8080:127.0.0.1:8080 user@your-vps でトンネルを開き、ローカルでは http://127.0.0.1:8080 を使用してください。
再起動後の最初の数秒または数分間、"message":"Loading model" を伴う HTTP 503 が返されます。数 GB のファイルの読み込みには時間がかかります。また、systemd はプロセスの起動直後、モデルがメモリに読み込まれる前に unit を active と報告します。
リクエストがハングし、その後 504 Gateway Time-out を返す。 プロキシがモデルの処理完了前にタイムアウトしました。proxy_read_timeout を増やし、生成されたトークンをそのままクライアントへ送信できるように proxy_buffering を無効にしてください。
unit がフラップした後に停止する 場合、start request repeated too quickly が表示されます。起動するたびに何らかの処理によって終了させられています。journalctl -u llama-server で OOM killer の行を確認し、--ctx-size または --parallel を下げるか、MemoryMax を増やしてください。
FAQ
llama.cpp の server と Ollama のどちらを VPS で実行すべきですか?
ビルドを正確に固定し、正確なフラグを渡し、更新によって予期せず変更されない 1 つのファイルに 1 つのモデルを保持したい場合は、llama-server を実行します。モデル管理、ワンコマンドでのアップグレードが必要な場合は Ollama を実行します。名前でモデルを取得し、複数のモデルをディスク上に保持し、アイドル状態のモデルをアンロードする処理を自分でスクリプト化せずに済むためです。どちらも OpenAI 互換 API を提供するため、後で切り替えてもクライアントコードは変わりません。
固定する llama.cpp のバージョンはどれですか?
実際にビルドしてテストしたタグであれば、どれでも構いません。llama.cpp はほぼすべてのマージにタグを付けており、タグ名は b10488 のようなビルド番号です。これは 18 August 2026 時点で最新でした。独立した stable ブランチはないため、「current」は 1 日に数回変わります。--branch <tag> で clone し、そのタグを含むファイル名でバイナリをインストールして、シンボリックリンクをそのバイナリに向けます。これにより、アップグレードとロールバックをそれぞれ 1 コマンドで実行できます。
llama-server にはどの程度の RAM が必要ですか?
まず GGUF ファイルのサイズを基準にし、KV cache の容量を加えます。KV cache は --ctx-size と --parallel slot の数に応じて増加します。公開されている数値だけでは、自分の構成の測定結果の代わりになりません。合計容量は、モデル、量子化方式、許可するコンテキストによって変わるためです。リクエスト処理中に systemctl show llama-server -p MemoryCurrent を実行し、表示された数値を使用します。
/health が「Loading model」とともに 503 を返すのはなぜですか?
プロセスは起動していますが、モデルファイルがまだメモリに読み込まれていないため、server は {"error":{"code":503,"message":"Loading model","type":"unavailable_error"}} を返します。これは再起動のたびに発生する通常の状態で、ファイルの読み込みにかかる時間だけ続きます。クライアントやプロキシが最初の 503 を致命的な障害として扱う場合にだけ問題になります。/health を、{"status": "ok" } を返すまでポーリングします。
llama-server をインターネットに直接公開できますか?
0.0.0.0 に bind してポートを開放しないでください。アカウント、レート制限、監査に利用できる十分なリクエストログがなく、組み込みのチェックも --api-key だけです。これは 1 つの文字列を比較します。デフォルトの 127.0.0.1 bind を維持し、TLS を設定した nginx を前段に置き、--api-key も設定してください。これにより、プロキシ設定の 1 つのミスでモデルが全員に公開される事態を防げます。