コンテンツにスキップ

宅内サービスのHTTPS化

Tailscaleには、Tailnet内のホスト名(<machine>.<tailnet>.ts.net)に対して Let's Encryptの正規証明書を発行してくれる機能 があります。Tailscale社が ts.net を持っていて、証明書発行時のドメイン認証(DNS-01チャレンジ)を代行してくれる仕組みです。

管理画面で MagicDNS と HTTPS Certificates を有効にしたら、あとは tailscale serve を1行叩くだけ:

sudo tailscale serve --bg 3000

これで自宅PCの localhost:3000https://home-pc.<tailnet>.ts.net/ で開けるようになります。TLS終端も、証明書の取得も更新も、ぜんぶTailscale任せ。自己署名証明書を各端末に信頼させて回る手間も、ブラウザの警告もなし。それでいて公開範囲は Tailnet内だけ で、インターネットには一切出ていません。

なぜ宅内にHTTPSが要るのか

近年のブラウザは、カメラ・PWA・パスキー・クリップボードAPIといった機能を HTTPSのページでしか動かしてくれません。開発PC上の localhost は例外扱いされますが、スマホ実機から http://192.168.x.x:3000 を見に行くと単なるHTTPなので全滅。「実機でしか確認できない機能ほどHTTPSを要求する」という噛み合わせの悪さがあります。

Tailscaleの証明書なら、端末側に何も入れず、インターネットにも出さずに、これが解決します。

実例:Dockerコンテナ群に「ポート番号なし」のURLでアクセス

自宅PCの docker compose で動かしている複数のWebアプリ(管理画面が :21202、ユーザー向けが :21203、みたいな構成)を、スマホやタブレットから https://home-pc.<tailnet>.ts.net/myapp/admin/ という ポート番号なしのURL で開けるようにしています。

ホスト側にリバースプロキシは立てていません。tailscale serve のパス指定で、コンテナのポートへ直接振り分けています:

sudo tailscale serve --bg --set-path /myapp/admin http://127.0.0.1:21202/myapp/admin
sudo tailscale serve --bg --set-path /myapp/app   http://127.0.0.1:21203/myapp/app

# 振り分けの確認
tailscale serve status

サブパス配下に置くときの注意点がひとつ。アプリ側がパスプレフィックスを知らないとリダイレクトが壊れます(Next.jsなら next.config.jsbasePath を設定して、上の例のようにプロキシ先URLにも同じパスを付けてそのまま渡す)。フロントからAPIを叩く場合も、localhost:ポート をハードコードするとFQDNアクセス時にCORSで死ぬので、フレームワークのrewritesなどでサーバサイド経由の同一オリジンに寄せておくのが無難です。

証明書ファイルが欲しいときは tailscale cert

Apache や nginx など既存のリバースプロキシでTLS終端したい場合は、証明書をファイルとして取得することもできます:

tailscale cert home-pc.<tailnet>.ts.net
# → .crt / .key が出力されるので、Webサーバーに設定する

こちらは自動更新の仕組みがないので、cron / systemd timer で週1回まわして nginx -s reload あたりを叩いておきます。tailscale serve は443番を掴むため、リバースプロキシと同居させるときはポートの衝突にも注意。serveに寄せられるなら、そのほうが運用は楽です。

注意点

  • 証明書を発行するとマシン名がCertificate Transparencyログに載る(IPは出ない)。案件名や顧客名をマシン名に含めないこと
  • 使えるのは *.ts.net のみ。独自ドメインには使えない
  • tailscale serve はTailnet内限定。インターネットに公開したいときだけ、別機能のFunnelを使います