リモートアクセス

雲瞰 は初期状態では LAN 内でのみ利用できます——外出するとスマホアプリから自宅のサーバーに届きません。本章では 雲瞰 を安全に外部公開する 3 つの方法(パブリック直結(推奨)、メッシュ VPN、リバースプロキシ)と、それぞれ必要なポートを説明します。パブリック直結に必要なのはドメイン 1 つとルーターの 1 ルールだけで、証明書は管理画面から自動取得されます。

まずこれだけは

外部公開の鉄則は 1 つ、HTTPS と強いパスワードです。雲瞰 のパブリック直結は「TCP 1 ポート + TLS 暗号化 + ログイン認証」であり、映像をそのまま公開するものではありません。一方でカメラ自身の RTSP / ONVIF ポートや、雲瞰 の内部ストリーミングポート(24214 / 23880)は絶対に転送しないでください——これらの前段にログインはなく、転送はカメラをインターネット中のスキャナーに差し出すのと同じです。

3 つの方法の選び方

方法必要なものルーターでの作業ライブ映像の遅延向いている人
パブリック直結ドメイン 1 つ(無料サブドメイン可)、証明書は自動取得IPv4:TCP 転送 1 本。IPv6:受信許可のみWebRTC で 0.5 秒未満グローバル IP か IPv6 がある回線(推奨)
メッシュ VPN使う端末すべてに同じアプリを導入何もしないWebRTC で 1 秒未満CGNAT かつ IPv6 なし、またはポートを一切開けたくない場合
リバースプロキシ + ドメインすでに動かしている nginx / Caddy / Luckyプロキシが使うポートを転送既定は HLS 約 2.5 秒(TCP 透過で WebRTC 化可)すでにプロキシを運用し 443 に統一したい人

3 つは排他ではなく、同時に有効化できます。迷ったらこの順に:まず回線に IPv6 があるか確認——あればパブリック直結はポート転送すら不要です。IPv6 はないがグローバル IP がある場合も同じくパブリック直結。どちらもない(CGNAT)ならメッシュ VPN です。

方法 1:パブリック直結(推奨)

雲瞰 は管理画面・アプリ API・ライブ映像をすべて 1 つの TCP ポートに集約します。同じポートで HTTPS と WebRTC の映像チャネルの両方を受け、サーバー側が自動的に振り分けます。したがってルーターの設定は1 本だけで、WebRTC 用に UDP を別途転送する必要はありません。

この方式が解決すること

従来、外出先のライブ映像は HLS(遅延 2.5 秒前後)に落ちるしかありませんでした。WebRTC には追加のポート転送と、サーバーが広告する「実際に到達できるアドレス」の両方が必要で、家庭回線で同時に満たすのは困難だったためです。今は 1 ポートで完結し、外からでも 1 秒未満で表示されます——「今、玄関にいるのは誰か」を見る用途では決定的な差です。

手順 1:ドメインと証明書(管理画面から自動取得)

パブリック直結には HTTPS が必須です。スマホアプリは信頼されない自己署名証明書を拒否するため、自分のドメインと正式な証明書が要ります。設定 → 一般 → HTTPS 証明書 でドメインと DNS プロバイダーの API キーを入力して保存すると、雲瞰 が Let's Encrypt へ自動申請し、期限前に自動更新します。

  • 80 番ポートを開ける必要はありません。DNS-01 認証(ドメイン配下に一時的な TXT レコードを書く方式)なので、受信 80 が塞がれている回線でも取得できます。
  • 対応 DNS プロバイダー:Alibaba Cloud、Tencent Cloud DNSPod、Huawei Cloud、Cloudflare、AWS Route 53、Porkbun、deSEC、DuckDNS。
  • 自分のドメインがなくても使えます:deSEC と DuckDNS は無料のサブドメインを配布しており、それを自分のドメインとして利用できます。
  • すでに証明書がある場合は fullchain.pem と privkey.pem を data/certs/ に置くだけです。雲瞰 はそれを使うだけで、あなたのファイルを上書きしません。
  • 申請前にセルフチェックを実行できます。実際にテスト用レコードを書いて削除し、API キーと権限を確認するもので、Let's Encrypt の発行回数を消費しません。

