メインコンテンツへスキップ
50% off 全プラン対象、期間限定。月額 $2.48/mo
15 min left
AIと機械学習

VPS で AI エージェントを夜間に実行するようスケジュールする方法

S 著者 Sajjad 15 分で読めます
Schedule AI Agents Overnight: a dark terminal showing a 02:00 timestamp and a green exit code 0 line, next to a clock and a completed job card

午前 2 時、一度も休止しない VPS 上でスケジュールされたジョブが起動します。ヘッドレスの claude -p 実行は、クローンしたリポジトリでキューに入ったタスクを誰にも何も尋ねずに処理し、終了します。朝に確認すると、コミットやレポート、あるいはどこでなぜ止まったかを正確に示すログが待っています。誰も見ていませんでした。

これは、ターミナルを一晩開いたままにして SSH 接続が切れないことを祈るのとは別のやり方です。夜間のエージェント実行でよくある障害点はホストそのものです。ノート PC がスリープする、蓋を閉じる、ネットワークが切れる、OS アップデートが作業中に再起動をかける、といった具合です。認証エラー、API エラー、権限待ちでの停止は依然としてジョブを止め得ますが、常時稼働のホストは最も起こりやすい障害要因を取り除きます。

本ガイドで扱うのは実際の仕組みです。主要なコーディングエージェント CLI が備えるヘッドレス用フラグ、スケジュール実行を起動する 2 つの方法とどちらを選ぶべきか、その下のホストに必要なもの、そして無人実行が後から説明したくないほどの費用や破壊を招かないためのガードレールです。

短いバージョン

  • 主要なコーディングエージェント CLI はいずれも、プロンプトを 1 つ最後まで実行して終了する非対話モードをドキュメント付きで備えています。Claude Code は claude -p、Codex CLI は codex exec、Gemini CLI は gemini -p。これは回避策ではなく、公式の機能です。
  • Claude Code には独自のスケジューリングもあります。Routines、Desktop のスケジュールタスク、そして /loop。読者によってはこれで十分で、VPS より管理の手間も少なくて済みます。
  • 夜間ジョブなら cron で十分です。再起動の可能性があるマシンでは systemd タイマーのほうが既定として適しています。 Persistent=true Persistent=true が、cron なら黙って飛ばしていた実行を拾ってくれるからです。
  • 推論はプロバイダーの API 側で行われるため、CLI 自体は軽量です。VPS はモデルではなく、そこで実行するコマンド(テスト、ビルド、コンテナ、並列ジョブ)に合わせてサイジングしてください。
  • スケジュールを放っておいても安全なのは、ガードレールがあるからです。すなわち、権限を絞ったツール、ターン数の上限、終了コードによる分岐です。スケジュールそのものは安全機構ではありません。

必要なもの

crontab を 1 行書く前、あるいは unit ファイルを作る前に、次の 5 つを用意してください。

  • SSH で接続でき、systemd ベースの Linux ディストリビューションが動作している VPS。
  • その VPS にインストールしたエージェントの CLI(Claude Code、Codex CLI、Gemini CLI のいずれか)。
  • 選んだ CLI 用の非対話型の認証情報。Claude Code の bare モードはアカウントのログイン情報を読まないため、環境変数の ANTHROPIC_API_KEY を設定するか、 apiKeyHelper を設定に指定する必要があります。通常の print モードの実行、Codex、Gemini は、ドキュメント記載のアカウントログイン認証情報も利用できます。
  • エージェントが操作対象とするリポジトリまたは作業ディレクトリ。
  • crontab を編集するか systemd の unit ファイルを作成できる権限を持つシェルアクセス。

セッションを保持せずにエージェントを実行する

ヘッドレスモードの比較: Claude Code は text、json、stream-json 出力で claude -p を実行し、Codex CLI は JSONL ストリームとサンドボックスポリシーで codex exec を実行し、Gemini CLI は TTY なしで gemini -p を実行します

