Claudeでsysadminのサーバー作業を安全に効率化
Claudeでfailed unitのログ確認、systemd unitの作成、nginxやCompose設定のレビューを行う方法を解説します。秘密鍵や認証情報など、絶対に貼り付けてはいけない4種類の情報も確認できます。
sysadmin 向けの Claude: まず助言を受け、実行はその後に行う
Claude は、sysadmin 向けのレビュー担当として使うと最も効果を発揮します。ログの抜粋、設定ファイル、意味の分からないコマンド、またはエラー文字列を貼り付けると、変更を加える前に確認できる説明が返ります。誤った回答でも、実行するまでは何も起こりません。そのため、モデルを助言の範囲にとどめることが安全性の基本です。
レンタル Linux VPS (virtual private server) では、毎週 6 つの作業が発生します。以下では、それぞれについて有効なプロンプトのパターン、回答を裏付けるコマンド、想定すべき失敗モードを示します。いずれも、モデルがサーバーにアクセスする必要はありません。ブラウザーのタブや自分のデスクトップ上のウィンドウから貼り付けられます。これは、Claude は Linux 上で desktop app と CLI の両方としてネイティブに動作するためです。
本番サーバーでは、実行する順序が重要です。説明を読み、自分で確認コマンドを実行し、その後で判断します。scratch VM では自律的に実行しても問題ありません。顧客向けサービスを提供するサーバーでは、レビューを優先します。モデルは、推測の対象となる実際の状態を確認できないためです。
貼り付けてはいけないもの
プロンプトに入力した内容はすべてサーバーの外部に送られます。次の4種類の情報はサーバー内に留めてください。
- 秘密鍵:
~/.ssh/id_ed25519、/etc/ssh/ssh_host_*_key、および/etc/letsencrypt/live/配下の TLS (transport layer security) 鍵。 - 認証情報ファイル:
.env、~/.aws/credentials、/root/.docker/config.json、および任意のファイルやログ行に含まれるデータベースのパスワード。 - アカウントデータ:
/etc/shadowと/etc/gshadow。sysadmin に関する質問への回答に、パスワードハッシュは必要ありません。 - ユーザーに属する情報: メールアドレス、注文データ、セッション Cookie や PII (personally identifiable information) を含むリクエストログなど。
公開鍵は貼り付けても安全です。秘密鍵は貼り付けてはいけません。両方のファイルは一見すると似ているため、コピーする前に1行目を確認してください。1行目に BEGIN OPENSSH PRIVATE KEY が含まれるファイルは、プロンプトに入力してはいけません。SSH 鍵マテリアルを正しく管理するための確認には、10分かける価値があります。
200行の中から1つのトークンを見つけられると考えず、貼り付ける前に秘匿化してください。
sudo journalctl -u myapp -n 200 --no-pager | sed -E 's/(token|secret|password|api[_-]?key)[=:][^[:space:]]+/\1=REDACTED/gI'Docker 固有の落とし穴もあります。docker compose config は、出力時に .env の値を展開するため、ディスク上のファイル自体に秘密情報が含まれていなくても、その出力は秘密情報になります。何も出力せずに検証する docker compose config -q を使用してください。エージェントが参照してよい情報に関する全体方針については、AI エージェントから秘密情報を除外するで環境側の対策を説明しています。
ジョブ 1: このサービスはなぜ失敗したのか
答えが含まれている、次の2つのコマンドから始めます。
systemctl status myapp.service --no-pager
sudo journalctl -u myapp.service -b -n 100 --no-pager -o short-isoモデルが推測できない情報も添えて、両方の出力を貼り付けます。ディストリビューションとバージョン、最後に変更した内容、以前に動作していたかどうか、壊れてからの経過時間を含めます。まず原因の仕組みを尋ねます。
Ubuntu 24.04 です。myapp.serviceは、1時間前に unit を編集するまでは正常に動作していました。systemctl statusと、直近100行の journal を貼ります。最初の本当のエラーはどの行で、何を意味しますか。まだ修正方法は不要です。
このプロンプトでは、「まだ修正方法は不要です」が重要な役割を果たします。ログでは、最初の障害によって発生した再試行の下に、最初の失敗が埋もれます。修正方法を尋ねると、モデルは見つけた最後の行を説明しがちです。重要な行は通常、ノイズの20行ほど上にあります。
得られるのは、Main PID: 1841 (code=exited, status=203/EXEC) のような行です。終了ステータス 203/EXEC は、ExecStart に指定されたファイルを kernel が実行できなかったことを示します。パスが存在しないか、ファイルは存在するものの実行可能ではありません。インストールされていない interpreter を示す #! の行でも、同じステータスになります。これらはすべて ls -l と head -1 で確認できます。
失敗パターンは、原因の捏造です。貼り付ける情報が少なすぎると、モデルは「ポートがすでに使用中です」のような一般的な原因で空白を補います。その場合は、次の質問を返します。「私が提示した情報のどの行が、それを裏付けていますか」。本文中のどこも指し示せない原因は、推測です。
systemd unit または cron エントリを作成する
unit ファイルに必要な情報を指定します。正確なコマンド、実行ユーザー、作業ディレクトリ、ネットワークを待つ必要があるかどうか、終了ステータスが 0 以外の場合の処理を明記します。そのうえで、何が返るかを確認してから有効化します。
sudo systemd-analyze verify /etc/systemd/system/myapp.service
sudo systemctl daemon-reload
sudo systemctl start myapp.service
systemctl status myapp.service --no-pagersystemd-analyze verify は systemd と同じ方法でファイルを解析するため、人間が見落としやすい問題を検出できます。ディレクティブのスペルミスがあると /etc/systemd/system/myapp.service:7: Unknown key name 'Enviroment' in section 'Service', ignoring. を出力します。バイナリが見つからない場合は Command /usr/local/bin/myapp is not executable: No such file or directory を出力します。どちらも daemon-reload の間は表示されません。そのため、unit は正常に読み込めても、実行した瞬間に失敗することがあります。
作成時によくある間違いは 2 つあります。1 つ目は After=network.target です。これはネットワークスタックの設定が完了したことを示すだけで、アドレスがすでに存在することは意味しません。特定の IP に bind するサービスは、起動時に bind: Cannot assign requested address で失敗します。修正には Wants=network-online.target と After=network-online.target を併用します。2 つ目は、daemonize するプログラムに Type=simple を指定することです。systemd は最初のプロセスをサービスとして扱いますが、親プロセスはすぐに終了します。その結果、unit は停止したと判断され、実際のプロセスだけが管理されないまま動き続けます。コマンドを見るだけでは、バイナリが fork するかどうかをモデルは判断できないため、この間違いを提示しやすくなります。作成案を受け入れる前に、各 Type= 値が systemd に約束する内容を確認してください。
スケジュールは読むのではなく、検証します。
systemd-analyze calendar 'Mon *-*-* 04:00:00'これにより正規化された形式と、式が次に実行される時刻が表示されます。式の意味についての議論を解消できます。timer と crontab のどちらを選ぶか検討している場合は、VPS 上の systemd services と timers で違いを確認できます。
cron には、質問しない限りモデルが警告しない落とし穴があります。cron は最小限の環境でジョブを実行するため、PATH はおおむね /usr/bin:/bin となり、shell の profile は読み込まれません。端末に貼り付けて実行すると動くジョブが、cron では /bin/sh: 1: docker: not found で失敗することがあります。そのバイナリが /usr/local/bin に存在するためです。crontab では絶対パスを使用してください。単一の crontab 行と比べて、unit ファイルでユーザー、環境、依存関係を明示することが過剰に感じられる場合は、systemd が解決するために作られた問題で、その冗長さの背景を確認できます。
ジョブ 3: nginx または Compose ファイルを公開前に確認する
このジョブは最も効果があります。ファイルを貼り付け、想定している動作を説明し、実際の動作を1行ずつ確認するよう依頼します。
この vhost はexample.comを HTTPS で提供し、/apiを port 8080 のローカルサービスにプロキシする想定です。内容を読み返し、この説明と一致しない点を挙げてください。
次に、文法を検証できるツールを実行します。
sudo nginx -t
docker compose config -qnginx -t は nginx: configuration file /etc/nginx/nginx.conf test is successful を出力するか、nginx: [emerg] unknown directive "proxy_pas" in /etc/nginx/conf.d/app.conf:12 のようにファイル名と行番号を示します。ファイルの解析に成功した場合、docker compose config -q は何も出力しません。インデントを誤ると、yaml: line 7: did not find expected key のような明確なエラーが表示されます。
どちらのツールも意図は確認しません。nginx -t を通過した設定でも、誤った port にプロキシしたり、127.0.0.1 のつもりが 0.0.0.0 で待ち受けたりすることがあります。この差分こそ、モデルを使う価値がある部分です。同時に、モデルが失敗する部分でもあります。1つの directive の修正を依頼すると、2つの directive を気付かないうちに削除したファイル全体を書き直して返すことがよくあります。変更した行と、それぞれの理由だけを提示するよう依頼し、手作業で編集してください。
実際に公開した内容を確認します。
sudo ss -tulpnsudo がない場合、待ち受け中の socket は確認できますが、それを所有する process は確認できません。出力に想定外の内容があれば、port の役割と Linux による bind の仕組みのほうが短く読めます。
ジョブ 4: 不慣れなコマンドを実行する前に説明させる
コマンドを貼り付け、次の4点を質問します。各フラグは何をするのか、何を書き込むのか、何を削除するのか、2回実行するとどうなるのかです。最後の質問は、ほかの質問より多くの被害を防ぎます。
find /var/log -name '*.gz' -mtime +7 -deleteを例にします。適切な回答では、-mtime +7が24時間単位の経過日数を数え、端数を切り捨てることを説明します。そのため、7日以上前ではなく、少なくとも8日前のファイルに一致します。また、findは式を左から右へ評価するため、-deleteを-nameの前に移動すると、開始パス以下のすべてが削除されることも説明します。この点はfindの man ページに警告として記載されており、実際に人々の/var/logを失わせてきました。
rsync -a --delete /srv/app/ /backup/app/も例になります。ソースの末尾にあるスラッシュは「このディレクトリの内容」を意味します。これを省くと、/backup/app/app/になります。--deleteを追加すると、ソースに存在しない宛先側の項目が削除されます。これはミラーには正しい動作ですが、ソースパスが間違っている場合は致命的です。
モデルではなく、ツールで検証します。
rsync -a --delete --dry-run /srv/app/ /backup/app/ | head -20
find /var/log -name '*.gz' -mtime +7-deleteを付けずにfindを実行すると、削除せずに一覧を取得できます。
失敗パターン: フラグの捏造。モデルは30年分の文書があるツールについては信頼できますが、ベンダーの CLI(コマンドラインインターフェイス)や最近追加されたサブコマンドについては精度が下がります。実在しないフラグを、もっともらしく生成することがあります。--helpなら、1秒で確認できます。引用も弱点になりやすいため、コマンドが$(...)式を展開する場合は、説明をそのまま信じず、実行前にコマンド置換がどのように展開されるかを確認してください。
Job 5: シェル履歴をランブックに変える
何かを動作させるために、2 時間かけて作業したところです。その知識はスクロールバックに残っていますが、来月には消えています。
history 200 > /tmp/session.txtそのファイルを読み、パスワード、トークン、顧客識別子を含む行をすべて削除してから、他の場所に移してください。Linux マシンでシークレットを見つける場所として、シェル履歴は非常に信頼できます。誰もが少なくとも 1 回は、シークレットをコマンドに直接入力するからです。~/.bashrc に HISTCONTROL=ignorespace を設定すると、先頭にスペースを付けて入力したコマンドは履歴に一切書き込まれません。
実用的なランブックを作成するプロンプトでは、手順だけでなく確認も求めます。
これは、まっさらな Debian 13 のマシンを動作する Postgres 環境にしたときのシェルセッションです。番号付きのランブックとしてまとめてください。1 ステップにつき 1 コマンドにしてください。各ステップの後に、動作を証明するコマンドを示し、正常な出力の内容を説明してください。私の特定のホストに依存したステップには印を付けてください。
失敗しやすいのは、整いすぎた説明になることです。作業中には、修正するまで 2 回間違えたステップがありました。しかし、トランスクリプトからそのステップを除くと内容がきれいになるため、モデルはその経緯を省きます。ランブックと履歴を比較し、その修正内容を戻してください。また、モデルはもっともらしい確認コマンドを作ることもあります。そのため、ファイルを保存する前に、生成されたすべての確認を実行してください。初回起動を扱うランブックであれば、新しい VPS の最初の 10 分と照合し、解決済みの問題をより悪い手順として記録しないようにしてください。
ジョブ 6: エラーメッセージを解決策に変える
正確なエラー文字列、そのエラーを出力したコマンド、エラーが出る前に変更した唯一の点を貼り付けます。原因を可能性の高い順に並べ、各原因を判定できるコマンドを1つずつ示すよう求めます。これにより、回答を検証可能な内容にできます。
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)。可能性の高い原因を順に並べ、各原因を確認または除外できるコマンドを1つずつ示してください。
このエラーの仕組みは明確です。別のプロセスがすでに port 80 を使用しており、sudo ss -tulpn | grep ':80 ' がそのプロセスを特定します。よくあるのは、reload の失敗後に2つ目の nginx master が残っているケースです。または、Apache が依存関係として導入され、独自の package によって起動されている場合もあります。
失敗のパターンは、原因を隠すことで機能する修正です。chmod 777、--privileged、SELinux の無効化、サービスの root 実行はいずれもエラーを消します。モデルが狭い権限で失敗した理由を説明するまで、権限を広げる修正は受け入れないでください。その説明こそが実際の回答です。回避策ではエラーが表面化しなくなるだけです。
正確に誤る点
- 実際のサーバーを確認できません。回答は貼り付けられた内容だけを基に生成され、抜粋が短すぎても、そのことを指摘しません。
- バージョンによって内容がずれます。パッケージ名やデフォルトのフラグはディストリビューションやリリースによって変わりますが、モデルはそれらすべてを平均した内容を返します。
- 間違っていても流暢です。幻覚による仕組みの説明も正しい説明とまったく同じように読めます。そのため、上記の各原因には、それを検証するコマンドを併記しています。
- 長いセッションでは話の流れを失います。2 時間に及ぶ会話の冒頭にある事実が、末尾の回答に反映されなくなります。
最後の点はモデル自体の問題というより、作業上の問題です。実用的な対策は、長い Claude Code セッションでコンテキストを管理することです。セッションを短くし、1 回につき 1 つのタスクだけを扱います。
サーバー上でエージェントを実行する
ここまでの内容はすべてコピーして貼り付けるだけなので、モデルがマシンに触れることはありません。エージェントがサーバー上でファイルを読み取り、コマンドを実行するようになると、リスクの性質が変わります。誤ったコマンドによって、サービスが停止する可能性があります。root ではなく専用の非特権ユーザーを割り当ててください。動作の傾向を把握するまでは本番サーバーで実行せず、最初にスナップショットを取得してください。VPS で Claude Code を安全に実行するでは、サンドボックス化と権限モデルについて説明しています。tmux 内で Claude Code を操作するでは、もう一つの問題に対処します。SSH(secure shell)セッションが切断されると、フォアグラウンドで動作中のエージェントも作業の途中で終了するためです。アカウントはサービスアカウントと同じ考え方で作成してください。詳しくは VPS での最小権限ユーザーを参照してください。
FAQ
Claude はサーバーのログを直接読み取れますか?
単独ではできません。チャットインターフェースから見えるのは、貼り付けたテキストだけです。サーバー上でコマンドラインツールとして実行する Claude Code は、起動したユーザーの権限でファイルを読み取り、コマンドを実行できます。そのため、信頼に関する判断がより大きくなります。通常のサポート質問であれば、agent に shell access を与えるより、編集して秘匿情報を除いた 100 行の抜粋を貼り付けるほうが速く、安全です。
サーバーから何を貼り付けてはいけませんか?
Private keys、.env ファイルなどの認証情報ストア、/etc/shadow、およびユーザーに属するデータは貼り付けないでください。ログの抜粋を prompt に渡す前に、token を編集して削除します。見落としやすい例として、docker compose config の出力には .env の値が展開されます。そのため、ファイルを検証し、何も出力しない docker compose config -q を使用してください。
Claude に本番環境の VPS でコマンドを実行させても安全ですか?
状況を把握していない新しい管理者と同じように扱ってください。読み取りは問題ありませんが、書き込みには確認が必要です。本番環境では、説明を求めたうえで、自分でコマンドを実行してください。agent に実行させる場合は、sudo を包括的に許可しない専用の非特権アカウントを与え、まず staging 環境で開始します。そこでのミスなら、障害ではなく再構築で対応できます。
Claude が存在しない flag を提案するのはなぜですか?
Claude はもっともらしいテキストを予測するためです。もっともらしい flag は、実在する flag と見分けがつきません。vendor CLI や新しい subcommand で特に起こりやすく、モデルが参照したドキュメントが少ないか、その後に変更されていることが原因です。--help と man を基準に確認し、削除または上書きを行うコマンドは、まず dry run を実行してください。
有効化する前に systemd unit を確認するにはどうすればよいですか?
sudo systemd-analyze verify /etc/systemd/system/myapp.service を実行します。systemd 自身の parser でファイルを解析し、未知の directive を行番号付きで報告します。また、存在しない、または実行可能でない ExecStart binary にもフラグを付けます。次に daemon-reload と start を実行し、systemctl status を確認してから enable します。正常に読み込める unit でも、初回実行時に失敗することがあるためです。