
そこで候補になるのが、*.example.comという形式のワイルドカード証明書だ。1枚の証明書で複数のサブドメインを保護できる。ただし、Let's Encryptでワイルドカード証明書を発行・更新するには、HTTP-01ではなくDNS-01チャレンジが必須になる。
ここを曖昧にしたまま設定を始めると、「証明書の取得はできたのに自動更新できない」「更新時だけ手動でTXTレコードを追加している」という、個人開発で最も避けたい運用に入りやすい。
ワイルドカード証明書の自動更新は、証明書の問題というより、DNSをどう安全に操作するかの問題だ。
私が個人開発でSSL証明書を扱うときに重視するのは、発行できるかどうかだけではない。90日間の有効期限を意識せず、ユーザーのアクセスを止めず、しかもDNSの強い権限をサーバーに渡さないこと。ここまで含めて、初めて「自動化できた」と考えている。
ワイルドカード証明書にDNS-01チャレンジが必要な理由
Let's Encryptの証明書発行では、申請者が本当にそのドメインを管理しているかを確認する。これがチャレンジ、つまりドメイン所有権の検証だ。
代表的な方法がHTTP-01チャレンジとDNS-01チャレンジである。
HTTP-01では、対象ドメインのWebサーバーに特定のファイルを配置する。認証局がそのURLへアクセスし、正しい内容が返ってくれば、ドメインを管理していると判断する。
一方、DNS-01では、指定された文字列をDNSのTXTレコードとして登録する。Let's EncryptがDNSへ問い合わせ、そのTXTレコードを確認することで所有権を検証する。
この2つは似た認証方法に見えるが、ワイルドカード証明書では使える方式が違う。*.example.comの証明書を発行する場合、DNS-01チャレンジが必須だ。
HTTP-01では「ドメイン全体の管理」を証明しにくい
HTTP-01チャレンジは、特定のホスト名へHTTPアクセスできることを確認する方式だ。たとえば example.com や www.example.com といった、実際にWebサーバーが応答するホストを対象にする。
しかし、ワイルドカード証明書が対象にするのは、まだ存在していないサブドメインも含めた範囲になる。
*.example.comを取得した時点で、次のようなサブドメインを保護できる。
app.example.comadmin.example.comapi.example.comstaging.example.com
このとき、特定のサブドメインに認証用ファイルを置くだけでは、「このドメイン全体をワイルドカードとして利用できる権限がある」ことを確認しにくい。
DNS-01では、_acme-challenge.example.comという認証専用の場所にTXTレコードを登録する。DNSゾーンを変更できるということは、対象ドメインの管理権限を持っている可能性が高い。ワイルドカード証明書では、このDNS上の証明が必要になる。
HTTP-01とDNS-01の違い
| 項目 | HTTP-01チャレンジ | DNS-01チャレンジ |
|---|---|---|
| 認証方法 | Webサーバーに認証ファイルを配置 | DNSにTXTレコードを登録 |
| ワイルドカード証明書 | 利用できない | 利用できる |
| 必要な操作 | HTTPで対象ホストへアクセス可能にする | DNSゾーンをAPIまたは手動で変更する |
| Webサーバー停止中の認証 | 難しい | DNSが機能していれば可能 |
| 自動更新の鍵 | Webサーバーとの連携 | DNS APIとの連携 |
| 主な注意点 | リバースプロキシやリダイレクト | TXT反映遅延とDNS権限管理 |
個人開発では、Docker上のアプリケーション、リバースプロキシ、CDN、ロードバランサーなどが複雑に絡むことがある。その場合、HTTP-01はWebサーバーの構成に引っ張られやすい。
DNS-01なら、認証の中心をDNSに寄せられる。これは強みだ。ただし、DNS APIの認証情報をどう保管するかという新しい課題も生まれる。
DNS-01チャレンジでTXTレコードが検証される仕組み
DNS-01チャレンジは、単に任意の文字列をTXTレコードへ登録する仕組みではない。Let's EncryptとACMEクライアントが、発行要求ごとに異なる値を生成し、それをDNS上で突き合わせる。
大まかな流れは次の通りだ。
1. ACMEクライアントが証明書の発行をLet's Encryptへ要求する
2. Let's Encryptがチャレンジ用のトークンを返す
3. ACMEクライアントがアカウント鍵を使って検証用の値を作る
4. _acme-challenge.example.comにTXTレコードを登録する
5. Let's EncryptがDNSへ問い合わせる
6. 正しいTXTレコードを確認できれば、ドメイン所有権を認証する
7. 証明書が発行される
実際の検証では、Let's Encryptから提示されたトークンと、ACMEアカウントの鍵に関係する情報から作られた値が使われる。クライアント側が適当に決めた固定文字列を置いておけばよいわけではない。
ここが、手動更新が安定しない理由でもある。初回発行時に手動でTXTレコードを追加できたとしても、次回更新時には別のトークンが発行される。以前登録した値を残しておくだけでは、次の認証を通過できない。
_acme-challengeは認証専用のDNS名
DNS-01で使うレコード名は、通常次の形式になる。
_acme-challenge.example.com
ワイルドカード証明書の対象が *.example.com であっても、TXTレコードを登録するのはワイルドカード名そのものではない。_acme-challengeという専用の名前だ。
DNSの管理画面では、ゾーン名の入力方法がサービスごとに違う。たとえば、DNSプロバイダーによってはホスト名欄に _acme-challengeだけを入力する場合もあれば、完全修飾ドメイン名を入力する場合もある。
この違いは、初回設定でよくつまずくポイントだ。登録したつもりなのに、実際には _acme-challenge.example.com.example.com のような別名になっていることがある。
認証が失敗したときは、まず次の2点を切り分ける。
- TXTレコードが正しい名前に登録されているか
- 外部のDNS問い合わせから、その値が見えているか
管理画面に表示されていることと、インターネット上のDNSから参照できることは同じではない。
TXTレコードの反映には時間差がある
DNSレコードの変更は、登録直後に世界中へ反映されるとは限らない。DNSキャッシュやTTL、権威DNSの構成によって、確認できるまで時間がかかることがある。
このため、ACMEクライアントがTXTレコードを登録した直後に検証を開始すると、Let's Encrypt側から古い状態が見える可能性がある。
DNS APIに対応したACMEクライアントやプラグインは、TXTレコードを登録した後に一定の待機時間を設けたり、DNSの状態を確認したりする処理を持っている。ここを自前のスクリプトで簡略化すると、たまに更新に失敗する不安定な仕組みになりやすい。
DNS-01の検証で見るべきなのは、サーバー上の設定ファイルだけではない。認証局から見たDNSの状態。ここが本質になる。
自動更新の中心はDNS APIとの連携
ワイルドカード証明書を自動更新するには、更新のたびに変わるTXTレコードを自動で書き換えなければならない。
この処理を担うのが、ACMEクライアントとDNS APIの連携だ。
代表的な選択肢には、CertbotのDNS APIプラグイン、lego、acme.shなどがある。どれを使うかよりも、自分が利用しているDNSサービスに対応しているか、認証情報を安全に管理できるか、失敗時のログを追えるかが重要になる。
自動更新フロー
自動更新の流れを、運用目線で分解するとこうなる。
1. 定期実行の仕組みがACMEクライアントを起動する
2. クライアントが証明書の有効期限を確認する
3. 更新が必要な時期であれば、Let's Encryptへ更新要求を送る
4. Let's Encryptが新しいDNS-01チャレンジを発行する
5. DNSプラグインがAPI経由でTXTレコードを登録する
6. DNSの反映を待つ
7. Let's EncryptがTXTレコードを検証する
8. 新しい証明書を保存する
9. Webサーバーやリバースプロキシへ証明書を読み込ませる
10. 古いTXTレコードを削除する
この最後の「証明書を読み込ませる」まで完了しないと、ファイルだけ新しくなっていて、実際の通信には古い証明書が使われ続けることがある。
NginxやApache、Traefik、ロードバランサーなど、TLS終端を担当している場所を明確にしておく必要がある。Docker環境では、証明書を取得するコンテナと、HTTPSを受けるコンテナが別になっている構成も珍しくない。
その場合、証明書ファイルを共有ボリュームへ保存し、更新後に対象コンテナへ設定の再読み込みを伝える設計が必要になる。
更新処理を定期実行するだけでは不十分
Let's Encryptの証明書の有効期間は90日間だ。一般的には、有効期限が30日を切った段階で更新を試みる。
ここで誤解されやすいのが、「一度手動で取得できたから、同じコマンドをcronに登録すれば自動更新できる」という考え方だ。
手動のDNS-01では、更新のたびに新しいTXTレコードを手で登録する必要がある。cronやsystemd timerはコマンドを実行するだけであり、DNSに新しいトークンを登録する機能を自動的に追加してくれるわけではない。
自動化には、少なくとも次の3つが必要になる。
- ACMEクライアント
- DNS APIまたは認証用DNSへの連携
- 更新後にサービスへ証明書を反映する処理
どれか1つでも欠けると、運用全体としては自動化されていない。
「定期実行できる」と「証明書更新が自動化されている」は別の話だ。TXTレコードの生成・登録・検証・反映までつながって、ようやく運用になる。
DNS APIの権限を広くしすぎない
DNS API連携で最も慎重に扱いたいのが、APIキーの権限だ。
証明書更新サーバーにDNSプロバイダーの管理者権限を持つキーを置くと、そのサーバーが侵害されたときにDNSゾーン全体を変更される危険がある。
攻撃者にDNSの書き換え権限を奪われると、Webサイトの向き先を変更されたり、メール関連のレコードを改ざんされたりする可能性がある。証明書更新を自動化したいだけなのに、ドメイン全体の管理権限を渡してしまうのは、権限設計として重い。
可能であれば、次のように絞り込む。
- 対象ドメインだけを操作できるAPIキーにする
- TXTレコードの作成・削除に必要な権限だけを与える
_acme-challengeに関係するレコードだけを扱う- APIキーをリポジトリへ保存しない
.envファイルを公開ディレクトリへ置かない- ログにAPIキーや認証トークンを出力しない
- 更新用ホストとアプリケーション実行環境を分離する
DNSサービスによって、レコード単位まで細かく権限を制限できるかは異なる。細かい制御ができない場合は、後述するCNAME委任やacme-dnsの利用を検討する価値がある。
Dockerでの秘密情報管理
Docker環境では、APIキーをイメージへ焼き込まないことが基本になる。
Dockerfileの中で環境変数を設定したり、設定ファイルをコピーしたりすると、イメージのレイヤーや履歴から情報が残ることがある。個人開発ではつい簡略化したくなるが、証明書更新のためだけに作ったイメージが、DNS操作権限を抱えたまま長期間残るのは避けたい。
運用方法は環境によって変わるが、少なくとも次の境界は意識したい。
- 秘密情報は実行時に注入する
- バックアップに秘密情報を含めない
- コンテナログで値を表示しない
- 更新コンテナに不要なアプリケーション権限を与えない
- APIキーを変更したときに、どこへ反映するか把握しておく
技術的に証明書を取得できることと、事故が起きても被害を限定できることは別の評価軸だ。サービス運用では、後者を後回しにすると、あとで必ず修正コストが発生する。
CNAME委任とacme-dnsで管理権限を分離する
DNS APIへ広い権限を与えたくない場合、_acme-challengeの処理だけを別のDNSへ委任する方法がある。
たとえば、メインのドメインDNSに次のようなCNAMEを設定する。
_acme-challenge.example.com → 認証専用の別名
この構成では、Let's Encryptが _acme-challenge.example.com を確認すると、CNAME先のDNSレコードを参照する。ACMEクライアントは、認証専用のDNSゾーンへTXTレコードを登録すればよい。
メインDNS全体を操作するAPIキーを証明書更新サーバーへ渡す必要がなくなる。これがCNAME委任の大きな利点だ。
acme-dnsを使う構成
acme-dnsは、ACMEチャレンジ用のTXTレコード登録を専用に扱う仕組みだ。通常のDNS管理と証明書認証の操作を分離できる。
考え方としては、次のようになる。
- 通常のWebサイトやメールのDNSはメインDNSで管理する
_acme-challengeだけを認証専用DNSへ委任する- ACMEクライアントには認証専用DNSの登録権限だけを持たせる
- メインDNSの管理者権限APIキーはサーバーへ置かない
この方法は、権限分離を重視する場合に向いている。一方で、認証専用DNSの構築・可用性・バックアップという運用対象が増える。
個人開発では、構成を増やすこと自体がリスクになる。セキュリティを高めるために新しいサービスを追加した結果、そのサービスを誰も監視していないという状態も起こり得る。
CNAME委任を採用するなら、次の仮説を置いてから判断したい。
- DNS APIの広い権限を減らせる
- 追加した認証専用DNSを継続的に保守できる
- 証明書更新に失敗したときの原因を追跡できる
- DNS委任の設定を将来の自分が理解できる
最後の項目は軽く見られがちだが、個人開発ではかなり重要だ。半年後に構成を見直したとき、なぜCNAMEが必要なのか説明できない仕組みは、改善の足かせになる。
systemd timerとcron、どちらで更新するか
証明書更新の定期実行には、cronやsystemd timerを使える。どちらでも定期的にACMEクライアントを実行できるが、運用時の見え方は少し違う。
cronは慣れている人が多く、設定も簡単だ。ただし、実行履歴や失敗状態の確認を別途用意しなければならないことがある。
systemd timerは、サービスとして実行処理を管理しやすい。ログをjournalctlで追跡でき、サービスとの依存関係も組みやすい。Linux環境でsystemdを利用できるなら、証明書更新のような定期処理には扱いやすい選択肢になる。
重要なのは、どちらを選ぶかより「失敗を見つけられるか」だ。
証明書更新の自動化で見るべき指標は、証明書が更新された回数だけではない。
- 更新処理が最後に成功した日時
- 証明書の有効期限までの日数
- DNS APIのエラー回数
- TXTレコードの反映待ちによる失敗
- 更新後のWebサーバー再読み込みの成否
- HTTPSアクセス時に実際に返されている証明書の期限
更新処理が毎日動いていても、毎回失敗していれば意味がない。逆に、証明書の期限だけを監視していると、失敗に気づく頃には残り日数が少なくなっている。
有効期限を監視する
Let's Encryptの証明書は90日間有効だ。更新処理は、有効期限が残り30日を切ったタイミングで実行される構成が一般的になる。
この仕組みでは、更新コマンドを毎日または定期的に動かしても、期限に余裕がある間は何もしない。必要な時期にだけ更新を実行する。
この方式の利点は、更新処理を一度失敗しても、翌日の実行で再試行できることだ。毎日実行しているからこそ、1回のDNS障害や一時的なAPIエラーで即座にサービス停止へつながりにくい。
ただし、再試行に任せるだけでは不十分だ。一定期間連続で失敗した場合に通知を出す仕組みが必要になる。
証明書更新の監視は、アプリケーションのアクティブユーザー数やコンバージョンのように目立つ指標ではない。だからこそ放置されやすい。しかし、SSL証明書が切れた瞬間、ユーザーのアクセスは一気に止まる。
ユーザーから見れば、「管理画面が少し壊れている」ではない。ブラウザーに警告が表示され、サービス全体が危険なサイトに見える。
更新後にWebサーバーへ反映する
新しい証明書が発行されても、Webサーバーが自動で新しいファイルを読み込むとは限らない。
NginxやApacheは、起動時に証明書を読み込む構成が一般的だ。そのため、証明書ファイルが更新されただけでは、プロセスが保持している証明書が古いままになる可能性がある。
更新フローの最後には、Webサーバーの再読み込みを組み込む。
ここで注意したいのは、再起動と再読み込みの違いだ。サービスを完全に停止して起動し直す方法は分かりやすいが、短い時間でも接続中のユーザーへ影響することがある。対応しているWebサーバーであれば、まずは設定の再読み込みを使う方が、サービスへの影響を抑えやすい。
Docker環境では、証明書更新コンテナからリバースプロキシへどう通知するかが課題になる。
- 同じコンテナ内で更新とWebサーバーを動かす
- Dockerソケットを通じて再起動する
- ホスト側のスクリプトから再読み込みする
- オーケストレーション環境のデプロイ処理と連携する
それぞれに利点とリスクがある。Dockerソケットをコンテナへ渡す方法は便利だが、ソケットへのアクセス権限が強くなりやすい。証明書更新のためだけに、コンテナへホスト操作に近い権限を与える設計は慎重に判断したい。
私なら、まず「どのプロセスがTLSを終端しているのか」を図にする。アプリケーションコンテナではなく、Nginx、Traefik、CDN、ロードバランサーのどこかが証明書を使っているかもしれない。
技術設定を先に触るのではなく、通信経路を整理する。ここを飛ばすと、更新後にどこへ反映すればいいのか分からなくなる。
失敗しやすいパターンを先に潰す
DNS-01チャレンジの自動更新は、仕組みを理解すれば難しくない。ただし、失敗する場所がいくつか決まっている。
DNS APIがTXTレコードを操作できない
利用中のDNSサービスにAPIがない場合、ACMEクライアントからTXTレコードを自動登録できない。
管理画面で手動登録できることと、APIで自動操作できることは別だ。APIが提供されていない場合、手動更新を続けるか、DNSサービスを変更するか、acme-dnsなどの専用構成を検討することになる。
「APIがないけれど、外部から何とか書き換える」という発想は、運用の安定性と安全性を崩しやすい。未知の連携サービスにドメイン管理権限を渡す前に、対応するACMEクライアントや委任方法があるかを確認したい。
TXTレコードが残り続ける
検証後にTXTレコードを削除できず、古い値が複数残ることがある。
Let's Encrypt側が正しい値を見つけられれば認証できる場合もあるが、不要なレコードが増え続ける状態は避けたい。DNS管理画面が見づらくなり、別の認証や設定との区別も難しくなる。
自動化では、登録だけでなく削除まで確認する。途中で処理が落ちた場合に、次回実行が古いレコードをどう扱うかも見ておく。
DNSの反映前に検証が始まる
APIでTXTレコードを登録した直後、Let's Encryptの検証が走ると、まだ外部DNSから値が見えないことがある。
この問題は、DNSサービスやネットワーク構成によって発生頻度が変わる。ローカルのDNS問い合わせでは見えるのに、別の環境から見ると見えないというケースもある。
対策は、ACMEクライアント側の待機時間やDNS伝播確認を適切に設定すること。固定の待機秒数だけに頼るより、実際に必要なDNS名とTXT値を確認できる仕組みの方が信頼性は高い。
証明書は更新されたが、ユーザーには古い証明書が返る
証明書ファイルの更新後に、Webサーバーの再読み込みが実行されていないパターンだ。
サーバー上のファイルを確認すると新しい期限になっている。しかし、ブラウザーや外部のTLS確認では古い証明書が返ってくる。
この場合、見る場所を分ける必要がある。
- ACMEクライアントが保存した証明書
- Webサーバーが設定ファイルで参照しているパス
- 実際に外部公開されているエンドポイント
- CDNやロードバランサーが保持している証明書
サービス運用では、「ファイルが正しい」より「ユーザーに正しい証明書が返っている」ことが重要だ。
個人開発での現実的な設計
個人開発では、最初から大規模な証明書基盤を作る必要はない。必要なのは、サービスの成長に合わせて壊れにくい最小構成を選ぶことだ。
サブドメインがまだ少なく、Webサーバーも単純なら、ワイルドカード証明書を使わず、ホストごとに証明書を管理する方法もある。ワイルドカード証明書は便利だが、DNS APIの権限管理が必要になる。
一方、開発環境、管理画面、API、ユーザー向け画面など複数のサブドメインを運用するなら、ワイルドカード証明書によって設定をまとめられる。サブドメイン追加のたびに証明書の発行対象を増やさなくてよい。
判断の軸は、次のように整理できる。
- サブドメインが増える予定はあるか
- DNS APIを安全に使えるか
- TLS終端の場所を把握できているか
- 証明書更新の失敗を通知できるか
- CNAME委任を維持できる運用体制があるか
- サービス停止時のユーザー影響が大きいか
私が重視するのは、現在の作業量だけではない。3か月後、半年後に自分がその設定を見直せるかどうかだ。
個人開発では、担当者が自分一人である。自分が忘れる、自分が忙しい、自分が別のプロダクトに集中する。これを前提に設計した方がいい。
自動化は「成功」ではなく「失敗時の体験」で評価する
Let's EncryptのDNS-01チャレンジを自動化すると、普段は証明書の存在を意識しなくて済むようになる。これは大きな改善だ。
ただし、自動化の価値は、正常系の作業時間を短くすることだけではない。DNS APIが落ちたとき、TXTレコードが反映されないとき、証明書の反映に失敗したときに、どれだけ早く気づけるかが本当の評価になる。
運用前には、一度は意図的に失敗させたい。
- DNS APIの認証情報を一時的に無効にする
- DNS反映の待機時間を短くして挙動を見る
- Webサーバーの再読み込みを止めて確認する
- 証明書の保存先を間違えた場合のログを見る
- 更新失敗時に通知が届くかを確認する
本番環境で無理に壊す必要はない。検証用ドメインやステージング環境を使い、どのログを見れば原因にたどり着けるかを確認しておく。
ここで見るのは、単に「エラーが出たか」ではない。
- DNS登録のどの段階で失敗したか
- Let's Encryptの検証が始まったか
- TXTレコードは外部から見えたか
- 証明書ファイルは生成されたか
- Webサーバーへの反映処理まで進んだか
- 次回実行で再試行できる状態か
この粒度で追えると、障害対応が推測ではなく検証になる。プロダクト改善と同じだ。仮説を置き、ログで確認し、失敗した箇所だけを直す。
まとめ:DNS-01を「証明書取得」ではなく運用設計として考える
Let's Encryptでワイルドカード証明書を扱う場合、DNS-01チャレンジは避けて通れない。
DNS-01では、_acme-challenge.example.comにTXTレコードを登録し、Let's EncryptがDNS問い合わせによってドメイン管理権限を確認する。ワイルドカード証明書はHTTP-01では発行できないため、DNS APIや認証専用DNSとの連携が必要になる。
自動更新を成立させる要素は、次の通りだ。
1. ワイルドカード証明書にはDNS-01チャレンジを使う
2. ACMEクライアントで毎回変わるTXTレコードを生成する
3. DNS APIまたはacme-dns経由でTXTレコードを登録する
4. DNS反映後にLet's Encryptの検証を実行する
5. 発行された証明書をTLS終端へ反映する
6. 90日間の有効期限と残り30日前の更新を監視する
7. APIキーの権限を必要最小限に抑える
8. 更新失敗を検知できる通知を用意する
最初のゴールは、証明書を発行すること。次のゴールは、更新処理を自動化すること。そして本当のゴールは、更新に失敗してもユーザーの痛みになる前に気づけることだ。
個人開発では、機能追加に比べて証明書管理は地味に見える。しかし、HTTPSが止まれば、コンバージョンもログインもサポート対応も止まる。サービスの信頼性を支える土台として、早い段階で仕組みにしておく価値は高い。
次に試したいことは、証明書の有効期限だけでなく、DNS APIの失敗回数、更新後の再読み込み結果、外部から見える証明書の期限まで一つの監視画面にまとめることだ。
「たぶん更新されている」から「更新状態を数字で確認できる」へ。個人開発の運用は、こうした小さな検証の積み重ねで、少しずつ強くなる。
Related reading: Laravel SanctumとPassport:API認証における選定基準の最適解 and Axiosインターセプターによるトークンリフレッシュ:非同期リクエストが並行処理される内部挙動.