ドメインはご自身のものが必要です

雲瞰 はドメインをホストしません(独自 DDNS も公式サブドメインの配布も行いません)。ドメインと DNS はあなた名義のままで、私たちは証明書の自動化だけを提供します——certbot や acme.sh と同じ立ち位置です。ドメインがない場合は、上記の deSEC / DuckDNS が無料です。

手順 2:公開ポートを選ぶ

家庭用回線では 443 を避けたほうが無難です

中国本土の家庭向け回線では、受信 80 / 443 が広く遮断されています(未登録サイトのホスティング防止のため)。「443 を使う」という海外の手順をそのまま真似ると、ルーターの設定は完全に正しいのに接続だけが届かず、どこにもエラーが出ないという最も厄介な状態になります。その環境では高位ポートを使ってください——既定の 23443 が手頃です。クラウドサーバーやその他の地域では制限がないため 443 で構いません(URL にポート番号を書かずに済みます)。

同じパネルで公開ポートを入力して保存すれば即座に反映されます。バックエンドが設定を作り直し、そのポートが実際に待ち受け状態になったことまで確認します。コンテナの再起動も compose の編集も不要です。ポートが他のプログラムに使われている場合や内部ポートと衝突する場合は、保存時にその場で拒否されます(保存後に静かに失敗することはありません)。

手順 3:ルーター

  • IPv4(グローバル IP あり):ポート転送を 1 本追加し、外側の 23443/tcp を 雲瞰 ホストの 23443 へ。必要なのはこれだけで、23515 などを転送する必要はありません。
  • IPv6:NAT がないため転送は不要です。ルーターの IPv6 ファイアウォールで 23443/tcp 宛の受信を許可してください。
  • DNS:グローバル IPv4 は A レコード、IPv6 は AAAA レコード。家庭回線のアドレスは変わるので、DDNS クライアントで自動更新させます。

手順 4:確認

https://<あなたのドメイン>:23443/ を開き、ログインしてライブ画面へ。1 秒以内に、WebRTC 経由で映像が出るはずです。

同じパネルに外部到達性のセルフチェックがあります。ドメインの解決先、ポートの開通、証明書の妥当性を確認し、どの段階で止まっているかを直接示します——パケットキャプチャは不要です。

グローバル IP がもらえない場合

セルフチェックが「グローバルアドレスが CGNAT(キャリアグレード NAT)帯域にある」と示した場合、ポート転送はどう設定しても通りません。設定ミスではありません。出口は 2 つ:IPv6 を使う(普及が進んでおり、転送すら不要)か、下のメッシュ VPN に切り替えるかです。ISP にグローバル IP を申請する手もあり、多くの地域で無料開通できます。

方法 2:メッシュ VPN(ポートを一切開けない)

メッシュツール(Tailscale / WireGuard / ZeroTier など)は、スマートフォンと自宅のサーバーの間に暗号化トンネルを構築します。スマートフォンは自宅にいるときと同じように内部アドレスで SkyView にアクセスできます——ルーターでポートを開ける必要は一切なく、サーバーがインターネットに公開されることもありません。

  1. 1

    サーバーにメッシュクライアントを導入

    SkyView を動かしているマシン(または同じ LAN 上のソフトルーター)に Tailscale をインストールし、tailscale up でログインします。WireGuard / ZeroTier も同様です。

    bash
    curl -fsSL https://tailscale.com/install.sh | sh
    sudo tailscale up
  2. 2

    スマートフォンに同じアプリを入れて同じアカウントでログイン

    スマートフォンに対応する Tailscale / ZeroTier アプリをインストールし、サーバーと同じアカウントでログインすれば、両者が同じ仮想 LAN に入ります。

  3. 3

    アプリにメッシュのアドレスを入力

    SkyView アプリのサーバーアドレスには、メッシュ側が割り当てた IP を入力します——Tailscale なら 100.x.x.x、ZeroTier なら 10.x.x.x です。ポートはこれまでどおり :23406 で、例えば http://100.x.x.x:23406 のようになります。

  4. 4

    完了

    外出先で 4G/5G に接続していてもアクセスでき、体験は自宅にいるときと変わりません。パブリック IP もドメインも HTTPS 証明書も不要です。

