2026年6月5日

2026年6月5日

マルチテナント用サブドメインをSSL化する方法:ワイルドカード証明書と個別発行の選び方

テナントごとに tenant1.example.com のようなサブドメインを割り当てるサービスでは、SSL化の方法は大きく2つです。自社ドメイン配下のサブドメインだけなら「ワイルドカード証明書を1枚取得してDNS認証で更新する」方式、顧客の独自ドメインも受け付けるなら「ホスト名ごとに証明書を自動発行する」方式が基本になります。この記事では、両方式の選び方、Let's Encrypt と Certbot での具体的な手順、Webサーバーへの組み込み、つまずきやすい点と確認方法までを順に説明します。

まず構成を決める:どちらの方式が合うか

マルチテナントのSSLで最初に考えるのは「証明書に載せるホスト名を誰が管理しているか」です。

  • 自社ドメイン配下のサブドメインのみ(*.example.com の範囲): ワイルドカード証明書が向いています。テナントが増えても証明書は1枚のままで、発行回数の制限にも当たりにくくなります。
  • 顧客の独自ドメインも接続する(shop.customer.jp を自社サービスに向けてもらう): 他社のドメインはワイルドカードに含められないため、ホスト名ごとの個別発行が必要です。
  • 両方を併用する: 標準サブドメインはワイルドカード、独自ドメインだけ個別発行、という分担がよく使われます。

なお、ワイルドカード証明書 *.example.com が有効なのは1階層だけです。tenant1.example.com は対象ですが、api.tenant1.example.com や example.com そのものは含まれません。テナントの下にさらに階層を作る設計にすると証明書の管理が急に複雑になるので、ホスト名は「テナント名.ベースドメイン」の1階層に収めるのが無難です。

方式1:ワイルドカード証明書をDNS-01で取得する

Let's Encrypt でワイルドカード証明書を取得するには、DNS-01チャレンジ(DNSにTXTレコードを置いてドメインの管理権限を示す方法)が求められます。HTTP-01(Webサーバーに確認ファイルを置く方法)では取得できません。

更新は約90日ごとに発生し、Let's Encrypt は証明書の有効期間をさらに短くする計画も公表しています(2026年10月現在)。手作業でTXTレコードを書き換える運用は続かないため、DNS事業者のAPIを使って自動化できるCertbotのDNSプラグインを使います。ここでは Cloudflare を例にします。

# Debian / Ubuntu の例(ディストリビューションにより入手方法は異なります)
sudo apt install certbot python3-certbot-dns-cloudflare

# APIトークンを保存するファイル(権限は所有者のみ)
sudo mkdir -p /root/.secrets
sudo tee /root/.secrets/cloudflare.ini > /dev/null <<'EOF'
dns_cloudflare_api_token = your-api-token
EOF
sudo chmod 600 /root/.secrets/cloudflare.ini

# ベースドメインとワイルドカードを1枚にまとめて取得
sudo certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials /root/.secrets/cloudflare.ini \
  -d example.com -d '*.example.com'

ポイントは次の3つです。

  • APIトークンの権限を絞る: 対象ゾーンのDNS編集だけを許可したトークンを作ります。アカウント全体の権限を持つキーをサーバーに置くと、漏えいしたときの被害が大きくなります。
  • **-d example.com も並べる**: ワイルドカードはベースドメインを含まないため、両方を1枚に入れておくとサーバー設定が簡単になります。
  • **シェルでは '*.example.com' を引用符で囲む**: 囲まないと、カレントディレクトリのファイル名に展開されることがあります。

Route 53 や Google Cloud DNS など、他のDNS事業者向けのプラグインもあります。対応していない事業者の場合は、DNSだけを対応事業者に移す、あるいは acme.sh のように対応範囲の広いクライアントを使う方法が考えられます。

取得後は sudo certbot renew --dry-run で、更新処理がAPI経由で通るかを先に確かめておきます。初回はうまくいっても、トークンの期限切れや権限不足で更新だけ失敗する例は少なくありません。

Webサーバーへの組み込み

nginx の場合、server_name にワイルドカードを書き、取得した証明書を指定します。アプリ側(WordPressマルチサイトなど)は Host ヘッダーでテナントを判別するため、プロキシする場合はヘッダーを引き継ぎます。

server {
    listen 443 ssl;
    http2 on;   # nginx 1.25.1 以降の書き方。古い版は listen 443 ssl http2;
    server_name example.com *.example.com;

    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto https;
    }
}

server {
    listen 80;
    server_name example.com *.example.com;
    return 301 https://$host$request_uri;
}

