[Linux] Debian 12 + Cloudflare Tunnel + ttyd で構築するブラウザ Web ターミナル完全ガイド

[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: ttyd v1.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 を構築する。

  1. 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
  1. Cloudflare ログインとトンネル作成:
cloudflared tunnel login
cloudflared tunnel create debian-ssh
  1. 設定ファイル (~/.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
  1. 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 ダッシュボードで認証プロバイダを設定する。

  1. Cloudflare Zero Trust ダッシュボード ➔ Settings ➔ Authentication(または Access settings)を開く。
  2. Identity providers で Add new を選択し、One-Time PIN (OTP) を追加・保存する。
  3. 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.serviceExecStart 行に -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.serviceExecStart-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

まとめ

  1. インフラ構成: ttyd(ローカル起動)+ cloudflared(トンネル接続)+ Cloudflare Access(PIN認証)で完全ポート全閉じ環境を実現。
  2. ttyd 必須オプション: クライアント操作を受け付けるには -W (--writable) オプションが絶対必須。
  3. セキュリティ: 通信はすべて HTTPS/WSS 暗号化。公開鍵を持ち歩かなくても Zero Trust 認証で同等以上の安全性を確保。
  4. 高速化: login -f username を使えばパスワード入力すら自動化可能。

ポート解放なし・公開鍵の持ち歩きなしで、あらゆる端末のブラウザがそのまま自作 Debian の開発端末化するのは想像以上に快適。

これで出先からの緊急メンテナンス環境も完璧。