Nginx Proxy Managerで自宅の複数サービスをサブドメインに振り分ける:内部HTTPS化の手順

Photo: Sergei Starostin / Pexels
先に結論です。自宅で動かしているサービスが3つを超えたら、Nginx Proxy Manager(以下 NPM)を入口にして、jellyfin.home.example.com のようなサブドメインで振り分けると、ポート番号を覚える必要がなくなります。HTTPS は、独自ドメインと Let’s Encrypt の DNS チャレンジでワイルドカード証明書を1枚取る方法なら、ルーターのポートを1つも開けずに用意できます。
名前の解決は、家の中の DNS(AdGuard Home など)で *.home.example.com を NPM のサーバーに向けるだけです。インターネットには何も公開しません。
構成:ドメイン・証明書・内部DNSの3つを決める
リバースプロキシを1台置くと、ブラウザは常に 443 番の NPM にアクセスし、NPM がホスト名を見て裏のサービスへ振り分けます。http://192.168.1.10:8096 のような URL を家族に覚えてもらう必要がなくなります。
用意するものは次の3つです。
- 独自ドメイン:Let’s Encrypt は
.localや.lanのような内部用の名前には証明書を出しません。Cloudflare など、NPM が DNS チャレンジに対応している DNS サービスで管理しているドメインが必要です。自宅用はhome.example.comのようにサブドメインを1段切っておきます - ワイルドカード証明書:
*.home.example.comの1枚で、その下の1段のサブドメインをすべてカバーできます。ワイルドカード証明書は DNS-01 チャレンジでしか発行されないとされていますが、裏を返せば、外から自宅サーバーに接続できなくても発行できます - 内部 DNS:
*.home.example.comの問い合わせに、NPM を動かすサーバーの LAN 内 IP を返す設定です
サービスごとに証明書を取ると、証明書の公開記録(Certificate Transparency ログ)にサービス名が1つずつ載ります。ワイルドカードなら *.home.example.com しか載らず、何を動かしているかを外に見せずに済みます。
NPM 自体は軽いので、すでに Docker が動いている N100 ミニPC に同居させれば十分です。
Docker Compose で Nginx Proxy Manager を立てる
/opt/npm/compose.yml を作ります。公式のセットアップ手順とほぼ同じ内容です。
services:
npm:
image: jc21/nginx-proxy-manager:latest
container_name: npm
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "81:81"
environment:
TZ: "Asia/Tokyo"
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencrypt
sudo ss -tlnp | grep -E ':(80|443|81) '
cd /opt/npm
sudo docker compose up -d
80 と 443 がプロキシ本体、81 が管理画面です。同じサーバーの AdGuard Home は管理画面が既定で 80 番なので、1行目の ss で空きを確かめ、ぶつかる場合は AdGuard Home 側のポートを変えます。
ブラウザで http://サーバーのIP:81 を開きます。最近のバージョンでは、初回に管理者アカウントを作る画面が出るとされています。古い解説記事にある admin@example.com / changeme で入る形式でも、ログイン直後にパスワードの変更を求められます。81 番はルーターでポート開放しないでください。
DNS チャレンジでワイルドカード証明書を取る
Cloudflare を例にします。Cloudflare のダッシュボードで API トークンを作り、権限は Zone → DNS → Edit だけにして、対象のゾーン(ドメイン)も1つに絞ります。トークンの値は作成時にしか表示されないので、その場で控えます。
NPM の管理画面で「SSL Certificates」から Let’s Encrypt の証明書を追加し、次のように入力します。
- ドメイン名:
*.home.example.com(サブドメインなしのhome.example.comも使うなら一緒に追加) - DNS チャレンジを使う設定をオンにして、DNS プロバイダに Cloudflare を選ぶ
- 認証情報の欄に
dns_cloudflare_api_token=控えたトークンを入れる
保存すると、NPM が確認用の TXT レコードを一時的に作り、証明書が発行されます。失敗する場合は、DNS の反映を待つ秒数(Propagation Seconds)を長めにして再試行するとよいとされています。更新は NPM が自動で行います。
内部 DNS を向けて、サービスごとに Proxy Host を作る
AdGuard Home を使っている場合は、DNS リライトの機能(英語表示では Filters → DNS rewrites)に次の1件を追加します。
- ドメイン:
*.home.example.com - 応答:
192.168.1.10(NPM を動かしているサーバーの IP)
家の機器が AdGuard Home を DNS として使っていれば、これだけで配下の名前がすべてこのサーバーに向きます。Cloudflare 側にプライベート IP の A レコードを登録する方法もありますが、ルーターによっては DNS リバインディング対策でその応答を捨てるものがあるとされており、内部 DNS で解決するほうが確実です。
次に、NPM の「Hosts」→「Proxy Hosts」でサービスを1つずつ追加します。Jellyfin なら次のとおりです。
| 項目 | 入力例 |
|---|---|
| Domain Names | jellyfin.home.example.com |
| Scheme | http |
| Forward Hostname / IP | 192.168.1.10 |
| Forward Port | 8096 |
| SSL タブ | 取得したワイルドカード証明書を選び、Force SSL をオン |
Home Assistant や Uptime Kuma のように画面の更新に WebSocket を使うサービスでは、Websockets Support もオンにします。
つまずきやすい3つのポイント
転送先に localhost と書くとつながらない。 NPM はコンテナの中で動いているので、localhost は NPM のコンテナ自身を指します。同じサーバーのサービスでも、サーバーの LAN 内 IP を書きます。もう一つの方法は、sudo docker network create proxy で共通のネットワークを作り、NPM と各サービスの compose.yml に次を足して、転送先にコンテナ名とコンテナ側のポートを書くことです。
networks:
default:
name: proxy
external: true
この形にすると、サービス側の ports: を消せるので、ポート番号で直接アクセスされる経路をなくせます。
Home Assistant が 400 Bad Request を返す。 Home Assistant は、許可したプロキシ経由のアクセスしか受け付けません。configuration.yaml に次を足して再起動します。trusted_proxies には NPM を動かしているサーバーの IP を書きます。
http:
use_x_forwarded_for: true
trusted_proxies:
- 192.168.1.10
ログイン後に古い URL へ飛ばされる。 Nextcloud や Vaultwarden のように自分の URL を設定として持つサービスは、その値も新しい https:// の URL に書き換えます。
まとめ
- サービスが増えたら、NPM を入口にしてサブドメインで振り分ける
- 証明書は独自ドメインと DNS チャレンジでワイルドカードを1枚。ポート開放は不要
- 内部 DNS(AdGuard Home の DNS リライト)で
*.home.example.comを NPM のサーバーに向ける - 転送先は
localhostではなく LAN 内 IP か、同じ Docker ネットワークのコンテナ名にする - 管理画面の 81 番は外に開けない。Home Assistant は
trusted_proxiesを忘れない
私は、自宅のリバースプロキシは「外に公開する道具」ではなく「家の中の URL を整理する道具」として使うのが安全だと考えています。最初は1サービスだけ登録し、鍵マーク付きで開けることを確かめてから増やすのがおすすめです。
運営者が作った Excel テンプレートを BOOTH で配布しています。IT 資産管理台帳(無料 Lite 版あり) / IT 資格の学習管理シート


