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 にアタッチする ACM 証明書は、必ず us-east-1(バージニア北部)リージョンで発行する必要があります。東京(ap-northeast-1)で発行した証明書は CloudFront からは使えません。

これは CloudFront が「グローバルサービス」として us-east-1 にメタデータを持っている設計の名残です。最初は混乱しますが、覚えてしまえば理由は明確。

実装上は、Terraform の provider alias で us-east-1 用 provider を別途用意して使います(記事 #7 参照)。

DNS validation の仕組み

ACM は「あなたが本当にそのドメインの持ち主か」を確認するために検証フローを通します。本シリーズでは DNS validation を使います。手順はシンプル:

  1. ACM に「lab.iigtn.com の証明書を発行して」とリクエスト
  2. ACM が「この CNAME を DNS に追加してください」とランダムな検証レコードを返す(例: _2611...lab.iigtn.com → _00b9...acm-validations.aws.
  3. あなたがそのドメインの DNS 管理画面で CNAME を追加
  4. ACM が定期的に DNS を引いて「あ、ちゃんと CNAME 立ってる」と確認
  5. 検証成功 → 証明書ステータスが 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.comapi.lab.iigtn.com など、後でサブドメインを増やしたい時に 証明書を再発行する必要がなくなる。1 回の DNS validation で 1 つのドメイン + すべてのサブドメインがカバーされます。

ACM は「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.combar.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_1 alias を定義。
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 に集約される仕様。