主要なコーディングエージェント CLI はいずれも、まさにこのための非対話モードを備えています。Claude Code は -p、あるいは --print。Codex CLI は codex exec。Gemini CLI は -p、あるいは --promptを受け取ります。いずれもプロンプトを 1 つ受け取り、最後まで実行して終了します。チャットのループも、開いたままにしておくターミナルも、再接続する対象もありません。

Claude Code はセッションを開いたままにしなくても実行できますか。できます。 オプション -p を渡すと、プロンプトは非対話モードで実行されます。Claude Code は最後まで処理し、結果を出力して終了します。チャットのループも、生かしておくべきものもありません。しかも対話型 CLI と同じ Agent SDK 上で動作します。出典は Anthropic 公式のヘッドレスモードのドキュメント.

CLI非対話モードのフラグ動作構造化出力
Claude Code-p / --printプロンプトを最後まで実行し、結果を出力して終了--output-format を text、json、stream-json のいずれかに設定
Codex CLIcodex exec進捗を stderr にストリーミングし、最終メッセージを stdout に出力して終了--json で JSONL のイベントストリームを取得
Gemini CLI-p / --promptプロンプトを非対話的に実行して終了--output-format json

ここで最も重要なのは Claude Code 自身のフラグです。実際にスクリプトを書く相手がそれだからです。そのうち 2 つは、誰も起きていない時間に許可を求めて止まることなく実行を進められるようにします。 --allowedToolsは特定のツールを事前に許可し、 --permission-modeは実行全体の基準を設定します。 --max-turns は、実行がエラーで終了するまでに使えるエージェントのターン数に上限を設けます。

--bare は、フック、スキル、プラグイン、MCP サーバー、そして CLAUDE.mdといったプロジェクト指示を読み込みません。スクリプト実行を高速かつ再現性の高いものにするためです。裏を返せば、ジョブが依存する指示はすべてプロンプトかコマンドに含める必要があります。bare モードはアカウントのログイン情報も読まないため、 Anthropic のドキュメントでは環境変数に API キーを設定するよう案内しています 。実行前に設定してください。Claude Code は --bg-pと組み合わせた場合は即座に拒否し、 --cloud はタスクの説明を伴う場合に同様に拒否します。何が衝突しているかを明示して停止し、あいまいな動作はしません。

実際の呼び出し例です。プロンプトとツール一覧はご自身のタスクに合わせて調整してください。

claude --bare -p "Review open PRs in this repo and summarize any blockers in NOTES.md" \
  --allowedTools "Bash(gh pr list *),Bash(gh pr view *),Bash(gh pr diff *),Read,Edit" \
  --permission-mode dontAsk \
  --max-turns 8 \
  --max-budget-usd 5.00 \
  --output-format json

予算とコマンドのパターンはタスクに合わせて調整してください。この例では、実行するアカウントで GitHub CLI の認証がすでに設定済みであることも前提にしています。

新しい VPS に Claude Code をセットアップしていて、ブラウザーのないマシンで認証する手順が知りたい場合は、 ヘッドレスサーバーで Claude Code を認証する方法。上の短い説明だけでもスケジュール実行は動かせます。

Codex CLI の exec モードについては OpenAI の非対話モードのドキュメントに説明があり、 --sandbox でポリシーを選択できます。 read-only が既定値で、 workspace-write はエージェントがワークスペース内に書き込むことを許可し、 --json は stdout をプレーンテキストではなく機械可読なイベントストリームに変えます。無人ジョブでは danger-full-access を避けてください。プロセスが隔離されていて、そのリスクを意図的に受け入れている場合は別です。

Gemini CLI のヘッドレスモードは プロジェクト公式のヘッドレス向けドキュメントに説明があり、TTY のない環境では自動的に、あるいは -pで明示的に有効化できます。一般エラー、入力エラー、ターン上限到達それぞれに専用の非ゼロ終了コードを返し、単一の汎用エラーコードにはまとめません。

スケジュールはどこに置くべきか

こうしたセットアップ作業に取りかかる前に。エージェントのベンダーがすでにスケジューリングを提供しているかもしれません。Claude Code には組み込みの選択肢が 3 つあり、そのうちの 1 つは自前で運用する VPS より本当に適している場合があります。

