Claude CodeをVPSのtmuxで常駐させる方法
SSH切断後もClaude Codeを動かす方法を解説します。tmuxの仕組み、Linux VPSの選び方、swapやディスク管理、想定すべき失敗例を確認できます。
問題は CLI ではなく、ノートパソコンの蓋です
Claude Code はノートパソコン上で正常に動作しますが、蓋を閉じると動作しなくなります。SSH セッションが切断され、シェルが SIGHUP を受け取り、テスト開始から 3 分後の agent も終了します。スリープしないマシン上で CLI を実行し、プロセスが SSH セッションの子プロセスにならないターミナルマルチプレクサ内で動かします。必要なのはこれだけです。重要なのはインストールではなく tmux です。
このページでは、agent を実行したままにするマシンの運用方法を説明します。電源を入れたままにできる Linux サーバーがない場合、この内容は適用できません。これが唯一の前提条件です。
tmux が実際に行うこと
SSH で接続すると、sshd はシェルを fork して擬似端末を割り当てます。そのシェルから起動したものはすべて、そのシェルの子プロセスです。接続を切ると、カーネルは pty を破棄し、シェルに SIGHUP を送ります。シェルは順に子プロセスにもハングアップを通知します。フォアグラウンドで実行中の長時間プロセスは終了します。
tmux はプロセスの親子関係を逆転させます。入力した tmux コマンドは、unix socket 経由で、端末から分離して動作する tmux server と通信する薄いクライアントです。セッション内のシェルは sshd ではなく、そのサーバーの子プロセスになります。SSH 接続を終了するとクライアントは終了しますが、サーバー、セッション、処理中の agent は動作を続けます。再接続して tmux attach を実行すれば、同じスクロールバックが残った同じシェルに戻れます。nohup もハングアップ後に処理を継続しますが、戻る方法はありません。バックグラウンド化した TUI に再接続することもできません。Claude Code は対話型のため、tmux(または screen)が適しています。
マシンのスペックを決める
CLI は Node プロセスです。マシンのリソースを消費する主因ではありません。リソースを消費するのは、エージェントが代わりに実行するビルド、完全なテストスイート、tsc、言語サーバー、Docker 上のデータベースなどです。CLI ではなく、ツールチェーンに合わせてスペックを決めてください。使う予定がなくても swap を追加します。これにより、突然の OOM kill を低速なビルドに変えられます。
sudo fallocate -l 4G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstabディスクも監視してください。リポジトリ、node_modules、Docker イメージはすぐに蓄積します。ツールチェーンがコンテナを越えて完全な仮想マシンまで使用し、KVM ゲストやローカル Kubernetes ノードを実行する場合は、契約前にプランで CPU 仮想化拡張機能が公開されていることを確認してください。VPS 上でネストした仮想化を実行するには、ゲスト内から設定を変更するのではなく、プロバイダー側で有効化する必要があります。
root ではないユーザーを先に作成する
専用のホームディレクトリを持つユーザーを作成し、公開鍵を配置します。
sudo adduser --disabled-password --gecos "" agent
sudo install -d -m 700 -o agent -g agent /home/agent/.ssh
sudo cp ~/.ssh/authorized_keys /home/agent/.ssh/authorized_keys
sudo chown agent:agent /home/agent/.ssh/authorized_keys
sudo chmod 600 /home/agent/.ssh/authorized_keys先に進む前に、2 つ目のターミナルからログインできることを確認します。この時点では、フォールバックとしてパスワード認証も有効です。公開鍵による認証が拒否されました と表示される場合、通常は鍵自体ではなく、その .ssh ディレクトリの所有者またはモードに問題があります。
意図的に、agent は sudo グループに所属させません。システムパッケージが必要な場合は、インストールします。この判断だけで、誤ったシェルコマンドによってホストが破損する可能性の大半を排除できます。
稼働させ続けるサーバーの SSH セキュリティ対策
公開インターネット上で常時稼働し、agent とソースコードを保持するマシンで、パスワード認証を有効にしておくリスクを負う必要はありません。無効にしてください。Ubuntu 24.04 と Debian 13 では、/etc/ssh/sshd_config に /etc/ssh/sshd_config.d/*.conf が含まれているため、メイン設定を編集するのではなく、ファイルを追加します。
# /etc/ssh/sshd_config.d/10-hardening.conf
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin no現在のセッションを開いたまま設定を検証して再読み込みし、2 つ目の端末から新しいセッションを接続して確認します。
sudo sshd -t && sudo systemctl restart sshUbuntu 24.04 には注意点があります。sshd は socket-activated です。認証設定は systemctl restart ssh に適用されますが、待ち受ける Port を変更した場合は systemctl daemon-reload と ssh.socket の再起動も必要です。
次にファイアウォールを設定します。有効化する前に SSH を許可してください。そうしないと接続できなくなります。
sudo ufw allow OpenSSH
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw enablefail2ban をインストールする場合は、導入による効果を理解しておいてください。パスワード認証を無効にすれば、ブルートフォース攻撃はそもそも成功しません。fail2ban は失敗した試行を journal に記録させないためのものです。
# /etc/fail2ban/jail.local
[sshd]
enabled = true
backend = systemd
maxretry = 5
bantime = 1h最後に、sudo apt install unattended-upgrades と sudo dpkg-reconfigure -plow unattended-upgrades で自動的にパッチを適用します。tmux との相互作用に注意してください。Unattended-Upgrade::Automatic-Reboot を有効にすると、カーネル更新によってマシンが再起動し、すべてのセッションが終了します。無効のままにして、実行中の処理がないタイミングに自分で再起動してください。同じ注意はリリースアップグレードにも当てはまります。Ubuntu 24.04 から 26.04 へマシンを移行すると sshd とカーネルが再起動されるため、重要な処理を実行している tmux セッションがない時間帯に実施してください。
Ubuntu に Node.js と Claude Code をインストールする
Claude Code は Node CLI なので、現行の Node が必要です。ディストリビューションのパッケージは古いことが多いため、Ubuntu と Debian では通常 NodeSource を使用します。NodeSource は署名済みのリポジトリを提供します(apt-key は不要です。このツールは廃止されています)。
curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash -
sudo apt install -y nodejs
node --versionここで間違えやすい点があります。CLI は agent ユーザーとしてインストールし、sudo npm -g は絶対に使用しないでください。 root 所有のグローバル prefix を使うと、後で権限エラーが発生し、npm キャッシュにも root 所有のファイルが残ります。まず npm の prefix をユーザーのホームディレクトリに設定します。
mkdir -p ~/.npm-global
npm config set prefix ~/.npm-global
echo 'export PATH="$HOME/.npm-global/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
npm install -g @anthropic-ai/claude-code
claude --versionexport は ~/.bashrc に記述し、~/.profile には記述しません。また、ファイルの先頭付近にある「If not running interactively, don't do anything」ガードより前に置きます。tmux は非ログインシェルを起動することがあり、その場合は ~/.bashrc を読み込んで ~/.profile を実行しません。~/.profile はログインシェルでのみ実行されます。nvm などのバージョンマネージャーでユーザー単位の Node を導入しても同じ構成にできます。いずれの場合も、npm install -g が sudo を必要としない状態にすることが目的です。npm は引き続き問題なく使用できます。または、Anthropic のネイティブインストールスクリプトを使用してください。現在のドキュメントでは、これが標準の方法です。貼り付けて実行する前に Anthropic のインストール手順を確認してください。インストール方法は変更されることがあります。
リポジトリ内で claude を実行すると起動します。初回実行時に認証手順が始まります。ヘッドレス環境にはブラウザーがないため、自分のマシンで開く URL と、ターミナルに戻して入力するコードが表示されます。(環境変数に API key を設定する方法もあります。)どちらの方法でも、その認証情報はサーバー上に保存されます。ここから、見落とされがちな点を確認します。
被害範囲を考える
シェルアクセスを持つエージェントは、シェルそのものです。実行ユーザーが読み取れるものはすべて読み取れ、そのユーザーが push できる場所にはどこにでも push できます。これはツールへの批判ではありません。シェルの定義そのものです。そのため、個々の設定よりも、どのアカウントで実行するかが重要になります。
- 専用の非特権ユーザーを使用する。
sudoグループには所属させず、自分のアカウントとホームディレクトリを共有しない。 - 本番環境の認証情報をサーバーに置かない。
~/.aws/credentialsに本番用の鍵を保持させず、本番環境から.envをコピーせず、重要な対象への書き込み権限を持つデータベースパスワードも渡さない。エージェントにはステージング環境用または読み取り専用の認証情報を与える。 - トークンの権限を限定する。 1 つのリポジトリだけに制限した細粒度の GitHub token を使う。読み取り権限だけで十分なら deploy key を使う。
Claude Code には、権限確認プロンプトを完全にスキップする flag があります。ラップトップや使い捨てのプロジェクトで使うかどうかは、利用者の判断です。しかし、token を保持するサーバーで使用すると、誤って解釈した指示と git push --force の間にある最後の防壁を取り除くことになります。スキップ対象のプロンプトも、すべてを許可するか拒否するかの二択ではありません。新しいデフォルトとして auto mode が導入されるため、監視していないサーバーでどの permission mode を固定すべきか把握しておく価値があります。flag が実際に変更する内容と、それを付けて実行するエージェントを、組み込み sandbox から使い捨ての VPS までの範囲でどう隔離するかについては、サーバー上で Claude Code を安全に実行する方法で説明します。
Deploy key と SSH agent forwarding の比較
git がラップトップ上の鍵を使えるように、ssh -A したくなるかもしれません。しかし、これによって何が許可されるかを理解してください。agent forwarding は、サーバー上でそのユーザーとして実行されるプロセスに、ローカルの SSH agent のソケットを公開します。接続を維持している間は、エージェントを含め、agent として実行されるすべてのプロセスが、到達可能な 任意の ホスト向けに鍵へ署名を要求できます。これは「この 1 つの repo を git pull させる」よりもはるかに広い権限です。
代わりに、サーバー上で鍵を生成します。その鍵をリポジトリ単位の deploy key として登録します。エージェントが push する必要がある場合に限り、書き込み権限を付与します。また、サーバーからの commit を識別できるように git identity を設定します。
ssh-keygen -t ed25519 -C "agent deploy key" -f ~/.ssh/id_ed25519_repo
cat ~/.ssh/id_ed25519_repo.pub # paste into the repo's Deploy Keys
git config --global user.name "Agent (build box)"
git config --global user.email "agent@example.com"tmux のワークフロー
インストールし(sudo apt install tmux)、最小構成の ~/.tmux.conf を設定します。
set -g mouse on
set -g history-limit 50000
set -g default-terminal "tmux-256color"日常の操作は、次の 4 つのコマンドで足ります。
tmux new -A -s claude # attach to session "claude", creating it if absent
# ...run `claude` inside it, work normally...
# Ctrl-b then d -> detach; everything keeps running
tmux ls # list sessions
tmux attach -t claude # reattach, from this machine or any other
tmux kill-session -t claudetmux new -A -s claude は覚えておくべきコマンドです。セッションが存在すれば接続し、存在しなければ作成します。そのため、作業開始時にも接続が切れた後の再開時にも、1 つのコマンドで対応できます。エイリアスを設定してください。セッション内では、Ctrl-b c でウィンドウを開き、Ctrl-b n と Ctrl-b p でウィンドウを切り替えます。Ctrl-b [ でコピーモードに入り、過去の出力をスクロールできます(q で終了します)。
終了させずに残すセッションについて、1 つ知っておくべきことがあります。エージェントはターンごとに会話全体を再送するため、セッションを 1 週間実行したままにする前に、長時間稼働する Claude Code セッションがトークンを何に消費するか を確認してください。
障害パターン
「セッションが消えました」 tmux ls には no server running on /tmp/tmux-1000/default と表示されます。ほとんどの場合、プロセスが tmux 内で実行されていません。SSH で接続し、claude を直接実行した状態で接続が切れたため、プロセスも終了しています。復旧できるものはありません。これを防ぐには、ログインするたびに tmux new -A -s <project> を最初に実行します。
ペインが小さな枠まで縮みます。 tmux は接続中のクライアントのうち、最も小さいサイズに合わせてセッションを表示します。そのため、別のマシンから接続したままの古いクライアントが表示領域を狭めることがあります。接続時に他のクライアントを強制的に切断します: tmux attach -d -t claude。
ビルドに Killed と表示されます。 スタックトレースはなく、1 語だけが表示されます。sudo dmesg -T | grep -i -E 'out of memory|killed process' で確認します。カーネルの OOM killer が最もメモリを使用しているプロセスを選択したことを示します。Node では、代わりに FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory と表示されることがあります。対処は次の順に行います。前述の手順で swap を追加し、テストとコンパイラの並列度を制限し、NODE_OPTIONS=--max-old-space-size=... で Node のヒープサイズを増やします。それでも足りなければ VPS のサイズを上げます。OOM killer はビルドではなく tmux サーバー を選択することもあり、その場合はセッションも終了します。systemd-oomd が実行中の場合は、同じ結果になるようにユーザー全体の slice を終了させることがあります。
npm error code EACCES / permission denied, mkdir '/usr/lib/node_modules/...'。 root が所有する prefix にグローバルインストールしています。前述の ~/.npm-global prefix を使用します。以前に sudo npm を実行したことがある場合は、Your cache folder contains root-owned files も表示されることがあります。sudo chown -R $(id -u):$(id -g) ~/.npm で修復します。
claude: command not found ですが、常に発生するわけではありません。 PATH の export が、"If not running interactively, don't do anything" というガードの下にある ~/.bashrc に記述されています。そのため、非対話シェルでは export がスキップされます。export をそのガードより上に移動し、~/.bashrc に記述します。~/.profile には記述しないでください。tmux はログインシェルではないシェルを起動することがあり、その場合は ~/.bashrc を読み込み、~/.profile には到達しません。
接続後に色が乱れます。 TERM の不一致が原因です。前述の default-terminal の行で修正できます。
再起動後にセッションが消えます。 バグではありません。tmux サーバーはプロセスであり、再起動すると終了します。uptime を確認します。
この構成が大きくなったときに起きる問題
プロジェクトが増える場合。 リポジトリごとに 1 つの tmux セッションを作成し、リポジトリ名を付けます。tmux ls がダッシュボードになります。命名規則を守らないと、セッション名が 0、1、2 のように増えていきます。複数のセッションを同時に実行する場合、それぞれを独立して運用する必要はありません。同じマシン上では 1 つのセッションから別のセッションへメッセージを送信できるためです。長時間のリファクタリングを実行しているエージェントから、別のエージェントにテスト実行を依頼するといった用途に便利です。ポートも同じように増えていきます。6 つのリポジトリがすべて :3000 を必要とするようになったら、ポートを手作業で割り当てるのをやめ、Traefik リバースプロキシで Docker Compose 上の複数のアプリケーションをルーティングする構成に切り替えます。ホスト名に基づく振り分けを Traefik に任せられます。
利用者が増える場合。 tmux のソケットはユーザーごとに分かれます。そのため、同じマシン上の 2 人の開発者はそれぞれ別の tmux サーバーを使用し、互いのセッションを参照できません。共有ソケットを使って 1 つのセッションを共有すると、全員が同じ Unix ユーザーとして同じシェルに入力することになります。その結果、監査と権限管理に影響します。ユーザーを分けることが、目立たないものの正しい方法です。
無人で実行する処理。 tmux は、接続して操作する対話型セッション向けです。監視する人がいない状態でスケジュール実行するジョブは、systemd の unit と timer に配置します。ログ記録、再起動ポリシー、ブート後も実行を継続する仕組みを標準で利用できます。cron のようなジョブを実行するために tmux を使おうとする場合、そのジョブをサービスとして扱うべきである可能性があります。
最後に、エージェントが起動する開発サーバーは 127.0.0.1 にバインドし、0.0.0.0 にはバインドしないでください。ポートを ufw で開放するのではなく、SSH トンネル(ssh -L 3000:127.0.0.1:3000 agent@your-server)経由で接続します。6 つ前後のポートを転送するようになった場合、または同じプレビューにスマートフォンとラップトップの両方から接続したくなった場合は、VPS 上にセルフホスト型の WireGuard VPN を構築する方法に切り替えます。開発サーバーはプライベートインターフェースにバインドし、ufw ではパブリックインターフェースからの接続を拒否し続けます。ファイアウォールは、穴を開け続けなければ役に立ちます。
Claude Code だけが選択肢ではありません。VPS 上でコーディング AI エージェントを実行する場合は、Aider と Goose も比較対象になります。
FAQ
Claude Code は SSH 接続が切れた後も実行され続けますか?
tmux 内で起動した場合に限ります。SSH シェルから直接起動したプロセスは、そのシェルの子プロセスであり、接続が切れると pty とともに終了します。tmux 内では、シェルがデタッチされた tmux サーバーに属するため、エージェントはタスクの途中でも処理を続け、tmux attach で同じスクロールバックに戻れます。毎回ログインしたら最初のコマンドとして tmux new -A -s <project> を実行すれば、この問題は解消します。
CLI は sudo npm install -g でインストールすべきですか?
いいえ。root 所有のグローバル prefix を使うと、後続のインストールで EACCES エラーが発生し、npm キャッシュにも root 所有のファイルが作成されます。npm の prefix を ~/.npm-global に設定するか、nvm などのバージョンマネージャーを使用してください。権限を持たない agent ユーザーでインストールし、対話型ガードより上で ~/.bashrc から PATH に ~/.npm-global/bin を追加して export します。すでに一度 sudo npm を実行している場合は、sudo chown -R $(id -u):$(id -g) ~/.npm でキャッシュを修復してください。
エージェントを実行しているホストで ssh -A エージェント転送を使用しても安全ですか?
ジョブに必要な範囲を大きく超える権限を与えることになります。転送を有効にすると、ローカル SSH エージェントのソケットが、そのユーザーで実行されるすべてのプロセスから利用可能になります。そのため、接続を維持している間は、ホスト上のあらゆるプロセスが、到達可能な任意のホストに対する署名をあなたの鍵へ要求できます。サーバー上で ed25519 鍵を生成し、リポジトリ単位の deploy key として登録してください。エージェントから push する必要がある場合に限り、書き込み権限を付与します。
ビルドで Killed とだけ表示されるのはなぜですか?
スタックトレースなしでこの 1 語だけが表示される場合、原因はカーネルの OOM killer です。sudo dmesg -T | grep -i -E 'out of memory|killed process' で確認してください。Node では代わりに JavaScript heap out of memory が表示されることがあります。修正は次の順で進めます。swapfile を追加し、テストとコンパイラの並列実行数を制限し、NODE_OPTIONS=--max-old-space-size=... を引き上げ、最後に VPS のサイズを大きくします。OOM killer はビルドではなく tmux サーバーを終了対象にすることがあり、その場合はセッション全体が切断されるため注意してください。
tmux と systemd サービスのどちらを使うべきですか?
tmux は、接続して監視し、入力する対話型セッションに適しています。エージェントセッションはまさにこの用途です。誰も監視しないスケジュール実行の処理は、systemd の unit と timer に適しています。ログ、再起動ポリシー、ブート後の継続実行を標準で利用できます。cron のようなジョブを実行するために tmux を使おうとしているなら、そのジョブはサービスとして実行すべきです。