SSD Nodes Learn
ガイド Matt Connor著者 Matt Connor ・更新日 2026-07-24

Claude CodeをVPSのtmuxで動かす方法

SSH切断によるSIGHUPでClaude Codeが停止する問題を解決します。Linux VPS上でtmuxを使用し、セッションを維持する設定方法を解説。ノートPCを閉じてもエージェントを稼働させ続けるための、ターミナルマルチプレクサを活用した運用手順をまとめました。

問題はCLIではなく、ノートPCの蓋にあります

Claude Codeは、ノートPCの蓋を閉じるまで正常に動作します。蓋を閉じるとSSHセッションが終了し、shellにSIGHUPが送信されます。その結果、テスト実行開始から3分後にagentも停止します。CLIを実行する際は、スリープしないマシンを使用してください。また、SSHセッションの子プロセスにならないterminal multiplexer内で実行してください。これが解決策のすべてです。インストール手順ではなく、tmuxの使用が重要になります。

このページは、agentを稼働させ続けるためのサーバーの運用について説明します。常時起動しておけるLinuxサーバーがない場合は、この記事の内容は適用されません。これが唯一の前提条件です。

tmux の実際の動作

SSH で接続すると、sshd は shell を fork し、pseudo-terminal を割り当てます。その shell から開始されたすべてのプロセスは、その child になります。接続が切断されると、kernel は pty を破棄します。shell は SIGHUP を受信し、その child たちも順次ハングアップします。その結果、フォアグラウンドで実行中の長時間プロセスは終了します。

tmux はこの所有関係を逆転させます。入力した tmux コマンドは、unix socket を介して、ターミナルから切り離されて動作する tmux server と通信する thin client として機能します。session 内の shell は、sshd ではなく、その server の child になります。SSH 接続を切断しても、client は終了しますが、server、session、および実行中の agent は継続します。再接続して tmux attach を行うと、同じ scrollback を保持した同じ shell に復帰できます。nohup もハングアップから生存しますが、復帰する方法はありません。バックグラウンドにある TUI に再アタッチすることはできないためです。Claude Code はインタラクティブなツールですが、tmux(または screen)はそれに適したツールです。

リソースのサイジング

CLIはNodeプロセスであり、マシンを占有する主な要因ではありません。マシンを占有するのは、エージェントが実行するタスクです。具体的には、ビルド、フルテストスイート、tsc、language server、Docker上のdatabaseなどが該当します。CLIではなく、toolchainに合わせてリソースを確保してください。swapは、使用する予定がなくても追加してください。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

ディスク容量にも注意してください。repos、node_modules、Docker imagesは急速に蓄積します。また、toolchainがcontainerを超えて、KVM guestやローカルのKubernetes nodeなどのfull virtual machinesに及ぶ場合は、事前にプランを確認してください。VPSでnested virtualizationを実行するには、guest内部での設定ではなく、provider側での有効化が必要だからです。

非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

意図的に、agentsudo グループには含めていません。システムパッケージが必要な場合は、その都度インストールします。この運用により、誤ったシェルコマンドによってホストが破壊されるリスクの大部分を排除できます。

常時稼働するサーバーのSSHセキュリティ

パブリックインターネットに常に接続され、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

設定の検証と再読み込みを行います。テスト用の新しいセッションを別のターミナルで開き、現在のセッションは開いたままにしてください。

sudo sshd -t && sudo systemctl restart ssh

Ubuntu 24.04における注意点:sshdはsocket-activatedです。認証設定はsystemctl restart sshに適用されますが、リスニングポートのPortを変更した場合は、systemctl daemon-reloadssh.socketの再起動も必要です。

次にファイアウォールです。SSHを有効にする前に、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-upgradessudo dpkg-reconfigure -plow unattended-upgradesを使用して自動パッチを適用してください。tmuxとの関係に注意してください。Unattended-Upgrade::Automatic-Rebootを有効にしている場合、カーネルのアップデートによる再起動で、すべてのセッションが終了します。Unattended-Upgrade::Automatic-Rebootは無効にし、実行中のプロセスがないタイミングで、自身のスケジュールに合わせて再起動を行ってください。

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所有にすると、後に権限エラーが発生し、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 --version