クラウド(Routines)Desktop のスケジュールタスク/loop
実行場所Anthropic のクラウド自分のマシン自分のマシン
マシンの電源が必要不要必須必須
セッションを開いたままにする必要不要不要必須
最小実行間隔1時間1 分1 分
ローカルファイルへのアクセスなし。新規クローンから実行されるフルアクセスフルアクセス

Anthropic 公式のスケジュールタスクのドキュメント は、これを VPS を頂点とする序列ではなく、本当の意味での三択として提示しています。タスクにマシンローカルな状態が不要で、1 時間という下限を許容でき、使うのが Claude Code だけなら、Routines は以下で説明する方法より管理が楽です。自分のマシンの電源が入っていなくても、Anthropic が新規クローンからクラウドで実行してくれます。

/loop は知っておく価値はありますが、この用途には合いません。開いたままのアイドル状態のセッションを必要とするからで、それこそが取り除こうとしている制約です。同じドキュメントは 4 つ目の選択肢として GitHub Actions にも触れています。特定のマシンに縛られたスケジュールではなく、すでに CI にトリガーがあるチーム向けです。

自前で運用する VPS が本領を発揮するのは、ジョブがローカルのファイルシステムとツールへのフルアクセスを必要とするとき、Claude Code・Codex CLI・Gemini CLI で同じ仕組みをまったく同じように動かしたいとき、あるいは Routines が許す間隔では粗すぎるときです。一般的なサーバーレス関数はここでは扱いづらいのが普通です。認証情報を復元し、リポジトリをクローンし、プラットフォームの実行時間制限内に終える必要があるからです。GitHub Actions のような使い捨ての CI ランナーは、実行ごとに新規チェックアウトでよいなら、依然として有効な第 3 の選択肢です。すでに遊んでいる常時稼働のハードウェアがあるなら、ホームラボのマシンでも構いません。ただし、その場合はプロバイダーではなく自宅ネットワークの信頼性とリモートアクセスに賭けることになります。

cron か systemd タイマーか

cron と systemd タイマーの比較: 左は crontab 1 行と、そのまま飛ばされる実行漏れ。右は .service と .timer の組み合わせで、Persistent=true による取りこぼしの補完、journald によるログ記録、単一インスタンスによる重複実行の防止

どちらのツールも同じコマンドを同じスケジュールで起動できますが、マシンが再起動したときの挙動と、セットアップにかかる手間の点で分かれます。

cronsystemd タイマー
セットアップの重さcrontab 1 行.timer ファイルと .service ファイル
実行漏れの補完なし。飛ばされた実行はそのまま消えるPersistent=true がシステム復帰と同時に実行
ログ手動。出力は自分でリダイレクトする自動。journald が取得する
依存関係の順序制御なしAfter= と Requires= による完全な systemd の順序制御

めったに再起動しないマシンでの夜間ジョブなら、素の cron で十分です。落とし穴は環境です。cron は最小限の PATHな PATH で起動し、あなたの代わりにリポジトリへ移動してくれることもなく、1 つ目がまだ走っている最中でも平気で 2 つ目を起動します。リポジトリのパス、絞り込んだエージェントのコマンド、認証情報の読み込みは、権限を制限したラッパースクリプトにまとめ、そのうえで flock を使って実行の重複を防ぎます。

# /usr/local/bin/agent-nightly
#!/usr/bin/env bash
set -euo pipefail
export PATH=/usr/local/bin:/usr/bin:/bin
export ANTHROPIC_API_KEY="$(
  cat "$HOME/.config/agent-nightly/anthropic_api_key"
)"
cd /srv/myrepo
exec /usr/local/bin/claude --bare -p \
  "Run the nightly dependency audit and write the findings to NOTES.md" \
  --allowedTools "Bash(npm audit *),Read,Edit" \
  --permission-mode dontAsk \
  --max-turns 8 \
  --max-budget-usd 5.00 \
  --output-format json
