ホームラボのイントラネットを構築してみた
Shinwoo PARK
はじめに
私は以前からずっと、ホームラボやセルフホスティングに一種のロマンを感じていた。
費用や実用性の問題でサーバーやセルフホスティングサービスを長く維持できなかったとしても、さまざまなサービスを継続的にセルフホスティングして使ってきたし、自分だけのサービスを持つことに楽しさを感じていた。
AIが登場して、AIと一緒にコーディングすることに慣れてくると、必要なものを自分で作ることが増えた。そして、そうして作ったものを自分のLinuxサーバーにデプロイして動かしてきた。
不便に感じていたのは、作るものが主に自分だけで使うサービスだったため、サービスごとに会員登録やログインの仕組みを組み込むのが、いくらAIに頼んでも面倒だったことだ。特に、すべてのサービスに個別の会員登録・ログイン機能を用意していたので、アカウントの管理も煩雑に感じていた。
この問題を解決するために、Cloudflare TunnelとAccessを使ってきた。
Cloudflare Tunnelはnginxやポートに関する負担を軽減してくれ、Accessは信頼できるゲートウェイであり、統一された認証システムとして、私の問題を解決してくれた。
しかし、軍隊にいる間にイントラネットと統一ログインシステムを目にして、自分のサーバーでもTunnelとAccessを使う代わりに、イントラネットの構築に挑戦してみることにした。
設計

Tailscale
イントラネットとは、組織内部からのみアクセスできる内部ネットワークのことだ。
プライベートVPNを構築したり、特定のバックボーンを利用したり、専用線を敷設したりして構築する。
私のイントラネットでは、Tailscaleが中核となる。TailscaleはプライベートVPNとして、認証されたユーザーの複数のデバイスにTailscale IPを割り当て、仮想的な内部ネットワークを形成する。

DNSのセルフホスティング
さらに、DNSもセルフホスティングしたいと考えた。
もちろん、TailscaleにもMagic DNSがある。しかし、*.ts.netドメインしか使えず、しかもランダムに割り当てられたドメインを使わなければならないため、不便だった。
それに、AdGuard Homeをセルフホスティングすれば、DNSと同時に広告ブロックの効果も得られるという追加のメリットがあった。
幸い、イントラネットに接続するデバイスのDNSを変更する必要がある点は、TailscaleのCustom DNS設定で解決できた。Tailscaleに接続したときに、設定したDNSを使うようにできるため、簡単に対応できた。
自己署名証明書
イントラネットを作るにあたり、自分独自のTLDを作りたいと思った(memo.psw、auth.pswのようなもの)。
通常、証明書は信頼されたCA(Certificate Authority)による署名を受けて発行される。
独自のTLDを作る場合の問題点は次のとおりだ。
- CAは、存在しないTLDに対して証明書を発行してくれない
- イントラネット環境のため、パブリックCAは自分のTLDドメインにアクセスしたり、認証を行ったりできない
そのため、自己署名証明書が必要だった。
そのために、step-caを利用した。
NGINX
最後に、サービスをホストするサーバー上に残り、各サービスへリクエストをルーティングするリバースプロキシが必要だった。
もちろん、ほかにも多くの選択肢があったが、NGINXを選んだ理由は単に使い慣れていたからだ。
Tailscaleのインストール
すべてのインストールと設定に先立ち、まず基盤となるプライベートVPNを用意するため、すべてのデバイスとサーバーにTailscaleをインストールした。
Android、iOS、macOS、Windowsなど、クライアントとなるデバイスでは、WebサイトまたはアプリストアからTailscaleをインストールしてログインすればよい。
Linuxのヘッドレスサーバーでは、次のコマンドを使った。
# インストール
curl -fsSL https://tailscale.com/install.sh | sh
# DNSサーバー — 独自のDNSを使うため
sudo tailscale up --accept-dns=false
# サービスサーバー — 通常の接続
sudo tailscale up
# 各サーバーのTailscale IPを確認
tailscale ip -4
tailscale ip -4で表示されるIPは、Tailscaleで使う内部ネットワークのIPだ。
DNSサーバーのインストールと設定
1. AdGuard Homeのインストール
Docker Composeで簡単にインストールした。
# /opt/adguardhome/docker-compose.yml
services:
adguard-home:
image: adguard/adguardhome:latest
container_name: adguard-home
volumes:
- ./work:/opt/adguardhome/work
- ./conf:/opt/adguardhome/conf
restart: unless-stopped
network_mode: host
その後、<tailscale-ip>:3000にアクセスすると、AdGuard Homeの初期設定ページが表示される。
初期設定が終わると、:3000は閉じられ、:80でホスティングされる。
初期設定では、管理者アカウントを設定し、DNSポートが53であることを確認する。
DNSの設定
初期設定(:3000)が終わり、管理者ページ(:80)に移動したら、いくつか設定を行う。
1. DNS Rewriteの設定(重要)
Web UI → Filters → DNS Rewrites → Add DNS Rewrite
次のDNSレコードを追加する。
*.psw→<サービスサーバーのtailscale ip>ca.internal→<DNSサーバーのtailscale ip>
pswは、イントラネットで使用する私のTLDだ。別のTLDやドメインを使いたい場合は、任意のものに変更してかまわない。

