SSD Nodes Learn Hosting plans →
ガイド Matt Connor著者 Matt Connor

Claude CodeをリモートVPSのtmuxで動かす方法

SSH切断後もClaude Codeのagentを動かす方法を解説します。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 自体は単一のネイティブバイナリであり、マシンのリソースを圧迫するものではありません。リソースを消費するのは、エージェントが代わりに実行する処理です。ビルド、完全なテストスイート、tsc、言語サーバー、Docker 上のデータベースなどが該当します。CLI ではなく、ツールチェーンに合わせてサイジングしてください。使用する予定がなくても、swap を追加してください。OOM による強制終了を、時間のかかるビルドに変えられます。

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

特別な理由がない限り、そのファイルには鍵交換を記載しないでください。古いセキュリティ強化ガイドからコピーした KexAlgorithms 行では、従来型のアルゴリズムだけが固定されていることがよくあります。そのため、最近の ssh client では 接続で耐量子鍵交換が使用されていないという警告 が表示されます。

現在のセッションを開いたまま設定を検証して再読み込みし、2 つ目の端末から新しいセッションを接続して確認します。

sudo sshd -t && sudo systemctl restart ssh

Ubuntu 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 enable

fail2ban をインストールする場合は、導入による効果を理解しておいてください。パスワード認証を無効にすれば、ブルートフォース攻撃はそもそも成功しません。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 をインストールする

npm を使うには Node.js 22 以降が必要です。(現在のパッケージは Node では動作しないネイティブバイナリを取り込むようになりましたが、npm は引き続きエンジンのバージョンを確認します。また、エージェント自身のツールチェーンでも通常は新しい Node が必要です。)ディストリビューションのパッケージは古いことが多いため、Ubuntu と Debian では通常 NodeSource を使用します。署名付きリポジトリが提供されるため、Ubuntu 24.04 で非推奨となり、Debian 13 ではすでに廃止された 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 が所有するグローバルプレフィックスを使うと、後で権限エラーが発生し、npm キャッシュにも root 所有のファイルが残ります。まず npm のプレフィックスをユーザーのホームディレクトリに設定します。

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

この export は ~/.bashrc に記述します。ファイル上部付近にある「If not running interactively, don't do anything」というガードより上に置いてください。デフォルトの tmux ペインはログインシェルを起動します。tmux は default-command を空のままにします。これにより、default-shell がログインシェルとして起動します。ログイン bash は ~/.profile を読み込みます。Ubuntu では、このファイルから ~/.bashrc が読み込まれます。たとえば default-command を自分で設定した場合など、非ログインシェルは ~/.bashrc を直接読み込みます。そのため、~/.bashrc なら両方のケースに対応できますが、~/.profile だけでは後者に対応できません。注意点として、~/.bash_profile または ~/.bash_login が存在すると、bash は ~/.profile を読み込みません。そのため、いずれかを作成済みであれば、そこから ~/.bashrc も読み込むようにしてください。nvm などのバージョンマネージャーでユーザー単位の Node を導入しても、同じ構成にできます。どちらの方法でも、npm install -g で sudo を実行する必要がない状態にすることが目的です。npm も問題なく動作しますが、Anthropic のドキュメントではネイティブインストールスクリプトが推奨されています。署名付きの apt、dnf、apk リポジトリも選択肢です。インストール方法は変わるため、貼り付けて実行する前に Anthropic のインストールドキュメントを確認してください。

リポジトリ内で claude を実行すると起動します。初回実行時には認証手順が始まります。ヘッドレス環境にはブラウザーがないため、自分の端末で開く URL と、ターミナルに戻すコードが表示されます。(環境変数に API key を設定する方法もあります。)どちらの方法でも、その認証情報はサーバー上に保存されます。次は、見落とされやすい点を確認します。

影響範囲を考える

シェルアクセスを持つエージェントは、シェルそのものです。実行ユーザーが読み取れるものはすべて読み取れ、そのユーザーが push できる場所にはどこでも push できます。これはツールへの批判ではありません。ツールの定義そのものです。そのため、個々の設定よりも、エージェントを実行するアカウントが重要になります。

  • 専用の非特権ユーザー。 sudo グループには所属させず、自分のアカウントとホームディレクトリを共有させません。
  • 本番環境の認証情報をサーバーに置かない。 本番用キーを保持する ~/.aws/credentials や、本番環境からコピーした .env を置きません。重要なデータに対する書き込み権限を持つデータベースパスワードも置かないでください。エージェントにはステージング環境用、または読み取り専用の認証情報を与えます。
  • 用途を限定したトークン。 1 つのリポジトリだけに制限したきめ細かな GitHub トークンを使用します。読み取りアクセスで足りる場合は deploy key を使用します。

Claude Code には --dangerously-skip-permissions というフラグがあり、権限確認のほぼすべてを省略できます。ノートパソコン上や使い捨てのプロジェクトで使うかどうかは、利用者の判断です。しかし、トークンを保持するサーバー上では、誤って解釈した指示と git push --force の間に残された最後の防壁を取り除くことになります。省略される権限確認も、すべてを許可するか拒否するかだけではありません。また、auto mode が現在のデフォルトの開始モードであること(Claude Code v2.1.283 以降)を踏まえると、監視していないサーバーで固定すべき権限モードを把握しておく価値があります。このフラグが実際に変更する内容と、このフラグで実行するエージェントを、組み込みの sandbox から使い捨ての VPS までの範囲で制限する方法については、サーバー上で Claude Code を安全に実行するで説明します。

Deploy key と SSH agent forwarding の比較

git がノートパソコン上のキーを使用できるよう、ssh -A したくなるかもしれません。これによって何が許可されるかを理解してください。agent forwarding は、ローカルの SSH agent のソケットを、そのサーバー上でそのユーザーとして実行されるプロセスに公開します。接続を維持している間は、エージェントを含む agent として実行されるプロセスが、到達可能な 任意の ホストに対する署名をキーへ要求できます。これは「この 1 つのリポジトリを git pull させる」範囲をはるかに超えています。

代わりに、サーバー上でキーを生成します。それをリポジトリ単位の deploy key として登録します。エージェントが push する必要がある場合に限り、書き込みアクセスを付与します。さらに 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 claude

tmux 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> を実行することです。

ペインが小さな枠に縮みます。 window-size オプションによって、接続中のどのクライアントがサイズを決めるかが決まります。tmux 3.1 以降(Ubuntu 24.04 には 3.4 が含まれます)のデフォルトは latest で、直近に操作されたクライアントが選ばれます。ただし、window-size smallest を含む設定では、接続中のクライアントのうち最小のサイズが適用されます。そのため、別のマシンから接続したままの古いクライアントによって表示領域が狭くなります。接続時に他のクライアントを強制的に切断するには、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 server を選択することもあり、その場合はセッションも終了します。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 ですが、ときどきしか発生しません。 ~/.bashrc の「If not running interactively, don't do anything」ガードより下に PATH の export があるため、非対話シェルではその設定が読み込まれません。export をそのガードより上に移動し、~/.bashrc に残してください。デフォルトの tmux ペインはログインシェルであり、Ubuntu の ~/.profile を経由して ~/.bashrc に到達します。一方、非ログインシェルは ~/.bashrc を直接読み込むため、この 1 ファイルで両方に対応できます。

接続後に色が乱れます。 TERM の不一致が原因です。上記の default-terminal の行で修正できます。

再起動後にセッションが消えます。 これはバグではありません。tmux server はプロセスであり、再起動によって終了します。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 を使おうとしているなら、そのジョブはサービスとして実行すべきです。