# crontab -e
0 2 * * * /usr/bin/flock -n "$HOME/.local/state/agent-runs/nightly.lock" /usr/local/bin/agent-nightly >> "$HOME/.local/state/agent-runs/nightly.log" 2>&1

認証情報とログのディレクトリを一度だけ作成し、ラッパースクリプトに実行権限を付与します。

install -d -m 700 \
  "$HOME/.config/agent-nightly" \
  "$HOME/.local/state/agent-runs"
touch "$HOME/.config/agent-nightly/anthropic_api_key"
chmod 600 "$HOME/.config/agent-nightly/anthropic_api_key"
"${EDITOR:-nano}" \
  "$HOME/.config/agent-nightly/anthropic_api_key"
sudo chmod 755 /usr/local/bin/agent-nightly

認証情報ファイルには API キーだけを貼り付けてください。crontab に直接書かないでください。

systemd タイマーはセットアップの手間が増えますが、cron にはない 2 つのものが手に入ります。自前のリダイレクトなしで使える journald へのログ記録と、 Persistent=trueです。以下の例では、専用アカウント agent-runner/srv/myrepoを所有していることを前提としています。API キーは unit に埋め込まず、root だけが読める認証情報ファイルに保存してください。

による systemd.timer のマニュアルによれば、 Persistent=true を設定すると「タイマーが非アクティブだった間に少なくとも一度トリガーされるはずだった場合、サービスユニットは直ちにトリガーされる」という意味になります。つまり、カーネル更新のために VPS が再起動している間に発火するはずだった実行は、次のスケジュール枠まで黙って消えるのではなく、復帰した瞬間に走ります。

サービスが使用する、root だけが読める認証情報ファイルを作成します。

sudo install -d -m 700 /etc/agent-nightly
sudo touch /etc/agent-nightly/anthropic_api_key
sudo chmod 600 /etc/agent-nightly/anthropic_api_key
sudoedit /etc/agent-nightly/anthropic_api_key

このファイルには API キーだけを貼り付けてください。

# /etc/systemd/system/agent-nightly.service
[Unit]
Description=Nightly scoped agent run
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
User=agent-runner
Group=agent-runner
WorkingDirectory=/srv/myrepo
Environment=HOME=/home/agent-runner
Environment=PATH=/usr/local/bin:/usr/bin:/bin
LoadCredential=anthropic_api_key:/etc/agent-nightly/anthropic_api_key
ExecStart=/bin/sh -c 'export ANTHROPIC_API_KEY="$(cat "$CREDENTIALS_DIRECTORY/anthropic_api_key")"; exec /usr/local/bin/claude --bare -p "Run the nightly dependency audit and write the findings to NOTES.md" --allowedTools "Bash(npm audit *),Read,Edit" --permission-mode dontAsk --max-turns 8 --max-budget-usd 5.00 --output-format json'
StandardOutput=journal
StandardError=journal
UMask=0077
# /etc/systemd/system/agent-nightly.timer
[Unit]
Description=Run agent-nightly.service at 2am daily, catching up missed runs

[Timer]
# Uses the VPS's configured local timezone
OnCalendar=*-*-* 02:00:00
Persistent=true
Unit=agent-nightly.service

[Install]
WantedBy=timers.target

systemd をリロードし、タイマーを有効化して、サービスを一度すぐに実行してください。認証情報・権限・パスの問題が午前 2 時ではなく今のうちに表面化します。

sudo systemctl daemon-reload
sudo systemctl enable --now agent-nightly.timer
sudo systemctl start agent-nightly.service
systemctl list-timers agent-nightly.timer
sudo journalctl \
  -u agent-nightly.service \
  -n 100 \
  --no-pager

Persistent=true が決定的な違いです。タイマーは取りこぼしたカレンダー実行を黙って捨てるのではなく、覚えています。

VPS に実際に必要なもの

初めてサイジングする人が驚くのはここです。推論はプロバイダーの API 側で行われるため、CLI 自体は軽量です。しかしエージェントはローカルでビルド、テスト、パッケージマネージャー、言語サーバー、コンテナを起動できるので、実際の下限を決めるのはリポジトリのワークロードです。