メッシュ VPN の得失

ポートを開けない = 外部からの攻撃面がゼロで、スキャナーからは存在すら見えません。グローバル IP にも依存しないので CGNAT でも通ります。メッシュ内ではスマホが LAN 上と同等に扱われるため、ライブ映像も低遅延の WebRTC のままです。代償は使う端末ごとにアプリの導入とログインが必要なことで、家族への一時的な共有は URL を渡すほど手軽ではありません。

方法 3:リバースプロキシ + ドメイン

パブリック IP とドメインをお持ちで、https://cam.example.com のようなアドレスでアクセスしたい場合(メッシュアプリの導入が難しい家族にも共有しやすくなります)、SkyView の手前に nginx / Caddy を一段置いて HTTPS 終端を行うことができます。SkyView のコンテナ内部にはすでに nginx が含まれているため、外側のリバースプロキシはトラフィックをまとめて :23406 へ転送するだけで済みます。

プロキシ経由でライブ映像はどうなるか

通常の HTTP レイヤーのリバースプロキシは管理画面と API を転送し、ライブ映像は HLS(遅延 2.5 秒前後)になります——機能上は問題ありません。1 秒未満の WebRTC が欲しい場合は、プロキシで TCP レイヤー(nginx の stream ブロック、Caddy の layer4 プラグイン)によりポートをそのまま 雲瞰 の公開ポートへ透過させてください。両立可能です:管理画面は 443 のプロキシ経由、ライブ映像は透過ポート経由。

nginx の設定

以下の内容をそのまま丸ごと/etc/nginx/sites-available/skyview.conf として保存し、<あなたのドメイン> を置き換えたら sites-enabled/ に ln -s してください——ファイル 1 つで完結し、別途 snippet を作る必要はありません。2 つの location / にある proxy_set_header のブロックはまったく同じ内容です:nginx の proxy_set_header は継承ではなく上書きなので、各 location がそれぞれ 1 セットずつ持つ必要があります。そのままコピーしてください。

nginx
# WebSocket Upgrade のパススルー —— http {} コンテキスト内に置く必要があります
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    listen 80;
    listen [::]:80;
    server_name <あなたのドメイン>;
    # certbot --nginx がこのブロックを自動的に 301 で https へリダイレクトするよう書き換えます
    location / {
        proxy_pass http://127.0.0.1:23406;
        proxy_http_version 1.1;
        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Host  $host;
        proxy_set_header X-Forwarded-Port  $server_port;
        proxy_set_header X-Forwarded-Proto $scheme;        # ★ これを忘れると HTTPS 環境でログインが無限ループします
        proxy_set_header Upgrade           $http_upgrade;
        proxy_set_header Connection        $connection_upgrade;
        proxy_buffering       off;
        proxy_connect_timeout 60s;
        proxy_send_timeout    1d;
        proxy_read_timeout    1d;
    }
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;
    server_name <あなたのドメイン>;

    ssl_certificate     /etc/letsencrypt/live/<あなたのドメイン>/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/<あなたのドメイン>/privkey.pem;

    # 録画のエクスポートや顔ライブラリの一括インポートは数百 MB になることがあり、長い JWT cookie も大きいため 414 エラーを防止
    client_max_body_size        200m;
    client_header_buffer_size   4k;
    large_client_header_buffers 8 16k;

    location / {
        proxy_pass http://127.0.0.1:23406;
        proxy_http_version 1.1;
        # ↓ 上の :80 の location と完全に同じ内容です。1 行ずつそのままコピーしてください(proxy_set_header は継承されません)
        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Host  $host;
        proxy_set_header X-Forwarded-Port  $server_port;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Upgrade           $http_upgrade;
        proxy_set_header Connection        $connection_upgrade;
        proxy_buffering       off;
        proxy_connect_timeout 60s;
        proxy_send_timeout    1d;
        proxy_read_timeout    1d;
    }
}
/etc/nginx/sites-available/skyview.conf

X-Forwarded-Proto / X-Forwarded-Host は必須 + すべての location に include すること

