ACME(RFC 8555)で証明書の取得を自動化するとき、最初に決めるのが HTTP-01 と DNS-01 のどちらを使うかです。HTTP-01 では、認証局がポート 80 経由で Web サーバーからトークンを取得します。DNS-01 では、認証局がゾーン内の TXT レコードを照会します。どちらも Baseline Requirements に定められたドメイン認証の方法で、得られる証明書もまったく同じですが、検証できる対象、実行できる場所、起こりうる問題が異なります。
比較表
| HTTP-01 | DNS-01 | |
|---|---|---|
| 仕様 | RFC 8555 §8.3、Baseline Requirements §3.2.2.4.19 | RFC 8555 §8.4、Baseline Requirements §3.2.2.4.7 |
| 証明する内容 | その名前に応答する Web サーバーの管理権限 | DNS ゾーン、または委任された _acme-challenge の名前の管理権限 |
| 証明の置き場所 | http://name/.well-known/acme-challenge/token | _acme-challenge.name の TXT レコード |
| 内容 | 鍵認可(token.thumbprint) | その SHA-256 ダイジェストを base64url にしたもの(43 文字) |
| ワイルドカードの名前 | 認められない | 認められる |
| IP アドレスの証明書 | 認められる(RFC 8738) | 不可能 |
| ネットワークの要件 | 受信方向の TCP ポート 80。Let's Encrypt はポート 80 または 443 へのリダイレクトを最大 10 回までたどる | 到達できる必要があるのは権威 DNS サーバーだけ |
| 内部ホストやファイアウォールの内側のホスト | 使えない | 使える |
| 多数のサーバーやエニーキャスト | すべてのノードがトークンを返すか、1 台の応答サーバーにプロキシする必要がある | 名前ごとに 1 つのレコード。サーバーの構成とは無関係 |
| サーバー上のシークレット | Web ルートへの書き込み権限以外になし | DNS API の認証情報(CNAME による委任で範囲を限定する) |
| よくある失敗 | ポート 80 の遮断、リダイレクトのループ、CDN や WAF による応答、古い AAAA レコード | セカンダリサーバーへの反映の遅れ、壊れた DNSSEC、消し忘れたレコード、API の制限 |
CA が実際に確認する内容
どちらのチャレンジも鍵認可(key authorization)を使います。これは、チャレンジのトークンと ACME アカウント鍵の JWK サムプリント(RFC 7638)をドットでつないだものです。アカウント鍵が証明の一部になっているため、トークンを見た第三者が、自分のアカウントでそのチャレンジを完了することはできません。HTTP-01 は鍵認可をそのまま返し、DNS-01 は TXT レコードに収まるその SHA-256 ダイジェストを公開します。以下のスクリプトは、RFC 7638 の例のアカウント鍵と RFC 8555 の例のトークンを使って、両方の値を再現します(Node.js 26)。
import { createHash } from "node:crypto";
// Account public key: the RSA example JWK from RFC 7638 §3.1
const accountJwk = {
kty: "RSA",
e: "AQAB",
n: "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
};
const b64url = (buf) => buf.toString("base64url");
const sha256 = (s) => createHash("sha256").update(s).digest();
// RFC 7638: hash the required members in lexicographic order
const { e, kty, n } = accountJwk;
const thumbprint = b64url(sha256(JSON.stringify({ e, kty, n })));
console.log("account key thumbprint:", thumbprint);
// http-01 (RFC 8555 §8.3): serve the key authorization itself
const httpToken = "LoqXcYV8q5ONbJQxbmR7SCTNo3tiAXDfowyjxAjEuX0";
console.log("\nhttp-01");
console.log(` GET http://www.example.org/.well-known/acme-challenge/${httpToken}`);
console.log(` body: ${httpToken}.${thumbprint}`);
// dns-01 (RFC 8555 §8.4): publish base64url(SHA-256(key authorization))
const dnsToken = "evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA";
const txt = b64url(sha256(`${dnsToken}.${thumbprint}`));
console.log("\ndns-01");
console.log(` _acme-challenge.www.example.org. 300 IN TXT "${txt}"`);
account key thumbprint: NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs
http-01
GET http://www.example.org/.well-known/acme-challenge/LoqXcYV8q5ONbJQxbmR7SCTNo3tiAXDfowyjxAjEuX0
body: LoqXcYV8q5ONbJQxbmR7SCTNo3tiAXDfowyjxAjEuX0.NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs
dns-01
_acme-challenge.www.example.org. 300 IN TXT "ZTRx1Ckl1-tM05o5zaizTTA0yUy5AGereMgSNWC6Ll8"
サムプリントは RFC 7638 §3.1 に掲載された値と一致しており、計算が正しいことを確認できます。これらの処理はすべてクライアントが行いますが、値を知っておくと、失敗したチャレンジを curl や dig で手作業でデバッグするときに役立ちます。
複数のネットワーク視点からの検証と DNSSEC
2025年から、パブリック CA は各検証をネットワーク上の複数の地点から確認しなければなりません(Baseline Requirements §3.2.2.9)。2026年6月15日以降は、互いに 500 km 以上離れ、2 つ以上の地域インターネットレジストリの地域にまたがる 4 つ以上のリモート視点が参加し、結果の不一致が認められるのは 1 つまでです。この最小数は 2026年12月15日に 5 つへ引き上げられます。つまり HTTP-01 では、その名前のすべてのエニーキャストノード、すべての GeoDNS の応答先、IPv4 または IPv6 のすべてのアドレスがトークンを返さなければなりません。DNS-01 では、CA に検証を依頼する時点で、すべての権威ネームサーバーがすでにレコードを持っていなければなりません。
2026年3月15日からは、CA はドメイン認証と CAA のルックアップで DNSSEC も検証しなければなりません(Ballot SC-085v2)。署名されていないゾーンには影響しませんが、署名済みのゾーンで署名が期限切れになっていたり DS レコードが壊れていたりすると、どちらのチャレンジでも証明書を発行できなくなりました。CA が確認するチェーンは DNSSEC インスペクターで確認できます。
DNS の認証情報の権限を最小限にする
DNS-01 の主なリスクは、必要になる認証情報です。Web サーバー上に置いた DNS API トークンで、ゾーン全体を書き換えられることも少なくありません。CA は CNAME や NS による委任をたどる通常の DNS の名前解決で TXT レコードを照会するため、_acme-challenge の名前を別のゾーンに向けておけば、サーバーに書き込みを許可するのはそのゾーンだけで済みます。
_acme-challenge.example.com. 3600 IN CNAME example-com.acme.example.net.
さらに、acme-dns などのツールや、1 つのレコードまたは 1 つのゾーンに範囲を限定したプロバイダーのトークンを使えば、盗まれた認証情報でできることを制限できます。これに、RFC 8657 のパラメーターを使った CAA レコードを組み合わせてください。CA は 2027年3月15日からこれらのパラメーターを処理しなければなりません。
example.com. IN CAA 0 issue "letsencrypt.org; accounturi=https://acme-v02.api.letsencrypt.org/acme/acct/1234567890; validationmethods=dns-01"
example.com. IN CAA 0 issuewild "letsencrypt.org; accounturi=https://acme-v02.api.letsencrypt.org/acme/acct/1234567890; validationmethods=dns-01"
validationmethods を設定しておけば、サーバーのポート 80 や Web 上のパスを一時的に乗っ取った攻撃者でも、HTTP-01 で証明書を取得することはできなくなります。これらのレコードは CAA レコード生成ツールで作成できます。
TLS-ALPN-01 と永続的な DNS レコード
TLS-ALPN-01(RFC 8737)は、ポート 443 の TLS ハンドシェイクの中で検証を行い、acme-tls/1 プロトコルと、鍵認可のダイジェストを含む自己署名証明書を使います。ポート 80 が閉じている環境の、TLS を終端するプロキシやロードバランサーに適しています。HTTP-01 と同様に IP アドレスは検証できますが、ワイルドカードは検証できません。
DNS-PERSIST-01 は、更新のたびに必要だった DNS の変更をなくします。Ballot SC-088v3(2025年10月)は、「DNS TXT Record with Persistent Value」(永続的な値を持つ DNS TXT レコード)を Baseline Requirements §3.2.2.4.22 として追加しました。_validation-persist.name に置いた長期間有効な 1 つの TXT レコードで CA と ACME アカウントの URI を指定し、必要に応じて policy=wildcard を付けます。ACME のチャレンジとしては、まだ IETF のドラフトの段階です。Google Trust Services は 2026年9月28日からドラフト 01 に対応していますが、Let's Encrypt はステージング環境でのみ提供しており、2026年10月時点では、ドラフトに残る未解決の問題が解決されるまで本番環境での提供を見送っています。トレードオフとして、アカウント鍵が恒常的な認可になります。そのため、アカウント鍵は DNS の認証情報と同じように保護してください。
まもなく更新のたびに検証が必要になる
証明書の有効期間の短縮に合わせて、認可を再利用できる期間も短くなっています。Baseline Requirements は、ドメイン認証の結果を再利用できる期間の上限を、現在は 200 日、2027年3月からは 100 日、2029年3月からは 10 日と定めています。Let's Encrypt のデフォルトのプロファイルは、現在は認可を 30 日間再利用しますが、2027年2月10日からは 10 日間、2028年2月16日からは 7 時間になります。どちらのチャレンジを選ぶにしても、無人で実行できなければなりません。TXT の値を手作業でコピーする運用は、2028年には通用しなくなります。
それぞれの用途
- ACME クライアントが動作する 1 台の公開 Web サーバー:HTTP-01 を使います。
- ワイルドカード証明書:DNS-01 を使います(CA が提供していれば DNS-PERSIST-01 も使えます)。
- 公開の名前を持つ内部ホスト、VPN 経由でのみ利用できるサービス、開発環境:DNS-01 を使います。これらの名前は CT ログに記録されることに注意してください。
- 負荷分散されたサーバー群、マルチリージョンやエニーキャストの構成:DNS-01 を使うか、すべてのノードが
/.well-known/acme-challenge/を 1 台の応答サーバーにプロキシする構成で HTTP-01 を使います。 - CDN とマネージドプラットフォーム:検証はプラットフォームに任せます(通常はエッジ経由の HTTP-01 か、一度だけ追加する CNAME)。
- IP アドレスの証明書:HTTP-01 か TLS-ALPN-01 を使います。Let's Encrypt では、有効期間 6 日の
shortlivedプロファイルでのみ利用できます。 - ポリシーでポート 80 を閉じている場合:DNS-01 か TLS-ALPN-01 を使います。
移行時の注意点
- HTTP-01 から DNS-01 への移行:チャレンジ専用のゾーンを作成し、各
_acme-challengeの名前を CNAME でそのゾーンに向け、そのゾーンに範囲を限定したトークンを ACME クライアントに渡してから、クライアントのチャレンジの種類を切り替えます。既存の証明書はそのまま使えます。 - チャレンジに応答する前に反映を確認する:ほとんどのクライアントは、すべての権威サーバーがレコードを返すまで待機できます。固定時間の待機ではなく、この機能を有効にしてください。
- 後片付けをする:クライアントは検証後に TXT レコードを削除すべきです(RFC 8555 §8.4)。残ったレコードは蓄積し、ルックアップを遅くします。
issueとissuewildの両方にaccounturiとvalidationmethodsを指定して、CAA で発行を制限します。- CA のスケジュールに合わせて更新する:対応している場合は ACME Renewal Information(ARI、RFC 9773)を使い、そうでなければ有効期間の約 3 分の 2 が過ぎた時点で更新します。証明書の有効期間計算ツールで、日付ごとの上限を確認できます。
- CT ログで発行を監視し(開発者向けの Certificate Transparency)、どの方法で取得された証明書も把握できるようにします。
参考資料
- RFC 8555 §8:ACME の識別子検証チャレンジ
- RFC 8737:TLS-ALPN-01 と RFC 8738:ACME の IP 識別子
- RFC 8657:CAA の accounturi と validationmethods
- RFC 7638 §3.1:JWK サムプリントの例
- RFC 9773:ACME Renewal Information
- CA/Browser Forum の Baseline Requirements §3.2.2.4、§3.2.2.9、§4.2.2
- Ballot SC-085v2:CAA と DCV のルックアップにおける DNSSEC の検証
- Let's Encrypt:チャレンジの種類
- Let's Encrypt:DNS-PERSIST-01
- Let's Encrypt:証明書の有効期間を 45 日に短縮