ACM 証明書の発行と DNS 検証フロー — Terraform で完全自動化
HTTPS でサイトを公開するには TLS 証明書が必要です。AWS 内で完結させるなら ACM (AWS Certificate Manager) が定石。パブリック証明書は 無料・自動更新 という最強コスパです。本記事では ACM 証明書の発行と DNS 検証の仕組み、それを Terraform でどう書くかを解説します。
ACM の特徴
| 項目 | 内容 |
|---|---|
| 料金 | パブリック証明書は完全無料 |
| 自動更新 | 有効期限の前に自動更新(手動操作不要) |
| 連携先 | CloudFront, ALB, API Gateway 等の AWS サービス |
| 取得方式 | DNS validation(推奨)/ Email validation |
| 有効期間 | 発行から約 13 か月(自動更新で実質永続) |
「Let's Encrypt + certbot で 90 日ごとの cron 監視」みたいな手間が一切要りません。AWS と心中する覚悟があれば最強の選択肢です。
CloudFront 用 ACM は us-east-1 必須
本シリーズで最初にハマるのが、この仕様:
これは CloudFront が「グローバルサービス」として us-east-1 にメタデータを持っている設計の名残です。最初は混乱しますが、覚えてしまえば理由は明確。
実装上は、Terraform の provider alias で us-east-1 用 provider を別途用意して使います(記事 #7 参照)。
DNS validation の仕組み
ACM は「あなたが本当にそのドメインの持ち主か」を確認するために検証フローを通します。本シリーズでは DNS validation を使います。手順はシンプル:
- ACM に「
lab.iigtn.comの証明書を発行して」とリクエスト - ACM が「この CNAME を DNS に追加してください」とランダムな検証レコードを返す(例:
_2611...lab.iigtn.com → _00b9...acm-validations.aws.) - あなたがそのドメインの DNS 管理画面で CNAME を追加
- ACM が定期的に DNS を引いて「あ、ちゃんと CNAME 立ってる」と確認
- 検証成功 → 証明書ステータスが
ISSUEDになる
所要時間は通常 5〜30 分。Route53 で DNS 管理しているなら、検証 CNAME も Terraform で自動投入できて、apply 1 回で完結します。
本シリーズの network_dns モジュール
これらを Terraform でこう書きました:
versions.tf — us_east_1 alias を要求
terraform {
required_version = ">= 1.5"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
configuration_aliases = [aws.us_east_1] # ← us-east-1 用 alias を要求
}
}
}
variables.tf — ドメイン名を入力に
variable "domain_name" {
description = "発行するドメイン (例: lab.iigtn.com)"
type = string
validation {
condition = length(var.domain_name) > 0 && !can(regex("\\.$", var.domain_name))
error_message = "domain_name は空でなく、末尾のドット (.) を含まないこと。"
}
}
main.tf — 証明書発行 + 検証待ち
# 証明書本体(us-east-1 で発行)
resource "aws_acm_certificate" "this" {
provider = aws.us_east_1
domain_name = var.domain_name
subject_alternative_names = ["*.${var.domain_name}"] # ワイルドカード追加
validation_method = "DNS"
lifecycle {
create_before_destroy = true # 差し替え時にダウン回避
}
}
# 検証完了を待つ
resource "aws_acm_certificate_validation" "this" {
provider = aws.us_east_1
certificate_arn = aws_acm_certificate.this.arn
validation_record_fqdns = [
for dvo in aws_acm_certificate.this.domain_validation_options :
dvo.resource_record_name
]
timeouts {
create = "75m" # 検証用 CNAME 投入に時間が必要なケースを想定
}
}
outputs.tf — Distribution に渡す ARN を出力
output "certificate_arn" {
description = "Validated ACM certificate ARN"
value = aws_acm_certificate_validation.this.certificate_arn
}
output "validation_records" {
description = "DNS に追加すべき CNAME レコード(手動投入時の参考用)"
value = {
for dvo in aws_acm_certificate.this.domain_validation_options :
dvo.domain_name => {
name = dvo.resource_record_name
type = dvo.resource_record_type
value = dvo.resource_record_value
}
}
}
aws_acm_certificate と aws_acm_certificate_validation の違い
初見で混乱するのが、Terraform に ACM 関連の resource が 2 つある こと:
| resource | 役割 |
|---|---|
aws_acm_certificate | 「証明書の発行リクエスト」を表す。これだけだとステータスは PENDING_VALIDATION のまま |
aws_acm_certificate_validation | 「検証完了まで apply をブロック」する仕組み。完了するまで他のリソース作成を待たせる |
後者は実際には何も作らない「待機専用」のリソース。これを certificate_arn で参照しないと、検証前の証明書が CloudFront にアタッチされて apply が失敗します。
ワイルドカード SAN を入れる理由
本シリーズでは lab.iigtn.com 単体ではなく、*.lab.iigtn.com も SAN に追加しています:
subject_alternative_names = ["*.${var.domain_name}"]
これで status.lab.iigtn.com や api.lab.iigtn.com など、後でサブドメインを増やしたい時に 証明書を再発行する必要がなくなる。1 回の DNS validation で 1 つのドメイン + すべてのサブドメインがカバーされます。
lab.iigtn.com」と「*.lab.iigtn.com」で検証用 CNAME が同一になるケースが多く、結局 1 個の CNAME を投入すれば両方検証完了します。
create_before_destroy の意味
証明書リソースに lifecycle { create_before_destroy = true } を付けています:
lifecycle {
create_before_destroy = true
}
これは Terraform の挙動を 「先に新しいのを作ってから古いのを消す」 に変える指定。デフォルトは「先に古いのを消してから新しいのを作る」で、その瞬間サイトがダウンする。create_before_destroy なら瞬断ゼロで証明書を入れ替えられます。
apply の流れ
cd terraform/envs/prod
terraform plan
terraform apply
Route53 で DNS 管理しているなら、apply 中に検証 CNAME が自動投入されて、5〜30 分待つと aws_acm_certificate_validation.this: Creation complete after Xm でステータスが ISSUED になります。
本シリーズでは Squarespace で DNS 管理していたので、自動投入はできず手動になりました(次の記事で詳説)。
発行確認
aws acm describe-certificate --region us-east-1 \
--certificate-arn <ARN> \
--query 'Certificate.{Status:Status,Domain:DomainName,NotAfter:NotAfter}' \
--output table
結果:
+----------+----------------------------+
| Status | ISSUED |
| Domain | lab.iigtn.com |
| NotAfter| 2027-05-09T08:59:59+09:00|
+----------+----------------------------+
有効期限は約 13 か月先。期限の前に ACM が自動更新します。
失敗時の対処
| 症状 | 原因 | 対処 |
|---|---|---|
| 75 分でタイムアウト | 検証 CNAME が DNS に投入されていない or 反映待ち | dig <CNAME> @8.8.8.8 で外部から見えるか確認 → 反映後に terraform apply 再実行 |
| CloudFront に証明書が選択肢として出ない | us-east-1 以外で発行している | provider alias を確認、us-east-1 で再発行 |
| 同じドメインで重複エラー | 古い証明書が残っている | ACM コンソールで古い証明書を削除してから apply |
次の記事
本シリーズで一番苦労した部分が、ここから先の 「Squarespace で DNS 管理されているドメインに、AWS の証明書を取得する」 部分です。Squarespace の DNS UI が NS レコードの追加を拒否する制約に当たり、当初の「Route53 hosted zone + NS 委譲」案を捨てて、CNAME のみで運用する設計に pivot した経緯を書きます。
📚 用語集
- ACM (AWS Certificate Manager)
- AWS が提供する TLS 証明書の発行・管理・自動更新サービス。パブリック証明書は無料。
- TLS / SSL 証明書
- HTTPS 通信に使う公開鍵証明書。サーバが「自分が本物の lab.iigtn.com である」ことを証明する電子証明書。
- パブリック証明書 (Public Certificate)
- 誰でも検証できる公開証明書。Web サイト用は通常これ。プライベート証明書(社内 PKI 用)と区別される。
- DNS validation
- ACM が「あなたがそのドメインの持ち主か」を、DNS レコード(CNAME)の存在で確認する検証方式。Email validation より自動化しやすい。
- SAN (Subject Alternative Names)
- 1 つの証明書がカバーする追加ドメイン名のリスト。
lab.iigtn.com+*.lab.iigtn.comなど。 - ワイルドカード証明書
*.example.comのように、任意のサブドメインをカバーする証明書。foo.example.comもbar.example.comも同じ証明書で配信できる。- aws_acm_certificate
- Terraform の AWS provider で「ACM 証明書発行リクエスト」を表すリソース。これだけでは PENDING のまま。
- aws_acm_certificate_validation
- 「ACM 検証完了まで apply をブロックする」専用の Terraform リソース。実際の AWS リソースは作らないが、依存関係の同期に使う。
- domain_validation_options
aws_acm_certificateの output 属性。「DNS に追加すべき検証 CNAME」のリスト。- provider alias
- Terraform で「同じ provider を別の設定で使う」仕組み。本シリーズでは us-east-1 用に
aws.us_east_1alias を定義。 - create_before_destroy
- Terraform の lifecycle 設定。「新しいリソースを作ってから古いのを消す」順序にする。瞬断ゼロでリソース入替が可能。
- ISSUED / PENDING_VALIDATION
- ACM 証明書のステータス。検証中は PENDING_VALIDATION、検証完了で ISSUED。
- NotAfter / 有効期限
- 証明書が無効になる日時。ACM は期限前に自動更新するので実質意識不要。
- dig
- DNS 問い合わせコマンド。
dig CNAME _xxx.lab.iigtn.com @8.8.8.8で「Google の DNS から見て CNAME が引けるか」を確認できる。 - Let's Encrypt
- 無料の TLS 証明書発行 CA(認証局)。AWS の外で運用する場合の選択肢。証明書の有効期間が 90 日と短く、cron で自動更新が必要。ACM は AWS 内で完結するので楽。
- CloudFront のグローバル性
- CloudFront は「単一リージョンに属さない」サービスとして設計されているが、メタデータと証明書は us-east-1 に集約される仕様。