コンテナ内の nginx は HTTP しか listen していないため、X-Forwarded-Proto でクライアントの実際の scheme を、X-Forwarded-Host で外部向けのドメインを判断しています。X-Forwarded-Proto を渡し忘れると → HTTPS 環境ではログイン直後にログイン画面へ戻され、ライブ映像もブラウザに Mixed Content として遮断されます。X-Forwarded-Host を渡し忘れると → 録画の再生 / ダウンロード / エクスポートのリンクが誤ったアドレスで組み立てられ、クライアントから開けなくなります。上記の nginx テンプレートと Caddy(既定でこの 2 つのヘッダーを渡します)はどちらも対応済みなので、そのままコピーすれば問題ありません——ポートは X-Forwarded-Port を手動で設定する必要はなく、コンテナ内の nginx が「外側にリバースプロキシがある場合 / IP に直接アクセスする場合」の両方を自動で吸収します。また、nginx の proxy_set_header は追加ではなく上書きです:ある location に 1 行でも書くと、上位の set_header はすべて無効になります——そのため、各 location は完全な 1 セットを include する必要があります。

証明書を取得します(マシンがインターネットから :80 にアクセスできる必要があります):

bash
certbot --nginx -d <あなたのドメイン> -m <あなたのメールアドレス> --agree-tos --no-eff-email --redirect

Caddy の設定(よりシンプル)

Caddy は certbot なしで Let's Encrypt 証明書を自動発行・自動更新し、設定もずっと短く済みます。/etc/caddy/Caddyfile:

caddyfile
<あなたのドメイン> {
    reverse_proxy 127.0.0.1:23406 {
        # Caddy は既定で X-Forwarded-{For,Proto,Host} を渡します
        # 長時間接続(双方向通話 WS / イベント SSE / ライブ映像)向けに flush とタイムアウトを大きくする
        flush_interval -1
        transport http {
            read_timeout  24h
            write_timeout 24h
        }
    }
    request_body {
        max_size 200MB
    }
}
/etc/caddy/Caddyfile

ポート早見表

ポートプロトコル用途外部に開ける?
23443TCPパブリック直結:管理画面 + API + ライブ映像(HTTPS、変更可)開ける(この方式で開ける唯一のポート)
23406TCPLAN 用の入口(平文 HTTP)開けない(外部からは上記 23443 を使用)
23515UDP + TCPライブ映像のメディアストリーム(LAN / メッシュ / IPv6 直結)不要
23880TCPRTSP。LAN 内の外部プレイヤー向け不要
24214TCP内部ストリーミングサービス厳禁

内部ポートは絶対に転送しない

24214(内部ストリーミングサービス)と 23880(RTSP)にはログインの層がありません——配信の認可は 23406 / 23443 の入口で行われます。これらを公開すると認証を丸ごと迂回され、カメラ映像が公開状態になります。23406 をそのまま転送するのも同様に避けてください:平文 HTTP のためパスワードが平文で流れ、さらにブラウザや Android 9 以降は安全でないページでカメラ権限を拒否します。外部アクセスには上記のパブリック直結(23443、HTTPS)を使ってください。

設定後の確認方法

bash
# 1. HTTP→HTTPS リダイレクト(リバースプロキシ構成の場合)
curl -sSI http://<あなたのドメイン>/healthz | head -1      # 301 が返れば OK

# 2. ヘルスチェック
curl -sS https://<あなたのドメイン>/healthz                 # {"code":0,...} が返れば OK

# 3. 未ログインで保護されたエンドポイントにアクセス
curl -sS -o /dev/null -w '%{http_code}\n' \
     https://<あなたのドメイン>/api/cameras                 # 401 が返れば OK

最後にブラウザで一連の流れを確認します:ログイン → ライブ監視で映像が表示される → ブラウザのコンソールに Mixed Content エラーが出ていない、の 3 点です。ログイン直後にログイン画面へ戻される、またはライブ映像で Mixed Content エラーが出る場合、99% は X-Forwarded-Proto が正しく渡されていません。詳しいトラブルシューティングはトラブルシューティングを参照してください。

このページは役に立ちましたか?