🚀

Cloudflare × Tailscale × ラズパイで作る超高速・ポート非公開なリモートWoL構成

に公開

はじめに

外出先からメインPCを起動したい。そのための手段として「VPN(L2TPやOpenVPN)を張ってからマジックパケットを投げる」というのは、あまりにも手数が多く、かつ「だるい」作業です。

本記事では、「ポート開放なし」「VPN接続待ちなし」、そして「独自ドメインへのHTTPS POST 1発」で自宅のPCを点火する、ゼロトラスト時代のWoL(Wake-on-LAN)構成を紹介します。


1. 目指した体験(UX)

  • 爆速: iPhoneのホーム画面からアイコンを叩くだけ。なんなら「Hey Siri」で済む。
  • シームレス: 自宅Wi-FiかLTEかを意識する必要なし。
  • セキュア: 独自ドメインで公開するが、自分以外のリクエストはラズパイに届きすらしない。
比較項目 境界防御型(従来のVPN) ゼロトラスト型(本構成)
プロトコル L2TP / IPsec / OpenVPN HTTPS (TLS 1.3) / WireGuard
認可(AuthZ) ネットワーク全体へのアクセス Service Tokenによる特定エンドポイント制御
可用性 グローバルIPの変化に弱い Anycastネットワークによる安定接続
FW設計 外部へポートを晒すリスクあり iptablesによるインバウンド全面拒否

2. システム構成:ダブル・トンネル戦略

本構成のキモは、役割の異なる2つのトンネルを併用することにあります。

  • Cloudflare Tunnel (Public Gate): 外部からのHTTPトリガー用。
    Service Token認証により、ブラウザログインなしでAPI的に叩けるようにします。
     ※ Cloudflare側の設定は別記事にて公開予定です。
  • Tailscale (Private Management): 内部管理および緊急時のバイパス用。
    IP直打ちによる高い安定性を確保します。

3. 技術スタックのこだわり

① Cloudflare Access の Service Token

通常、Cloudflare Accessで保護されたURLにアクセスすると「メール認証」などの画面が出て自動化が阻害されます。これを回避するためにService Tokenを発行し、iOSショートカットからのリクエストにカスタムヘッダ(Client ID / Secret)を仕込むことで、シームレスに認証させます。

② iptables による「OSレベルのゼロトラスト」

「VPNの中だから安全」という過信を捨てます。ラズパイのOSレベルでファイアウォール(iptables)を設定し、Webhookポート(9000)へのアクセスを以下に限定します。

  • lo(ループバック:同一ホスト内のCloudflare Tunnel用)
  • tailscale0(Tailscale経由のセキュアな通信)
    それ以外(192.168.x.x 等)からのアクセスは、たとえLAN内であっても全て DROP します。

4. 構築スクリプト:One-Tap Ignition

環境構築を「パッケージの導入からデーモン登録、FW設定までを1発で終わらせるスクリプト ignite.sh 」としてまとめました。

[!NOTE]
ユーザー名やパス、MACアドレスを自動取得して systemd サービスを動的に生成するように設計しています。

ignite.sh
#!/bin/bash
set -euo pipefail

# ==========================================
# 構成設定
# ==========================================
# 起動対象PCのMACアドレス
TARGET_MAC="XX:XX:XX:XX:XX:XX"

# Webhookを待ち受けるポート(標準: 9000)
WEBHOOK_PORT="9000"

# Cloudflaredを別ホストで運用している場合に、そのIPをホワイトリストに追加(任意)
# 同一ホスト内でのみ利用する場合は空のままで問題ありません
CLOUD_FLARED_SRC_IPV4=""
CLOUD_FLARED_SRC_IPV6=""

# 各種パス設定
USER_NAME="$(whoami)"
BASE_DIR="$HOME/webhooks"
SERVICE_FILE="/etc/systemd/system/webhook.service"

echo "--- 🛠️ Tailscale & WoL Environment Setup Start ---"

# ------------------------------------------
# 1. Tailscaleの導入
# ------------------------------------------
# VPNレイヤーの構築。ポート開放なしでセキュアな通信路を確保します
if ! command -v tailscale &> /dev/null; then
  echo "[1/8] Installing Tailscale..."
  curl -fsSL https://tailscale.com/install.sh | sh
else
  echo "[1/8] Tailscale is already installed."
fi

# ------------------------------------------
# 2. ネットワーク認証
# ------------------------------------------
# Tailscaleのメッシュネットワークに参加させます(ブラウザ認証が必要です)
echo "------------------------------------------------"
echo "[2/8] Tailscaleの認証を開始します。"
echo "表示されるURLをブラウザで開き、ログインを完了させてください。"
echo "------------------------------------------------"
sudo tailscale up

# ------------------------------------------
# 3. IPアドレスの特定
# ------------------------------------------
# 最終的なエンドポイントURLを表示するために、VPN内のIPv4を取得します
TS_IP="$(tailscale ip -4)"
echo "[3/8] Tailscale IPを取得しました: ${TS_IP}"

# ------------------------------------------
# 4. 依存パッケージのインストール
# ------------------------------------------
# 非対話モードでインストールを実行し、セットアップを完全に自動化します
echo "[4/8] Installing webhook / wakeonlan / firewall tools..."
sudo apt update -qq

# iptables-persistentのインストール時のダイアログ(自動保存の確認)を事前に回避
echo "iptables-persistent iptables-persistent/autosave_v4 boolean true" | sudo debconf-set-selections
echo "iptables-persistent iptables-persistent/autosave_v6 boolean true" | sudo debconf-set-selections

sudo apt install -y webhook wakeonlan iptables-persistent -qq

