[Linux] Debian 12 + Cloudflare Tunnel + ttyd で構築するブラウザ Web ターミナル完全ガイド
はじめに
「出先からスマホやブラウザで自宅の Debian サーバーをさくっと操作したい。」
「でもポート解放は絶対にしたくないし、暗号化も認証も厳重に固めたい。」
そんなワガママを叶えるべく、Web ターミナルエミュレータである ttyd と Cloudflare Tunnel + Cloudflare Access (Zero Trust) を組み合わせて Web ターミナル環境を構築した。
本記事では、Debian 12 への ttyd インストールから systemd サービス化、Cloudflare Tunnel の開通、Zero Trust 認証の設定までの全構築手順と、途中で踏み抜いた「画面は映るのに一切キー入力を受け付けない」というトラップの処方箋をまとめて残しておきます。
開発環境・前提条件
- OS: Debian GNU/Linux 12 (bookworm) x86_64
- ドメイン: Cloudflare で管理済みの独自ドメイン(
lain-lab.com) - Web Terminal:
ttydv1.7.x - 認証・トンネル:
cloudflared+ Cloudflare Access (One-Time PIN 認証)
システム構成概要
[ ブラウザ / クライアント ]
│ (HTTPS / WebSockets)
▼
[ Cloudflare Access ] ── (メール 6桁PINコードによる Zero Trust 認証)
│
▼
[ Cloudflare Tunnel (cloudflared) ]
│ (ローカル暗号化通信: 127.0.0.1:7681)
▼
[ Debian 12 : ttyd ] ── (Linux login プロンプト)
自宅ルーターの 22 番ポートや 80/443 番ポートを一切開放せず、ブラウザから SSL/TLS で暗号化された WebSockets 通信経由でターミナルにアクセスする構成。
構築手順
Step 1: ttyd のインストールと systemd サービス化
まずは Debian サーバー上に Web ターミナル本体となる ttyd を導入する。
# 依存パッケージのインストール
sudo apt update
sudo apt install -y build-essential cmake git libjson-c-dev libwebsockets-dev
# ttyd の取得とビルド
git clone https://github.com/tsl0922/ttyd.git
cd ttyd && mkdir build && cd build
cmake ..
make && sudo make install
常時稼働させるため、systemd のサービス定義ファイルを作成する。
sudo nano /etc/systemd/system/ttyd.service
以下の内容を記述して保存:
[Unit]
Description=ttyd Web Terminal
After=network.target
[Service]
# -i でローカルホスト限定、-W で書き込み許可、-p でポート指定
ExecStart=/usr/local/bin/ttyd -i 127.0.0.1 -W -p 7681 login
Restart=always
User=root
[Install]
WantedBy=multi-user.target
サービスの有効化と起動を実行:
sudo systemctl daemon-reload
sudo systemctl enable --now ttyd
Step 2: Cloudflare Tunnel ( cloudflared ) のセットアップ
サーバーのポートを開放せずに外部から安全に接続するため、Cloudflare Tunnel を構築する。
cloudflaredのインストール:
curl -L --output cloudflared.deb https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb
sudo dpkg -i cloudflared.deb
rm cloudflared.deb
- Cloudflare ログインとトンネル作成:
cloudflared tunnel login
cloudflared tunnel create debian-ssh
- 設定ファイル (
~/.cloudflared/config.yml) の作成:
tunnel: <TUNNEL-UUID>
credentials-file: /root/.cloudflared/<TUNNEL-UUID>.json
ingress:
- hostname: ssh.lain-lab.com
service: [http://127.0.0.1:7681](http://127.0.0.1:7681)
- service: http_status:404
- DNS ルーティングの追加とサービス起動:
cloudflared tunnel route dns debian-ssh ssh.lain-lab.com
sudo cloudflared service install
sudo systemctl start cloudflared
Step 3: Cloudflare Access (Zero Trust) による保護設定
誰でもターミナル画面にアクセスできてしまう状態を防ぐため、Cloudflare ダッシュボードで認証プロバイダを設定する。
- Cloudflare Zero Trust ダッシュボード ➔ Settings ➔ Authentication(または Access settings)を開く。
- Identity providers で Add new を選択し、One-Time PIN (OTP) を追加・保存する。
- Access ➔ Applications から新規アプリケーションを追加:
- Application type: Self-hosted
- Application domain:
ssh.lain-lab.com - Policy: Create a policy(Action: Allow, Include: メールアドレスまたはドメイン制限)
これで https://ssh.lain-lab.com にアクセスした際、6 桁の PIN コード認証を通過しない限りターミナルが表示されない仕組みが完成する。
踏み抜いたハマりポイント・トラブルシューティング
トラップ1: 画面は映るのにキー入力が一切反応しない(Read-Only罠)
Cloudflare Access の認証を突破し、無事に lain login: の画面が表示された。
「勝った!」と思いキーボードを叩くも、カーソルがピクリとも動かない。日本語入力(IME)を試すと候補ウインドウは出るものの、確定すると文字が消滅する。
lain login: aaaaaaa^[OQ ← 文字を入力しても一切描画・送信されない
原因:ttyd はデフォルトで「閲覧専用(Read-Only)」で動作する
ttyd はデフォルトの起動オプションだとクライアントからの入力(書き込み)を一切許可しない仕様になっている。そのため、画面の出力ログだけがブラウザに届き、こちらからのキー入力パケットはすべてサイレントに破棄されていた。
解決策:起動引数に -W (--writable) オプションを追加する
/etc/systemd/system/ttyd.service の ExecStart 行に -W を追加する。
# -W (または --writable) を追加して書き込みを許可する
ExecStart=/usr/local/bin/ttyd -i 127.0.0.1 -W -p 7681 login
設定反映:
sudo systemctl daemon-reload
sudo systemctl restart ttyd
これでブラウザからの文字入力や Enter 送信が正常に通るようになる。
トラップ2: 「SSH公開鍵なし・パスワード入力」の安全性についての誤解
Web ターミナルを開くと Linux 標準の login: プロンプトが出現するため、「Web ブラウザからパスワードを入れるとネットワーク上に平文で流れて危険なのでは?」と不安になる。
解決策:Cloudflare 側で TLS 暗号化 + Zero Trust 認証を挟む
実際には、ブラウザと Cloudflare 間の通信はすべて HTTPS / WebSockets (WSS) で SSL/TLS 暗号化されている。そのため、Wi-Fi 盗聴や経路上のパケット解析でパスワードが裸で漏洩する心配はない。
さらに、手前に Cloudflare Access を配置してログイン保護をかけているため、「SSH の公開鍵認証」の代わりに「Cloudflare の Zero Trust 認証」が堅牢な壁として機能している。
運用テクニック: Cloudflare Access 認証を活かした Linux パスワードの自動スキップ
Cloudflare Access 側ですでに本人確認が完了しているため、アクセスした瞬間に Linux のパスワード入力を省略して直接ログインさせたい場合がある。
その場合は、ttyd の起動コマンド末尾に login -f <ユーザー名> を指定すればOK。
sudo systemctl edit --full ttyd.service
# lain ユーザーでパスワード入力をスキップして自動ログイン
ExecStart=/usr/local/bin/ttyd -i 127.0.0.1 -W -p 7681 login -f lain
サービスを再起動(sudo systemctl restart ttyd)すれば、URL を開いた瞬間にシェル(lain@lain:~$)が立ち上がる爆速環境が完成する。
Web UI 統合:親要素の壁紙を透過表示させる xterm.js テーマ設定
ttyd を Web アプリケーションの iframe 等に埋め込んで表示する際、親要素の壁紙画像やバックグラウンドグラデーションを透けさせるには、ttyd 起動時に xterm.js のテーマオプションで背景色にアルファ値(透明度)を指定する必要があります。
デフォルト状態ではキャンバス背景が不透明な黒(#000000)で塗られており、フロントエンドの CSS 側で backgroundColor: transparent を指定しても透過されません。
1. systemd サービスファイルの編集
/etc/systemd/system/ttyd.service の ExecStart に -t theme='{"background": "rgba(0, 0, 0, 0.45)"}' を追記します。
[Unit]
Description=ttyd Web Terminal
After=network.target
[Service]
ExecStart=/usr/local/bin/ttyd -i 127.0.0.1 -W -p 7681 -t theme='{"background": "rgba(0, 0, 0, 0.45)"}' login
Restart=always
User=root
[Install]
WantedBy=multi-user.target
2. 設定の反映と再起動
sudo systemctl daemon-reload
sudo systemctl restart ttyd
まとめ
- インフラ構成:
ttyd(ローカル起動)+cloudflared(トンネル接続)+Cloudflare Access(PIN認証)で完全ポート全閉じ環境を実現。 ttyd必須オプション: クライアント操作を受け付けるには-W(--writable) オプションが絶対必須。- セキュリティ: 通信はすべて HTTPS/WSS 暗号化。公開鍵を持ち歩かなくても Zero Trust 認証で同等以上の安全性を確保。
- 高速化:
login -f usernameを使えばパスワード入力すら自動化可能。
ポート解放なし・公開鍵の持ち歩きなしで、あらゆる端末のブラウザがそのまま自作 Debian の開発端末化するのは想像以上に快適。
これで出先からの緊急メンテナンス環境も完璧。