「自宅PCでもタブレットでも、ブラウザだけで自分のサーバーに入って Claude Code を動かしたい」——そう思って最初はブラウザ版VS Code(code-server)を入れたのですが、メモリを約290MBも食うため、低スペックなサーバーでは重すぎました。
そこで乗り換えたのが ttyd。消費メモリは約8MB(code-serverの約1/35)の、ブラウザにターミナルだけを出す軽量ツールです。Claude Code はターミナルで動くので、これで必要十分でした。
ただしWebターミナル=ブラウザからシェルが触れる=実質リモートのフルアクセスです。雑に公開すると非常に危険なので、この記事では HTTPS+2段認証+レート制限+セッション永続化(tmux) まで含めた、安全な構築手順を実体験ベースでまとめます。
⚠️ 注意:本記事は自分が管理するサーバーで行う前提です。Webターミナルはシェルへの入口になるため、必ず認証・暗号化をかけ、信頼できる人だけがアクセスできる状態にしてください。
全体像:何をどう組むか
構成はシンプルです。
ブラウザ ──HTTPS──▶ nginx(リバースプロキシ+認証)──▶ ttyd(127.0.0.1で待受)──▶ シェル
- ttyd はローカル(127.0.0.1)だけで待ち受け、直接は外に出さない。
- 外部公開とTLS・認証は nginx に任せる。
- ブラウザを閉じても作業が消えないよう tmux で永続化。
ポイントは「ttydを直接インターネットに晒さない」こと。必ずnginx(HTTPS+認証)の内側に置きます。
ttyd を導入する
AlmaLinux / RHEL系なら EPEL から入ります(Debian/Ubuntu は apt install ttyd)。
sudo dnf install -y ttyd
ttyd --version
systemd サービスとして常駐させます。ローカル(127.0.0.1)だけで待ち受け、外向きは後段のnginxに任せます。
# /etc/systemd/system/ttyd.service
[Unit]
Description=ttyd web terminal
After=network.target
[Service]
Type=simple
User=youruser
WorkingDirectory=/home/youruser
# -i 127.0.0.1 : ローカルのみ待受 / -b /webterm : サブパス公開 / -W : 入力可
ExecStart=/usr/bin/ttyd -p 7681 -i 127.0.0.1 -b /webterm -W /bin/bash -l
Restart=on-failure
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now ttyd
この時点ではローカルでしか繋がりません。次にnginxで安全に公開します。
nginx で HTTPS 公開+Basic認証
すでに Let’s Encrypt などで HTTPS が動いているサーバー前提です。該当サーバーブロックに、次の location を足します。
location /webterm/ {
# 1段目:Basic認証(bcryptの強いパスワードを推奨)
auth_basic "Secure Terminal";
auth_basic_user_file /etc/nginx/.htpasswd-webterm;
# WebSocketを通すための設定(これが無いと画面が出ない)
proxy_pass http://127.0.0.1:7681;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection upgrade;
proxy_read_timeout 86400s;
}
Basic認証用のパスワードは、bcrypt(-B)で強めに作ります。
# user名・パスワードは自分のものに置き換え
sudo htpasswd -cB /etc/nginx/.htpasswd-webterm youruser
sudo chown root:nginx /etc/nginx/.htpasswd-webterm
sudo chmod 640 /etc/nginx/.htpasswd-webterm
反映します(設定変更は reload で十分。restart は不要)。
sudo nginx -t && sudo systemctl reload nginx
これで https://example.com/webterm/ にアクセスすると、Basic認証を経てターミナルが開きます。
セキュリティTip:パスは
/webterm/のような分かりやすい名前より、推測されにくい固有のパス(例:/webterm-3f9a2/)にしておくと、無差別スキャンに見つかりにくくなります(ttydの-bと nginx のlocationを揃えるだけ)。ハマりどころ:WebSocketのヘッダ(Upgrade / Connection)を入れ忘れると、ページは出るのに画面が真っ黒になります。
/webterm/wsが確立できているか(ステータス101)をアクセスログで確認すると切り分けが早いです。
セキュリティを固める(Webシェルなので必須)
Basic認証だけでも一応動きますが、シェルへの入口である以上、多層で守るべきです。私は次の4層にしました。
1段目:強いBasic認証(上で設定済み)
bcryptの強パスワード。使い回しは厳禁。
2段目:ターミナル側でOSパスワードを要求する
ttyd の起動コマンドを ログインを挟む形にすると、ブラウザ認証の先でもう一度OSパスワードを要求できます(=2重パスワード)。
# 非rootユーザーで su を使うと、必ずパスワードを要求される
ExecStart=/usr/bin/ttyd -p 7681 -i 127.0.0.1 -b /webterm -W /bin/su -l youruser
補足:
/bin/loginを使う方法もありますが、環境によってはターミナルにプロンプトが描画されず画面が真っ黒になることがあります。私は非rootユーザーでsu -l <自分>にしたら、Password:が確実に表示されて安定しました(通常のシェル出力なので描画が素直)。
3段目:レート制限(総当り対策)
nginx の limit_req で、/webterm/ への過剰なアクセスを絞ります。
# httpコンテキスト
limit_req_zone $binary_remote_addr zone=webterm_limit:10m rate=1r/s;
# location /webterm/ の中
limit_req zone=webterm_limit burst=5 nodelay;
4段目:fail2ban で自動BAN
認証失敗を繰り返すIPを自動でBANします。nginx-http-auth のジェイルを有効化し、対象ログにそのサーバーブロックのエラーログが含まれているか確認しておきます。
⚠️ 落とし穴:ttydは1ページで
/webterm/・/webterm/ws・/webterm/tokenと複数の認証リクエストを出します。パスワードを間違えると一気に数回失敗 → 自分まで即BANされがちです。自分の固定IPは fail2ban のignoreipに入れておくと安心です(回線でIPが変わる場合は都度更新)。
ブラウザを閉じても作業を消さない(tmux)
ttyd はブラウザ接続が切れると、その端末のプロセスを終了します。つまりブラウザを閉じると、動かしていた Claude Code も巻き込んで終了します(長時間の作業だと致命的)。
これを防ぐのが tmux。起動コマンドを「シェル → tmux に自動アタッチ」にしておくと、ブラウザを閉じてもサーバー側で動き続け、開き直すと続きからになります。
sudo dnf install -y tmux
起動コマンドを tmux 経由にします(2段目のOSパスワードも維持)。systemd に直接長いコマンドを書くとクォート解釈で事故りやすいので、ラッパースクリプトにするのが確実です。
# /usr/local/bin/ttyd-session.sh
#!/bin/bash
exec /bin/su -l youruser -c 'exec tmux new-session -A -s main'
# ttyd.service の ExecStart
ExecStart=/usr/bin/ttyd -p 7681 -i 127.0.0.1 -b /webterm -W /usr/local/bin/ttyd-session.sh
これで、
- ブラウザを閉じる=自動でデタッチ(裏で動き続ける)
- 開き直すと同じセッション
mainに再アタッチ=続きから - PCとタブレットなど複数端末から同じ画面に同時接続も可能
になります。tmuxの最低限の操作だけ覚えておくと快適です。
- スクロール:
Ctrl+b→[→(矢印/PageUp)→qで戻る - 手動デタッチ:
Ctrl+b→d
code-server / Remote Control との使い分け
ブラウザからClaude Codeを触る方法は他にもあります。ざっくり比較すると:
| 方法 | 消費メモリ | 向いている人 |
|---|---|---|
| ttyd(本記事) | 約8MB | 軽さ重視・低スペック・自分でサーバー管理できる人 |
| code-server(ブラウザ版VS Code) | 約290MB | エディタUIごとブラウザで使いたい人・余裕あるサーバー |
| 公式 Remote Control アプリ | (端末側アプリ) | Pro/Maxの個人ユーザー・スマホ/タブレットで手軽に |
私は「低スペックサーバーで、ターミナルさえ出れば十分」だったので ttyd を選びました。エディタのGUIが要るなら code-server、サブスクで手軽に、なら公式のRemote Control、という住み分けです。
まとめ
- ttydなら、ブラウザにターミナルだけを約8MBで出せる(code-serverの約1/35)。低スペックでも軽快。
- 構成は ブラウザ → nginx(HTTPS+認証)→ ttyd(ローカル)→ シェル。ttydは直接外に出さない。
- Webシェルは危険なので、Basic認証+OSパスワード(2段)+レート制限+fail2banで固める。
- tmuxで永続化すれば、ブラウザを閉じても Claude Code の作業は消えない。
ターミナルベースで動く Claude Code とは相性抜群です。低スペックサーバーで運用しているなら、ぜひ。
あわせて読みたい:
・低スペックサーバーでClaude Codeが重い・固まる時のメモリ対策(軽量化の実体験)
・Remote Control を使ってみた感想(公式アプリでの操作)
・Claude Code 1ヶ月のトークンコストを実測(コストの実測)


コメント