# ------------------------------------------
# 5. Webhookエンドポイントの定義
# ------------------------------------------
# 特定のURLを叩いた際に実行される挙動(WoLコマンド)をJSONで定義します
echo "[5/8] Creating hooks.json..."
mkdir -p "$BASE_DIR"
cat <<EOF > "$BASE_DIR/hooks.json"
[
  {
    "id": "wake-pc",
    "execute-command": "/usr/bin/wakeonlan",
    "command-working-directory": "/tmp",
    "pass-arguments-to-command": [
      {
        "source": "string",
        "name": "$TARGET_MAC"
      }
    ]
  }
]
EOF

# ------------------------------------------
# 6. ファイアウォール(iptables)によるアクセス制限
# ------------------------------------------
# Webhookポートを「信頼できる送信元」以外から遮断し、ゼロトラストを徹底します
echo "[6/8] Configuring firewall rules for port ${WEBHOOK_PORT}..."

# 6-0) ループバックアクセスの許可(同一ホスト内通信用:Cloudflare Tunnel等)
if ! sudo iptables -C INPUT -i lo -p tcp --dport "${WEBHOOK_PORT}" -j ACCEPT 2>/dev/null; then
  sudo iptables -I INPUT 1 -i lo -p tcp --dport "${WEBHOOK_PORT}" -j ACCEPT
fi

# 6-1) Tailscaleネットワーク(VPN経由)からのアクセスを許可
if ! sudo iptables -C INPUT -i tailscale0 -p tcp --dport "${WEBHOOK_PORT}" -j ACCEPT 2>/dev/null; then
  sudo iptables -I INPUT 1 -i tailscale0 -p tcp --dport "${WEBHOOK_PORT}" -j ACCEPT
fi

# 6-2) (任意)特定のIPv4ソースからのアクセスを許可
if [[ -n "${CLOUD_FLARED_SRC_IPV4}" ]]; then
  if ! sudo iptables -C INPUT -p tcp -s "${CLOUD_FLARED_SRC_IPV4}" --dport "${WEBHOOK_PORT}" -j ACCEPT 2>/dev/null; then
    sudo iptables -I INPUT 1 -p tcp -s "${CLOUD_FLARED_SRC_IPV4}" --dport "${WEBHOOK_PORT}" -j ACCEPT
  fi
fi

# 6-3) デフォルトで指定ポートへの外部アクセスをDROP(ホワイトリスト形式)
if ! sudo iptables -C INPUT -p tcp --dport "${WEBHOOK_PORT}" -j DROP 2>/dev/null; then
  sudo iptables -A INPUT -p tcp --dport "${WEBHOOK_PORT}" -j DROP
fi

# IPv6環境のセキュリティ設定(Tailscale IPv6対応)
if command -v ip6tables &>/dev/null; then
  sudo ip6tables -I INPUT 1 -i lo -p tcp --dport "${WEBHOOK_PORT}" -j ACCEPT 2>/dev/null || true
  sudo ip6tables -I INPUT 1 -i tailscale0 -p tcp --dport "${WEBHOOK_PORT}" -j ACCEPT 2>/dev/null || true
  
  if [[ -n "${CLOUD_FLARED_SRC_IPV6}" ]]; then
    sudo ip6tables -I INPUT 1 -p tcp -s "${CLOUD_FLARED_SRC_IPV6}" --dport "${WEBHOOK_PORT}" -j ACCEPT 2>/dev/null || true
  fi
  
  if ! sudo ip6tables -C INPUT -p tcp --dport "${WEBHOOK_PORT}" -j DROP 2>/dev/null; then
    sudo ip6tables -A INPUT -p tcp --dport "${WEBHOOK_PORT}" -j DROP
  fi
fi

# 設定を再起動後も有効にするために保存
sudo netfilter-persistent save >/dev/null

# ------------------------------------------
# 7. systemdによるサービス化
# ------------------------------------------
# OS起動時に自動実行し、プロセス停止時も自動復旧するように管理します
echo "[7/8] Configuring systemd service..."
sudo bash -c "cat <<EOF > $SERVICE_FILE
[Unit]
Description=Webhook Server for WoL
After=network-online.target tailscaled.service
Wants=network-online.target tailscaled.service

[Service]
User=$USER_NAME
ExecStart=/usr/bin/webhook -port $WEBHOOK_PORT -hooks $BASE_DIR/hooks.json -verbose -hotreload
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF"

# ------------------------------------------
# 8. サービスの有効化とステータス確認
# ------------------------------------------
echo "[8/8] Activating service..."
sudo systemctl daemon-reload
sudo systemctl enable webhook
sudo systemctl restart webhook

echo ""
echo "--- 🎉 All tasks completed! ---"
echo "iPhoneのショートカット等に以下のURLを登録してください:"
echo "👉 http://${TS_IP}:${WEBHOOK_PORT}/hooks/wake-pc"
echo ""
echo "※セキュリティ: ポート ${WEBHOOK_PORT} はVPNおよび許可IP以外から完全に遮断されています。"
echo "------------------------------------------"
sudo systemctl status webhook --no-pager


5. 検証:0.5ミリ秒の快感

実際に構築した結果、リクエストの処理速度は驚異の 500µs(0.5ミリ秒) !

[webhook] 2026/01/24 05:22:13 ... loaded: wake-pc
[webhook] 2026/01/24 05:23:11 ... 200 | 0 B | 528.998µs | POST /hooks/wake-pc

VPNのハンドシェイクを待つ数秒間が、Web技術(HTTPS)に置き換わることで完全に消え去りました。


おわりに

「VPNを繋ぐ」という古いお作法を捨て、Web標準の技術で物理デバイスを操作するのは、エンジニアとして最高の体験です。独自ドメイン wol.your-domain.com が、あなたのPCの「イグニッション(点火スイッチ)」になります。

ぜひ、余っているラズパイを「最強の門番」に変えてみてください。

Discussion