AWS ACMのACME機能でパブリック証明書を取得してみた(HTTP-01編)
AWS ACMがACMEプロトコルに対応
先日、AWS Certificate Manager (ACM) に ACME (Automated Certificate Management Environment) プロトコル対応 が追加されました。これにより、ACMをCAとしてパブリック証明書を、Let’s Encryptなどと同じ要領で自動発行・自動更新できるようになりました。
参考:AWSの公式ブログ
そもそもなぜこのような話をしているのかというと、2025年4月にCA/Bフォーラムによって、SSL/TLSサーバ証明書の有効期間が段階的に短縮されることが投票によって可決されました。証明書の更新方法については様々ありますが、最終的な有効期間である47日ごとに手動で証明書を再設定・更新するのは現実的ではありません。様々な自動化の手段はありますが、今回は主にAWS外(オンプレを想定)のWebサーバに対して、ACMのACMEエンドポイントから証明書を取得する場合を想定して、新機能を試してみました。今回はHTTP-01方式を扱っており、DNS-01方式は別の記事とします。
HTTP-01とDNS-01
ACMEによって証明書を取得しようとしているドメイン名の所有権の検証方式にはHTTP-01とDNS-01があり、それぞれ特性が異なります。
| 観点 | HTTP-01 | DNS-01 |
|---|---|---|
| 必要な権限 | Webサーバ書き込み | DNSレコードの書き込み |
| AWS認証情報 | 不要 | 必要 |
| ワイルドカード証明書の発行 | 不可 | 可 |
一般的にHTTP-01は以下のようなケースで有効だと考えられます
- DNSの権限をACMEクライアントに渡したくない場合
- API未対応であったり反映が遅いDNSプロバイダを利用している場合
- リバースプロキシを用いた構成でシンプルに導入したい
など。
ただしあくまで一般的であり、ACMEサーバの実装次第では、ケアが不要になる特徴もあります。
今回のテストした、AWS ACMのACME機能はまさに、PRE_APPROVED authorization という認証モデルを採用しており、証明書取得時にACMEクライアントからライブチャレンジ(HTTP-01/DNS-01)は要求されないようです。
参考:Issuing certificates through ACME - AWS Certificate Manager
検証構成
この記事では「AWS外のサーバから、ACMのACMEエンドポイントを叩いて証明書を取得する」というシナリオの検証です。そのため、オンプレサーバの代わりにpublic subnetに配置したEC2インスタンスを利用し、インターネット越しにACMEプロトコルでやりとりする構成としました。
EC2上のOSはAlmaLinux 9、Webサーバはnginx、ACMEクライアントはcertbotを使い、いずれもディストリビューションの標準リポジトリ(EPEL含む)から導入します。ドメイン名はRoute 53で管理しているドメイン( test.comとします)のサブドメインを利用します。
構成図を以下に示します。
せっかくなので、ホスト名と接続プロトコルを表示する画面を生成AIに作ってもらいました。
手順
1. ACMでACMEエンドポイントを作成
AWSコンソール → Certificate Manager → ACME → エンドポイント。
- エンドポイント名:
acm-acme-demo - 証明書キーのタイプ: サポートされているすべてのキータイプ
- ドメイン:
acm-acme-dev.test.com - ホストゾーン:
test.com(Route 53から選択)
作成後、Route 53への自動的なドメイン検証が走り、エンドポイントが有効化されます。証明書キーのタイプについては、一旦すべてのキータイプにしましたが、ここはセキュリティ要件に合わせて設定するべき項目かと思われます。
作成完了時に ACME エンドポイント URI が表示されます。
2. EAB認証情報を作成
続いてEAB認証情報を作成します。EABはACMEプロトコル(RFC 8555)で定義されている「ACMEアカウントを外部の顧客管理システムに紐付ける」仕組みです。AWS ACM ACMEは「AWSアカウントの契約者だけが利用できる」ようにする必要があるため、EABが必須になっています。
EABは以下2要素から構成されます:
- Key ID (KID): ACMEアカウントを識別する公開ID
- HMAC Key: 共有秘密鍵(パスワード相当)
これらはACMEアカウントの 初回登録時の1回だけ使われ、以降のリクエストではcertbot自身の鍵ペアで署名されるため、EABは登場しません。
エンドポイント詳細画面の「外部アカウントバインディング」タブ →「EAB認証情報を作成」。
- 有効期限: デフォルト90日(デモ用途なので小さくしても良いかも)
作成完了画面で、表示をクリックすると、以下の2つが表示されます。
- キー ID (KID)
- HMAC キー
3. EC2側の準備
certbotのインストール
certbotはEPELリポジトリから導入します。
sudo dnf install -y epel-release
sudo dnf install -y certbot python3-certbot-nginx
先ほど書いたように、AWS ACMのACME機能を用いる場合は、以下の設定は不要になりますが、一般的なHTTP-01方式の場合は必要になります。
[AWS ACMでは不要]nginx設定(HTTP用)
以下のように、設定ファイルを追加します。これによって、ACMEサーバからのチャレンジファイルをWebサーバのドキュメントルート配下に配置することとなり、ドメインの所有権を証明します。
/etc/nginx/conf.d/acm-acme-dev.conf:
server {
listen 80;
server_name acm-acme-dev.test.com;
root /usr/share/nginx/html;
index index.html;
location /.well-known/acme-challenge/ {
root /var/www/html;
}
}
[AWS ACMでは不要]webrootディレクトリの準備
sudo mkdir -p /var/www/html/.well-known/acme-challenge
sudo chown -R nginx:nginx /var/www/html
AlmaLinux等でSELinuxが有効な場合は、nginxが/var/www/html以下のファイルを読み込めるようにしてやる必要があるかと思います。
4. certbotで証明書取得
それではいよいよ、certbotで証明書の取得をしてみます。今回はデモの簡単化のため、環境変数に認証情報を直接セットします。本番環境では適切な方法で認証情報を渡してください。
ここでのACME_URLが先ほど取得したACMEディレクトリのURLです。
export ACME_URL="https://acm-acme-enroll.ap-northeast-1.api.aws/<UUID>/directory"
export EAB_KID="<キーID>"
export EAB_HMAC="<HMACキー>"
export EMAIL="(メールアドレス)"
以下のコマンドで証明書を取得します。
sudo -E certbot certonly \
--server "$ACME_URL" \
--eab-kid "$EAB_KID" \
--eab-hmac-key "$EAB_HMAC" \
--email "$EMAIL" \
--agree-tos \
--no-eff-email \
--webroot -w /var/www/html \
-d acm-acme-dev.(test.com)
このコマンドの実行時に起こっていることは以下です。
- certbotが自身の鍵ペアを生成
- EAB認証情報を使ってACMサーバーにACMEアカウントを登録
- acm-acme-dev.test.com の証明書を要求
- ACMサーバーがランダムな文字列(チャレンジ)を返す
- certbotが /var/www/html/.well-known/acme-challenge/<ランダム名> にチャレンジファイルを作成
- ACMサーバーが http://acm-acme-dev.test.com/.well-known/acme-challenge/<ランダム名> にアクセス
- 内容が一致すればチャレンジ成功 → 証明書発行
- certbotが /etc/letsencrypt/live/acm-acme-dev.test.com/ に証明書を保存
成功すると /etc/letsencrypt/live/acm-acme-dev.test.com/ に証明書一式が保存されます。
$ sudo -E certbot certonly \
--server "$ACME_URL" \
--eab-kid "$EAB_KID" \
--eab-hmac-key "$EAB_HMAC" \
--email "$EMAIL" \
--agree-tos \
--no-eff-email \
--webroot -w /var/www/html \
-d acm-acme-dev.(test.com)
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Account registered.
Requesting a certificate for acm-acme-dev.(test.com)
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/acm-acme-dev.(test.com)/fullchain.pem
Key is saved at: /etc/letsencrypt/live/acm-acme-dev.(test.com)/privkey.pem
This certificate expires on 2026-09-21.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
If you like Certbot, please consider supporting our work by:
* Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
* Donating to EFF: https://eff.org/donate-le
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Certbot has set up a scheduled task to automatically renew this certificate in the background.
とあるように、systemdのtimerも自動でセットしてくれます。
さて、実際に取得した証明書を確認してみます。
$ sudo openssl x509 -in /etc/letsencrypt/live/acm-acme-dev.(test.com)/fullchain.pem \
-noout -issuer -subject -dates
issuer=C=US, O=Amazon, CN=Amazon ECDSA 256 S08
subject=
notBefore=Aug 7 09:40:36 2026 GMT
notAfter=Sep 21 10:40:36 2026 GMT
$ sudo openssl crl2pkcs7 -nocrl -certfile /etc/letsencrypt/live/acm-acme-dev.(test.com)/fullchain.pem | \
openssl pkcs7 -print_certs -noout | grep -E "subject=|issuer="
subject=
issuer=C=US, O=Amazon, CN=Amazon ECDSA 256 S08
subject=C=US, O=Amazon, CN=Amazon ECDSA 256 S08
issuer=C=US, O=Amazon, CN=Amazon Root CA 4
subject=C=US, O=Amazon, CN=Amazon Root CA 4
issuer=C=US, ST=Arizona, L=Scottsdale, O=Starfield Technologies, Inc., CN=Starfield Services Root Certificate Authority - G2
Amazon発行の証明書が取れているのが確認できます。
せっかくなのでいくつか確認してみましょう。
subjectが空
subject= の後が空になっています。証明書の Subject Distinguished Name(CN)を持っていません。一方でSAN (Subject Alternative Name) を見てみましょう。
$ sudo openssl x509 -in /etc/letsencrypt/live/acm-acme-dev.(test.com)/fullchain.pem \
-noout -text | grep -A 1 "Subject Alternative Name"
X509v3 Subject Alternative Name: critical
DNS:acm-acme-dev.(test.com)
RFC2818でCNは非推奨となり、SANが優先されることになっているので、そういう意味でACMは正しい実装です。
ECDSA証明書が発行された
Amazon ECDSA 256 S08 から発行されているECDSA P-256 の証明書です。エンドポイント作成時に「サポートされているすべてのキータイプを許可」を選んだので、certbotのデフォルト(ECDSA)が優先されたようです。
Amazon Trust Services のルートを使っている
Amazon Root CA 4 は Amazon Trust Services のルート証明書で、主要ブラウザにプリインストールされています。Starfield Services Root Certificate Authority - G2はクロス署名で、古い環境でも動作できるようにしているのでしょう。
さて、それではnginxの設定に、certbotで取得した証明書を設定してnginxを再起動します。
/etc/nginx/conf.d/acm-acme-dev.confに以下を追記。
server {
listen 443 ssl http2;
server_name acm-acme-dev.(test.com);
ssl_certificate /etc/letsencrypt/live/acm-acme-dev.(test.com)/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/acm-acme-dev.(test.com)/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
root /usr/share/nginx/html;
index index.html;
}
以下のようにWebブラウザからのアクセスでも証明書が確認できました。
更新の確認
証明書の更新間隔が短くなったので、きちんと自動更新してくれるかが気になりますよね。systemd タイマーcertbot-renew.timerを見てみます。
$ sudo systemctl status certbot-renew.timer
● certbot-renew.timer - This is the timer to set the schedule for automated renewals
Loaded: loaded (/usr/lib/systemd/system/certbot-renew.timer; enabled; preset: enabled)
Active: active (waiting) since Fri 2026-08-07 10:58:19 UTC; 3s ago
Until: Fri 2026-08-07 10:58:19 UTC; 3s ago
Trigger: Fri 2026-08-07 14:13:08 UTC; 3h 14min left
Triggers: ● certbot-renew.service
Aug 07 10:58:19 ip-192-168-0-10.ap-northeast-1.compute.internal systemd[1]: Started This is the timer to set the schedule for automated renewals.
timer定義は /usr/lib/systemd/system/certbot-renew.timerにあり、以下の設定になっていました。
$ sudo cat /usr/lib/systemd/system/certbot-renew.timer
[Unit]
Description=This is the timer to set the schedule for automated renewals
[Timer]
OnCalendar=*-*-* 00/12:00:00
RandomizedDelaySec=12hours
Persistent=true
[Install]
WantedBy=timers.target
1日2回(0時と12時)を起点に、最大12時間のランダム遅延で実行されます。ACMサーバーへの一斉アクセスを避けるための工夫がされています。
証明書ファイルが更新されてもnginxはメモリ上の古い証明書を保持し続けるため、更新後の自動reloadフックを設定します。
/etc/letsencrypt/renewal-hooks/deploy/ 配下のスクリプトは、証明書更新完了時に自動実行されますので、ここにnginxを自動reloadするスクリプトを配置してみるのが良いでしょう。
AWS ACM ACMEの独自設計
さて、もう1つAWS ACM ACMEは独自の認証を行っていると書きましたが、その挙動をcertbotのログから確認してみます。
new-order レスポンス
certbotからのnew-orderリクエスト(証明書を要求)の直後、ACMサーバーから返ってきたレスポンスが以下です。
{
"authorizations": ["...authz/39625934-97fd-4b24-9258-e33bcb1f4ed2"],
"expires": "2026-10-07T10:42:34Z",
"finalize": "...",
"identifiers": [{"type": "dns", "value": "acm-acme-dev.test.com"}],
"status": "ready"
}
注目すべきは最後の “status”: “ready” です。
RFC 8555 Section 7.1.6 の Order Objects の状態遷移によれば、通常のACMEでは、初回のnew-orderに対して返るorderのstatusは “pending” です。クライアントがチャレンジを完了して authorization が valid になると、order が “ready” に遷移します。
一方で、今回はいきなり"ready"が返ってきています。これはすでにauthorizationはvalidであることを意味していると思われます。
ちなみに、“identifiers”: [{“type”: “dns”,のところで「おや?DNS-01方式でやったつもりではないんですが…」と思いましたが、これは検証方式のことではなく、どのドメインで証明書を出すかを示すもののようです。
The only type of identifier defined by this specification is a fully qualified domain name (type: "dns").
(参考: RFC 8555: Automatic Certificate Management Environment (ACME) | rfc-editor.org)
authorization レスポンス
certbotがauthorizationリソースを取得しようとした際のレスポンスは以下です。
{
"challenges": [],
"expires": "2026-10-07T10:42:34Z",
"identifier": {"type": "dns", "value": "acm-acme-dev.test.com"},
"status": "valid"
}
ここで、注目するべきは以下の2点でしょう。
"challenges": [](challengesが空配列)"status": "valid"(statusが最初からvalid)
RFCにも記載がある通り、ACMEプロトコルでは、authorizationリソースのchallenges フィールドに HTTP-01 / DNS-01 などのチャレンジ情報が配列で入っており、クライアントはそのうちのいずれかを使って検証します。
nginx アクセスログに何も記録されない
実際のこの試行中にnginxのアクセスログを確認していましたが、リクエストが記録されていませんでした。
もしHTTP-01チャレンジが実行されていたのであれば、ACMサーバーからGETリクエスト~/.well-known/acme-challenge/(token)にGETリクエストが来るはずですが、そのアクセスがないことからもリクエストがされていないようです。
つまりEC2のセキュリティグループで80番ポートを遮断しても問題ないでしょう。
まとめ
AWS ACMのACME機能を使い、AlmaLinux上のnginxに対してパブリック証明書を発行・自動更新する仕組みを構築しました。仕組みの本質はLet’s Encryptとほぼ同じで、certbotなど既存のACMEクライアントがそのまま使えます。
一般的なHTTP-01方式の特徴は、DNSへの権限を配らずにWebサーバー内で完結できることでしょうか。既存のWebサーバー運用の延長で導入しやすい方式と言えます。
一方で、一方でcertbotのdebugログを確認すると、AWS ACM ACMEは PRE_APPROVED authorization という独自の実装を採用しており、HTTP-01のチャレンジ処理は実際には発生していないように見えます。certbot側でDNS-01方式を指定した場合の挙動も確認しさらに深堀りすると面白そうです。