Certbot の certonly はWebサーバー設定を書き換えないため、更新後にnginxへ証明書を読み直させる処理が必要です。/etc/letsencrypt/renewal-hooks/deploy/ に systemctl reload nginx を実行するスクリプトを置く方法が一般的です。これを忘れると、ファイルは更新されているのに古い証明書を配信し続け、期限切れで初めて気づくことになります。

WordPressマルチサイト(サブドメイン型)の場合

WordPressマルチサイトをサブドメイン型で運用している場合は、wp-config.php の SUBDOMAIN_INSTALL が true になっていることを確認します。そのうえで、ネットワーク管理画面の各サイト設定でサイトアドレスが https:// になっているかを見直します。投稿本文や設定に http:// のURLが残っていると混在コンテンツの警告が出るため、作業前にデータベースのバックアップを取り、WP-CLI の wp search-replace を --network と --dry-run 付きで試してから置換します。本番の前にステージング環境で一度通しておくと安全です。

方式2:独自ドメインごとに証明書を自動発行する

顧客の独自ドメインを受け付ける場合、顧客側にCNAMEなどで自社サービスへ向けてもらい、そのホスト名についてHTTP-01で証明書を発行します。テナント追加のたびに手作業で certbot -d を実行する運用は規模が大きくなると回らないため、次のような仕組みを使います。

  • Caddy のオンデマンドTLS: 初めてアクセスが来たホスト名に対してその場で証明書を取得します。誰でも任意のホスト名を向けられる状態だと無制限に発行要求が飛ぶため、「このホスト名は登録済みテナントか」を自社APIに問い合わせる ask の設定が欠かせません。
  • Kubernetes の cert-manager: Ingress にホスト名を追加すると証明書リソースが作られ、発行と更新が自動化されます。
  • ロードバランサーやCDNの証明書管理機能: クラウド事業者やCDNが独自ドメインの証明書を代行する機能を提供している場合があります。対応範囲や上限は事業者・プランで異なるため、各社のドキュメントで確認してください。

Caddy の例を示します。ask に指定したURLは ?domain=ホスト名 付きで呼ばれ、200を返したときだけ発行に進みます。

{
    on_demand_tls {
        ask http://127.0.0.1:9123/check
    }
}

https:// {
    tls {
        on_demand
    }
    reverse_proxy 127.0.0.1:8080
}

/check 側は、テナント管理DBに登録済みで、かつ顧客のDNSが自社に向いていることを確認できたホスト名だけ200を返すように実装します。

つまずきやすい点

  • 発行回数の制限: Let's Encrypt には登録ドメイン単位・アカウント単位の発行回数制限があります。テナントごとに tenantN.example.com の個別証明書を発行する設計にすると、テナントが急増したときに制限に当たりやすくなります。自社ドメイン配下はワイルドカードにまとめるのが現実的です。具体的な上限値は変更されることがあるため、公式ドキュメントの Rate Limits を参照してください。
  • CAAレコード: ドメインにCAAレコードを設定している場合、利用する認証局(Let's Encrypt なら letsencrypt.org)が許可されていないと発行に失敗します。ワイルドカードを許可するには issuewild の指定も確認します。
  • 更新失敗に気づけない: Let's Encrypt は2025年に有効期限切れの通知メールを終了しています。外部の監視サービスや、後述のコマンドを定期実行する仕組みで期限を見張る必要があります。
  • 秘密鍵とAPIトークンの扱い: 秘密鍵やトークンをGitに含めない、バックアップの保存先の権限を絞る、退職者や外部委託先と共有したトークンは作業後に無効化する、といった基本を守ります。

動作確認の方法

設定後は、新規テナントを想定した適当なサブドメインと、既存テナントの両方で証明書の中身を確かめます。

# 配信されている証明書のホスト名(SAN)と有効期限を表示
openssl s_client -connect tenant1.example.com:443 -servername tenant1.example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -subject -ext subjectAltName -enddate

-servername を付けないとSNIが送られず、別のホスト用の証明書が返って誤った判断をすることがあります。SANに *.example.com が含まれ、期限が想定どおりであれば成功です。あわせてブラウザでも開き、混在コンテンツの警告が出ていないかを確認します。

まとめ

  • 自社ドメイン配下のテナントは、ワイルドカード証明書をDNS-01で取得し、DNS APIで更新まで自動化するのが基本です。
  • ワイルドカードは1階層のみ有効で、ベースドメイン自体は含まれません。ホスト名の設計は1階層に収めます。
  • 顧客の独自ドメインは個別発行が必要です。Caddy のオンデマンドTLSやcert-managerを使い、未登録のホスト名で発行しない制御を入れます。
  • 更新後のリロード、CAAレコード、発行回数の制限、期限監視を事前に確認し、変更はバックアップを取ってステージングで試してから本番に反映します。

お気軽にご相談ください

お見積りへ お問い合わせへ
▲