ca.internalはCAへのリクエストに使うドメインだ。別のドメインに設定しても問題ない。ただし、以降の設定でも変更後のドメインを一貫して使う必要がある。
2. 上流DNSの設定
Settings → DNS settings → Upstream DNS servers
https://1.1.1.1/dns-query
https://8.8.8.8/dns-query
上流DNSを設定すると、イントラネットのドメイン以外はパブリックDNSに転送される。
つまり、イントラネットに接続した状態でも外部サイトにアクセスできるということだ。
2. TailscaleのDNS設定
Tailscale Admin Console → DNSタブで設定する。
Nameservers → Add nameserver → Customをクリックする。
Nameserver IP欄にDNSサーバーのTailscale IPを入力して保存する。

Override DNS Serversオプションは、Tailscaleに接続されたデバイスのデフォルトDNSを上書きする。
これを有効にしないと、Tailscale DNSより先にローカルDNSが使われるため、AdGuard Homeをインストールした意味が薄れてしまう。
AdGuard Homeにも上流DNSが設定されているため、ほかのDNSと同様に安全だ。
3. 内部CAの構築
step-caとstep CLIのインストール
# (最新バージョンの確認先: github.com/smallstep/certificates)
# step-ca
wget https://dl.smallstep.com/gh-release/certificates/gh-release-header/v0.27.4/step-ca_linux_0.27.4_amd64.tar.gz
tar xzf step-ca_linux_0.27.4_amd64.tar.gz
sudo mv step-ca /usr/local/bin/
# step CLI
wget https://dl.smallstep.com/gh-release/cli/gh-release-header/v0.27.4/step_linux_0.27.4_amd64.tar.gz
tar xzf step_linux_0.27.4_amd64.tar.gz
sudo mv step_0.27.4/bin/step /usr/local/bin/
CAの初期化
step ca init \
--name "Home Internal CA" \
--dns "ca.internal" \
--dns "<DNSサーバーのtailscale ip>" \
--address "<DNSサーバーのtailscale ip>:8443" \
--provisioner "admin@internal" \
--acme
実行すると、次のファイルが生成される。
~/.step/certs/root_ca.crt— ルートCA証明書~/.step/certs/intermediate_ca.crt— 中間CA証明書~/.step/secrets/— 秘密鍵
systemdサービスの登録と有効化
sudo cat > /etc/systemd/system/step-ca.service << 'EOF'
[Unit]
Description=step-ca Internal CA
After=network.target
[Service]
User=step
ExecStartPre=/bin/sh -c 'until ip -4 addr show tailscale0 | grep -q <DNS 서버 tailscale ip>; do sleep 1; done'
ExecStart=/usr/local/bin/step-ca /home/step/.step/config/ca.json
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
sudo useradd --system --home /home/step --create-home step
sudo cp -r ~/.step /home/step/
sudo chown -R step:step /home/step/.step
sudo systemctl enable --now step-ca
ACMEプロビジョナー
次のコマンドでプロビジョナーがあるか確認する。
step ca provisioner list
# "acme"プロビジョナーが存在すること
なければ、次のコマンドで追加する。
step ca provisioner add acme --type acme
4. Nginxと証明書の発行
この作業はサービスサーバーで行う。
Nginxとcertbotのインストール
sudo apt update && sudo apt install -y nginx certbot
Nginxの設定ファイルを作成
私のサーバーでは、以前からnginxがインストールされ、サービスが稼働していた。そのため、外部公開サービスとイントラネットサービスを分けるために、/etc/nginx/intra.dディレクトリを作成した。
nginxの設定ファイルを次のように作成する。
server {
listen <サービスサーバーのtailscale ip>:80;
server_name <ドメイン>;
location ^~ /.well-known/acme-challenge/ {
root /var/www/certbot;
default_type text/plain;
try_files $uri =404;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
// 初回の証明書発行後に有効化
// listen <サービスサーバーのtailscale ip>:443 ssl;
listen <サービスサーバーのtailscale ip>:443;
server_name <ドメイン>;
// 初回の証明書発行後に有効化
// ssl_certificate /etc/letsencrypt/live/env.psw/fullchain.pem;
// ssl_certificate_key /etc/letsencrypt/live/env.psw/privkey.pem;
location / {
proxy_pass http://127.0.0.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-Proto $scheme;
}
}
証明書の発行にはHTTP-01のwebroot方式を使うため、認証ファイルをnginxから提供できるように、rootとtry_filesを設定する。
初回の証明書発行前は証明書ファイルがないので、SSLを適用せずに443ポートだけを開いておく。証明書の発行後にSSLを適用する。
証明書の発行
CAの初期化時に作成したルート証明書をサービスサーバーに転送済みであるものとする。
sudo certbot certonly \
--webroot -w /var/www/certbot \
--server https://ca.internal:8443/acme/acme/directory \
-d <ドメイン>
実行すると証明書が発行され、certbotが自動的に再発行も行う。
つまり、そのドメインで一度証明書を発行すれば、その後の再発行などを気にする必要はない。
ただし、certbotが証明書を更新してもnginxは再起動されないため、古い証明書を使い続ける問題が起きることがある。
そのため、certbotにフックを追加し、証明書の更新後にnginxを再起動するようにする。
sudo mkdir -p /etc/letsencrypt/renewal-hooks/deploy
printf '#!/bin/sh\nset -e\nnginx -t\nsystemctl reload nginx\n' \
| sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh >/dev/null
sudo chmod 755 /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
5. ルート証明書のインストール
これで最後の手順だ。
証明書を発行しても、自分たちで作ったCAはデフォルトでは信頼されない。
ルート証明書をインストールせずにTailscaleに接続し、nginxで設定したドメインにアクセスすると、ブラウザーは信頼されていない証明書だと警告する。
そのため、使用するデバイスに「この証明書を発行した機関は信頼できる」と知らせる必要がある。
それがルート証明書をインストールする手順だ。
iPhone(iOS)の場合
- ルート証明書を自分のスマートフォンに転送する。
- ファイルアプリでルート証明書を探して開くと、「プロファイルがダウンロードされました」と表示される。
- 設定を開いてプロファイルをインストールする。
- 設定 → 一般 → 情報 → 証明書信頼設定
- 「ルート証明書を全面的に信頼する」のセクションで、Home Internal CAのトグルをONにする
Androidの場合
- ルート証明書を自分のスマートフォンに転送する。
- ファイルアプリでルート証明書を探して開くと、Androidが証明書のインストールを検出し、インストール画面を表示する。
参考:イントラネットに接続するアプリを開発する場合、アプリはこの証明書設定の影響を受けない。そのため、アプリの開発時にネットワークセキュリティ設定へ証明書を追加する必要がある。
<network-security-config>
<base-config cleartextTrafficPermitted="false">
<trust-anchors>
<certificates src="system" />
<certificates src="user" />
</trust-anchors>
</base-config>
</network-security-config>
Windowsの場合
- Win + R → certmgr.msc
- 信頼されたルート証明機関 → 証明書を右クリック → すべてのタスク → インポート
- ルート証明書ファイルを選択し、信頼されたルート証明機関ストアに保存する
PowerShellで一度に実行することもできる。
Import-Certificate -FilePath "root_ca.crt" `
-CertStoreLocation Cert:\LocalMachine\Root
イントラネットを構築してみて
最初にTailscaleを知ったきっかけは、リモート開発時にローカルの開発サーバーへもっと簡単にアクセスする方法を探していたことだった。
しかし、TailscaleのトンネリングとMagicDNSは、当初の目的に使うには複雑で面倒に感じられ、Tailscaleがどのようなサービスなのか詳しく調べる前に忘れてしまっていた。
ところが、イントラネットを作りたいと思った瞬間に真っ先にTailscaleが頭に浮かび、結果的に最良の選択肢だったと思う。
Tailscaleの料金が高ければ、おそらく諦めていただろう。
しかし、グループに参加できるユーザー数には制限がある一方、デバイスの登録数は無制限という太っ腹なプランのおかげで、イントラネットの夢を実現できた。
また、ネットワークについてももう少し勉強してみたいと思うようになった。
イントラネットを構築する過程でも、ネットワークの流れを理解しただけで、Tailscaleが具体的にどのように動作するのか、LinuxでDNSがどのように切り替わるのかなどは理解できないまま作業していた。
Tailscaleについてより深く知ることは、コンピューターに関する知識を身につけるうえで大きな助けになるはずだと思う。