軽量なスケジュールジョブ 1 つなら、1〜2 vCPU と 2〜4 GB のメモリ、そして NVMe ストレージを出発点にしてください。大きなリポジトリ、コンパイラ、Docker ビルド、テストスイート、同時実行では、これよりずっと多くが必要になることがあります。スペックを引き上げる要因は、API の向こう側のモデルではなく、エージェントが実行する最も重いローカルコマンドです。この VPS ですでに Docker のワークロードを動かしていて、予算の全体像をもっと知りたい場合は、 ビルドマシンのサイジングとセキュリティ対策 が、別の無人ワークロードについて同じトレードオフを解説しています。

もう 1 つ計画しておくとよいことがあります。無人実行は、問題があってもなくても毎晩ログを生み出します。cron がファイルに書き出すなら logrotate を導入し、journald の保持上限も既定値のままで VPS のディスクに収まると決めつけず、確認してください。

このアプローチ全体は、午前 2 時に起きていて、ノート PC が何をしていようと稼働し続けるホストに依存しています。それこそが root 権限付きの Linux VPS Linux VPS の役目です。何かに眠らされることもなく、他人の cron ジョブと共有することもありません。

Linuxプランを見る

root権限、NVMe、AMD EPYCのパワーを備えたLinux VPSで開発を。

Linuxプランを見る

無人実行を破綻させないために

スケジュール実行がうまくいくかどうかの最大の差は、途中で人間が質問に答えなくても完了できるくらいタスクの範囲が絞られているかどうかです。欲張ったプロンプトは、その場に誰もいない状況で下せない判断を待って止まります。範囲が狭く自己完結したタスクは、きれいに完了して終了します。

2 つの権限フラグは、実行が午前 2 時に確認待ちで止まらないようにするためにあります。しかし、無条件の Bash アクセスは狭いガードレールではありません。サービスアカウントにできることのほとんどを実行できてしまいます。 Bash(git status *)のようなコマンド単位のルールを使い、 --permission-mode dontAskを組み合わせ、サービスは root ではない専用アカウントで実行してください。ターン数と支出にはそれぞれ独自の上限があります。 --max-turns はエージェントがさまよえる長さを制限し、 --max-budget-usd は 1 回の実行が API 呼び出しに使える金額に上限をかけます。

ヒント: --output-format json を付けて実行し、各呼び出しの total_cost_usd フィールドをログに残しましょう。スケジュール実行が実際に一晩あたりいくらかかっているかを追い、ある回だけ目に見えて高いときに通知を出すための、最もすっきりした足がかりです。配線に 5 分かける価値はあります。追っているのは抽象概念ではなく、あなた自身の請求額なのですから。

無人実行によるコスト超過は絵空事ではありません。 Hacker News のある投稿では、あるユーザーが、毎日走らせていたコーディングエージェントのワークフローで、プロンプトキャッシュが部分的にしか効かず約 64.7 億の入力トークンがキャッシュされなかった結果、AWS Bedrock の総額 37,901.73 ドルの請求を受けたと報告しています。これは Claude Code のヘッドレスモードではなく別のスタックでの出来事ですが、コストのログ記録と実行ごとの厳格な予算がスケジュールに組み込まれるべき理由をよく示しています。

ヒント: Claude Code は成功時に終了コード 0、失敗時に 0 以外のコードで終了します。終了ステータスを確認するラッパースクリプトを用意すれば、失敗時に通知を飛ばせるので、うまくいかなかった夜は 3 日後にたまたま気づくのではなく翌朝には表に出てきます。

最低限、各ジョブは専用ブランチか使い捨ての worktree で実行し、マージ前に人によるレビューを必須にしてください。権限を絞った認証情報、ファイルシステムの隔離、サーバーレベルでの影響範囲の制御は、スケジューリングガイドの末尾に段落を足すのではなく、独立した記事で扱うに値する大きなテーマです。

スケジュールを放っておいても安全にしてくれるのはガードレールです。スケジュールそのものは安全機構ではありません。

cron では足りなくなるとき