export設定は~/.profileではなく~/.bashrcに記述します。また、ファイルの冒頭付近にある「If not running interactively, don't do anything」というガード句よりもに記述する必要があります。tmuxはnon-login shellを開始する場合があり、その場合~/.bashrcを読み込み、~/.profileをスキップするためです。~/.profileはlogin shellでのみ実行されます。nvmなどのバージョンマネージャーを使用してユーザーごとにNodeをインストールすることでも、同様の結果が得られます。いずれの方法でも、目的はnpm install -gsudoを必要としないようにすることです。npmはそのまま問題なく動作します。または、現在推奨されているAnthropicのネイティブインストールスクリプトを使用してください。貼り付ける前にAnthropicのインストールドキュメントを確認してください。インストール方法は変更される可能性があります。

リポジトリ内でclaudeを実行して開始します。初回実行時に認証の手順が表示されます。ヘッドレス環境にはブラウザがないため、ローカルマシンで開くためのURLと、ターミナルに入力するためのコードが表示されます。(環境変数にAPIキーを設定する方法もあります。)いずれの方法でも、認証情報はサーバーに保存されます。ここで、多くの人が見落としがちな手順に移ります。

影響範囲(Blast Radius)について

Shellアクセス権を持つAgentは、実質的にShellそのものです。実行ユーザーが読み取れるものはすべて読み取れ、実行ユーザーが書き込める場所にはすべて書き込めます。これはツールの欠陥ではなく、ツールの定義です。そのため、個別の設定よりも、Agentを実行するアカウントの権限が重要になります。

  • 専用の非特権ユーザーを使用する。 sudoグループには所属させず、自身のユーザーアカウントとホームディレクトリを共有しないでください。
  • サーバー内に本番環境の認証情報を置かない。 ~/.aws/credentialsに本番用の鍵を保存したり、.envを本番環境からコピーしたりしないでください。重要なデータへの書き込み権限を持つデータベースパスワードも避けてください。Agentには、Staging環境用または読み取り専用の認証情報を渡してください。
  • スコープを限定したトークンを使用する。 特定のリポジトリに限定した細粒度なGitHub tokenや、読み取り権限のみで十分な場合はDeploy keyを使用してください。

Claude Codeには、権限確認のプロンプトを完全にスキップするflagがあります。ノートPCや使い捨てのプロジェクトであれば、使用するかどうかは判断次第です。しかし、トークンを保持するサーバー上でこれを使用すると、誤った指示が git push --force に直結するリスクが生じます。このflagが実際に何を変更するのか、および内蔵のsandboxから使い捨てのVPSに至るまで、このflagで動作するAgentをどのように制御するかについては、サーバーでClaude Codeを安全に実行する方法で解説しています。

Deploy key と SSH agent forwarding の違い

GitでノートPC上の鍵を使用するために、ssh -A したくなるかもしれません。しかし、その権限の内容を理解してください。Agent forwardingを使用すると、ローカルのSSH agentのsocketが、サーバー上でそのユーザーとして実行されるプロセスに公開されます。Agentを含む agent として実行されるプロセスは、接続が維持されている限り、到達可能なあらゆるホストに対して鍵による署名を要求できます。これは単に「このリポジトリをgit pullする」という範囲を大きく超えています。