タイマーで動かすプロンプトが 1 つだけなら、ここまでの内容以上のものは要りません。条件分岐とリトライ、Slack 通知を伴う 3 段構えのステップとなると話は別です。

知っておく価値のある選択肢が 3 つあります。それぞれ違う理由で一段上に位置します。

  • Dagu は最も軽い一段上です。YAML で定義する自己完結型のジョブに、DAG による依存関係、リトライ、そして何が実行されたかを見るための Web UI が付きます。
  • n8n は、エージェントの実行がワークフロー全体ではなく、複数の連携や通知の中の 1 つのノードにすぎない場合に最も向いています。
  • Kestra は 3 つの中で最も重く、データやインフラのパイプラインをオーケストレーションするために作られています。エージェントのスケジューリングが目的そのものではなく、より大きなパイプラインの一部である場合に正解となります。

毎晩 1 つのプロンプトを走らせるだけの読者にとって、3 つとも過剰です。必要以上に重い構成を勧めるより、はっきりそう言っておくべきでしょう。もし将来、いくつもの手順の連鎖がどれかを正当化するようになったら、 Dagu, n8n、そして Kestra はいずれもワンクリックでデプロイできます。セットアップの手間に見合うかどうかを判断しているまさにその瞬間に、これは実質的な利点です。

LangChain や CrewAI のようなマルチエージェントのオーケストレーションフレームワークは、まったく別の話題です。すでに存在する CLI をスケジュール実行するのではなく、エージェントシステムそのものを作る話です。

よくある質問

Claude Code はセッションを開いたままにせずに実行できますか

できます。オプション -p を渡すと、プロンプトは非対話モードで実行されます。Claude Code は最後まで処理し、結果を出力して終了します。チャットのループも、開いたままにしておくセッションもありません。

Claude Code に Routines があるなら VPS は必要ですか

常にそうとは限りません。Routines はマシンの電源が入っていなくても Anthropic のクラウドで動き、新規クローンから始まりますが、自分のマシンにしかないファイルにはアクセスできず、実行間隔は最短でも 1 時間です。自前運用の VPS が本領を発揮するのは、タスクにローカルファイルが必要な場合、任意の間隔が必要な場合、あるいは複数ベンダーの CLI で同じように動く仕組みが必要な場合です。

スケジュール実行するエージェントには cron と systemd タイマーのどちらを使うべきですか

VPS がメンテナンスで再起動することがあるなら、systemd タイマーです。 Persistent=true は、ダウンタイム中に発火するはずだったジョブをシステム復帰と同時に実行します。cron にこれに相当する仕組みはありません。ずっと起動しているマシンでの夜間ジョブなら cron で十分です。

VPS でスケジュール実行する AI エージェントにはどれくらいのメモリが必要ですか

軽量なスケジュールジョブ 1 つなら、まず 1〜2 vCPU と 2〜4 GB のメモリから始め、そのうえでエージェントが実行する最も重いローカルコマンドに合わせてサイジングしてください。ビルド、テスト、Docker、大規模リポジトリ、同時実行のほうが、リモートのモデル推論よりはるかに影響します。

エージェントをスケジュール実行すると課金方式は変わりますか

スケジュール実行によって課金方式が別になることはありません。Claude Code の -p はサブスクリプションの認証情報でも API キーでも動作しますが、 --bare はサブスクリプションのログインを無視するため、環境変数の ANTHROPIC_API_KEY を設定するか、 apiKeyHelper を設定に指定する必要があります。Codex と Gemini は、それぞれの CLI に設定した認証方式に従います。価格や利用条件は変わりやすいので、構築時には各プロバイダーの最新価格と自分の使用量データを確認してください。API 経由の Claude Code 実行では、 total_cost_usd JSON 出力のこのフィールドをログに残すこともできます。

共有

ディスカッション

コメント

ログインしてディスカッションに参加してください。

ブログの他の記事

読み進める。

デプロイの準備はできましたか? 月額2.48ドルから。

2008年から独立運営のクラウド。AMD EPYC、NVMe、40 Gbps。14日間返金保証。