代わりに、サーバー上で鍵を生成してください。それをリポジトリごとのDeploy keyとして登録し(Agentが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 は必ず覚えておく必要があります。このコマンドは、セッションが存在すればアタッチし、存在しなければ新規作成します。そのため、一日の作業開始と、切断後の再開の両方にこのコマンドを使用できます。エイリアスを設定しておきましょう。セッション内では、Ctrl-b c でウィンドウを開き、Ctrl-b nCtrl-b p でウィンドウを切り替えられます。Ctrl-b [ でコピーモードに入り、過去のログをスクロールできます(q で終了します)。

セッションを終了せずに放置する場合の注意点があります。エージェントはターンごとに会話全体を再送信します。セッションを1週間放置する前に、Claude Code の長時間セッションがトークンをどのように消費するか を確認してください。

Failure modes

"My session is gone." tmux ls prints no server running on /tmp/tmux-1000/default. This nearly always means the process was never inside tmux — you SSH'd in, ran claude directly, and the disconnect killed it. Nothing to recover. The habit that prevents it: tmux new -A -s <project> is the first command after every login.

The pane shrinks to a tiny box. tmux sizes a session to the smallest attached client, so a stale client still attached from another machine squeezes the display. Force the others off as you attach: tmux attach -d -t claude.

A build prints Killed. One word, no stack trace. Confirm with sudo dmesg -T | grep -i -E 'out of memory|killed process' — the kernel OOM killer picked the biggest process. From Node you may instead see FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory. Fixes, in order: add swap (above), cap test and compiler parallelism, raise Node's heap with NODE_OPTIONS=--max-old-space-size=..., or size the VPS up. The OOM killer may also pick the tmux server rather than the build, taking your session with it; if systemd-oomd is running, it can kill a whole user slice with the same effect.

npm error code EACCES / permission denied, mkdir '/usr/lib/node_modules/...'. A global install into a root-owned prefix. Use the ~/.npm-global prefix above. If you already ran sudo npm at some point you may also see Your cache folder contains root-owned files — repair with sudo chown -R $(id -u):$(id -g) ~/.npm.

claude: command not found — but only sometimes. Your PATH export sits in ~/.bashrc below the "If not running interactively, don't do anything" guard, so non-interactive shells skip it. Move the export above that guard and keep it in ~/.bashrc, not ~/.profile: tmux may start non-login shells, which read ~/.bashrc and never touch ~/.profile.

Garbled colours after attaching. A TERM mismatch — the default-terminal line above is the fix.

Sessions vanish after a reboot. Not a bug: the tmux server is a process, and a reboot ends it. Check uptime.

規模拡大に伴う問題点

プロジェクトの増加。 リポジトリごとに名前を付けたtmuxセッションを1つずつ作成します。tmux ls がダッシュボードになります。命名規則を守らないと、セッションが 012 と増えてしまいます。ポートの競合も同様です。6つのリポジトリがすべて :3000 を使用しようとする場合、手動での割り当てはやめるべきです。代わりに Traefik reverse proxy を使用して Docker Compose 上の複数のアプリをホスト名で振り分ける 方法を採用してください。

ユーザーの増加。 tmux socket はユーザーごとに管理されます。そのため、同一マシン上の2人の開発者は、それぞれ独自の tmux server を持ち、お互いのセッションを見ることはできません。共有 socket を介して1つのセッションを共有すると、全員が同じ Unix ユーザーとして同じ shell に入力することになります。これには監査や権限上の問題が伴います。ユーザーを分けることが、確実で標準的な解決策です。

非対話的なジョブ。 tmux は、アタッチして操作する対話的なセッション用です。誰も監視しないスケジュール実行のジョブは、systemd unit と timer で管理すべきです。そうすれば、ログ出力、再起動ポリシー、OS起動時の自動起動が標準で利用できます。cron のようなジョブに tmux を使おうとするのは、そのジョブがサービスとして動作すべきであるという兆候です。

補足事項:エージェントが起動する dev server は、0.0.0.0 ではなく 127.0.0.1 にバインドするように設定し、ufw でポートを開放するのではなく、SSH tunnel (ssh -L 3000:127.0.0.1:3000 agent@your-server) を介してアクセスしてください。6つ以上のポートを転送したり、スマートフォンとノートPCの両方から同時にプレビューを確認したりする場合は、代わりに VPS 上に self-hosted WireGuard VPN を構築 してください。dev server はプライベートインターフェースにバインドし、ufw でパブリックインターフェースからの通信をすべて拒否するようにします。ファイアウォールは、ポートを開放し続けなければ機能しません。

Claude Code だけが選択肢ではありません: VPS 上でコーディング AI エージェントを実行する 方法として、Aider や Goose も検討してください。

FAQ

SSH接続が切断された後も Claude Code は実行され続けますか?

tmux 内で起動した場合のみ実行され続けます。SSH シェルから直接起動したプロセスは、そのシェルの子プロセスであるため、接続が切れると pty と共に終了します。tmux 内であれば、シェルはデタッチされた tmux サーバーに属するため、エージェントはタスクを継続し、tmux attach によって同じスクロールバックに戻ることができます。ログイン後の最初のコマンドとして毎回 tmux new -A -s <project> を実行すれば、この問題は解決します。

sudo npm install -g で CLI をインストールすべきですか?

いいえ。root 所有の global 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 エージェントの socket が、そのユーザーとして実行されるすべてのプロセスに公開されます。そのため、接続を維持している限り、マシン上のあらゆるプロセスが、到達可能な任意のホストに対して、あなたの鍵を使って署名を行うことが可能になります。サーバー上で ed25519 鍵を生成し、リポジトリごとの deploy key として登録してください。エージェントが push を行う必要がある場合に限り、書き込み権限を付与するようにしてください。

ビルド中に Killed とだけ表示されるのはなぜですか?

スタックトレースのない単語のみが表示される場合は、カーネルの 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 を検討している場合は、そのジョブはサービスとして構成すべきです。