TerraformでACMの証明書発行とDNS検証を自動化する設計|aws_acm_certificate_validationの依存関係と発行待ちの実践

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOMELinux技術 リナックスマスター.JP(Linuxマスター.JP)Terraform > TerraformでACMの証明書発行とDNS検証を自動化する設計|aws_acm_certificate_validationの依存関係と発行待ちの実践
「TerraformでACM証明書を発行したら、aws_acm_certificate_validationがいつまでも終わらない」
「DNS検証レコードとaws_acm_certificate_validationの依存関係の書き方がわからない」

AWSでHTTPS化を自動化しようとすると、必ずACM(AWS Certificate Manager)の証明書発行フローに直面します。AWSコンソールから手動で発行する場合は、画面の指示通りに進めれば数分で完了しますが、Terraformでコード化するとなると話が違います。

aws_acm_certificate(証明書リソース)、aws_route53_record(DNS検証レコード)、aws_acm_certificate_validation(発行完了待ち)の3つのリソースを正しい依存関係で組み合わせなければなりません。この依存関係を間違えると、terraform applyがいつまでも進まないか、ALBに証明書がアタッチされずHTTPS通信が切れます。

この記事では、for_eachを使ったDNS検証レコードの自動生成パターン、CloudFront向けのus-east-1プロバイダーalias設計、ワイルドカード証明書の書き方、タイムアウト時のトラブルシュートまで、実践的なHCLコードと実行例を交えて解説します。
動作確認環境: Terraform v1.9 / AWS Provider v5.x

この記事のポイント

・aws_acm_certificate_validationは証明書リソースとRoute 53 CNAMEレコード両方に依存する
・for_eachでdomain_validation_optionsを展開すると検証レコードを自動生成できる
・CloudFront用証明書はprovider = aws.us_east_1のalias設計が必須
・DNS検証がタイムアウトするときはRoute 53ゾーンのNameserver一致を最初に疑う


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

ACM証明書発行をTerraformで自動化する前に

ACMの証明書発行には「ドメイン検証」と「メール検証」の2種類があります。Terraformで完全自動化できるのはDNS検証だけです。DNS検証では、AWSが指定するCNAMEレコードをRoute 53に登録することで「このドメインのDNSを管理している=ドメインオーナーである」と証明します。

Terraformでの自動化フローは次の3段階です。

ステップ1:aws_acm_certificateで証明書リソースを作成(この時点ではまだ「検証待ち」状態)
ステップ2:domain_validation_optionsからCNAMEレコードをRoute 53に登録
ステップ3:aws_acm_certificate_validationでAWSがCNAMEを確認するまで待機

この3つをHCLで正しく表現できれば、terraform apply 1回で証明書発行からALB・CloudFrontへのアタッチまで完全自動化できます。

基本設定:3つのリソースの組み合わせ方

1. aws_acm_certificateの最小構成

まず証明書リソースを定義します。validation_method = "DNS" が必須です。

# 証明書リソース resource "aws_acm_certificate" "this" { domain_name = var.domain_name # 例: example.com validation_method = "DNS" lifecycle { create_before_destroy = true # 証明書差し替え時のダウンタイム防止 } }

create_before_destroy = trueは必須と考えてください。証明書を更新(ドメイン追加等)する際、Terraformはデフォルトで「古い証明書を削除してから新しい証明書を作る」動作をします。ALBやCloudFrontが古い証明書を参照しているうちに削除されると、HTTPS通信が一時的に切れます。このlifecycleを設定しておけば「新しい証明書を作ってからリソースをアタッチし直し、その後に古い証明書を削除する」順序に変わります。

2. DNS検証レコードをRoute 53に自動生成する

aws_acm_certificateを作成すると、domain_validation_optionsというアトリビュートに、AWSが要求するCNAMEレコードの情報(CNAME名・値)が入ります。これをfor_eachでループしてRoute 53に登録します。

# Route 53ゾーン参照(既存ゾーンをdata sourceで取得) data "aws_route53_zone" "this" { name = var.hosted_zone_name # 例: "example.com." private_zone = false } # DNS検証レコード(CNAMEをRoute 53に登録) resource "aws_route53_record" "cert_validation" { for_each = { for dvo in aws_acm_certificate.this.domain_validation_options : dvo.domain_name => { name = dvo.resource_record_name record = dvo.resource_record_value type = dvo.resource_record_type } } allow_overwrite = true name = each.value.name records = [each.value.record] ttl = 60 type = each.value.type zone_id = data.aws_route53_zone.this.zone_id }

for_eachのキーをdvo.domain_nameにしているのは、ワイルドカード証明書(*.example.com)を発行するときに複数のdomain_validation_optionsが生成されるためです。キーが重複しないよう、ドメイン名でマップを作っています。

allow_overwrite = trueも付けておくことを推奨します。同じドメインで複数の証明書を管理していると、検証レコードが既に存在する場合があります。このオプションがないと「レコードがすでに存在する」エラーが出ます。

3. aws_acm_certificate_validationで発行完了を待つ

CNAMEレコードをRoute 53に登録したら、AWSが実際にDNSを確認して証明書を発行するのを待ちます。

# 証明書発行完了の待機 resource "aws_acm_certificate_validation" "this" { certificate_arn = aws_acm_certificate.this.arn validation_record_fqdns = [for record in aws_route53_record.cert_validation : record.fqdn] }

validation_record_fqdnsはCNAMEレコードのFQDNのリストです。for_eachで複数のレコードを作った場合は、for式で全FQDNを渡します。このリソースは実際のAPIリソースを作るわけではなく、「Route 53に登録したCNAMEをAWSが確認するまでTerraformがapplyを待機する」役割だけを持ちます。

ALBやCloudFrontのHTTPS設定でACM証明書ARNを参照するときは、aws_acm_certificate.this.arnではなくaws_acm_certificate_validation.this.certificate_arnを使います。こうすることでTerraformが「証明書の発行が完了してからALBに証明書をアタッチする」依存関係を自動的に解決します。

# ALBのHTTPSリスナー(証明書発行完了後にアタッチ) resource "aws_lb_listener" "https" { load_balancer_arn = aws_lb.this.arn port = 443 protocol = "HTTPS" ssl_policy = "ELBSecurityPolicy-TLS13-1-2-2021-06" # aws_acm_certificate.this.arn ではなく validation 完了後の ARN を参照する certificate_arn = aws_acm_certificate_validation.this.certificate_arn default_action { type = "forward" target_group_arn = aws_lb_target_group.this.arn } }

terraform planを実行すると、このリソースは証明書発行フロー全体の依存チェーン(aws_acm_certificate > aws_route53_record > aws_acm_certificate_validation)のあとにアタッチされます。

TerraformでACMとALBを組み合わせたHTTPS化の設計をより体系的に学びたい方は Terraform実践コース(terraform.linuxmaster.jp) も参考にしてください。

ワイルドカード証明書と複数ドメインの設計

1. ワイルドカード証明書の書き方

*.example.comのワイルドカード証明書はsubject_alternative_namesで追加します。

resource "aws_acm_certificate" "this" { domain_name = var.domain_name # 例: example.com subject_alternative_names = ["*.${var.domain_name}"] # ワイルドカード追加 validation_method = "DNS" lifecycle { create_before_destroy = true } }

ワイルドカードを追加するとdomain_validation_optionsが2行になります(example.comと*.example.comの2つ)。ただし、AWSはワイルドカード証明書の検証に使うCNAMEをルートドメインと同じ値にするため、実際には1つのCNAMEレコードで両方の検証が完了します。前述のfor_eachパターンはこのケースも正しく処理します。

2. SAN(サブジェクト代替名)に複数のドメインを追加する

複数ドメインをまとめて1枚の証明書でカバーする場合は、subject_alternative_namesにリストで渡します。

resource "aws_acm_certificate" "this" { domain_name = "example.com" subject_alternative_names = [ "*.example.com", "api.example.net", "www.example.net", ] validation_method = "DNS" lifecycle { create_before_destroy = true } }

example.comとexample.netのような異なるドメインを1枚の証明書に含める場合、それぞれのRoute 53ゾーンに検証レコードを登録する必要があります。zone_idをdomain_nameで条件分岐させるHCLパターンを示します。

# 複数ゾーンを扱う場合のdata source data "aws_route53_zone" "zones" { for_each = toset(["example.com", "example.net"]) name = each.value private_zone = false } resource "aws_route53_record" "cert_validation" { for_each = { for dvo in aws_acm_certificate.this.domain_validation_options : dvo.domain_name => { name = dvo.resource_record_name record = dvo.resource_record_value type = dvo.resource_record_type # ドメインの末尾2ラベルでゾーンを選択する zone_id = data.aws_route53_zone.zones[ join(".", slice( split(".", dvo.domain_name), length(split(".", dvo.domain_name)) - 2, length(split(".", dvo.domain_name)) )) ].zone_id } } allow_overwrite = true name = each.value.name records = [each.value.record] ttl = 60 type = each.value.type zone_id = each.value.zone_id }

3. create_before_destroyで証明書を安全に差し替える

subject_alternative_namesを変更した場合、TerraformはACM証明書を作り直す(destroy + create)必要があります。lifecycle のcreate_before_destroy = trueがあれば「新しい証明書作成 > ALB等のアタッチ更新 > 古い証明書削除」の順になり、HTTPS通信の瞬断を防げます。

このフローが正しく動くには、ALBリスナー等でaws_acm_certificate_validation.this.certificate_arnを参照していることが前提です。aws_acm_certificate.this.arnを直接参照していると、依存関係チェーンが切れてcreate_before_destroyが期待通りに機能しないことがあります。

CloudFront向け:us-east-1プロバイダーのalias設計

CloudFrontにACM証明書を使う場合、証明書はus-east-1(バージニア北部)リージョンに存在しなければなりません。これはAWSの制約で、ap-northeast-1(東京)で発行した証明書はCloudFrontには使えません。

Terraformでこれを解決するには、プロバイダーのalias機能を使います。

# プロバイダー定義(通常のリージョンとus-east-1の2つを定義) provider "aws" { region = "ap-northeast-1" # 東京(デフォルト) } provider "aws" { alias = "us_east_1" region = "us-east-1" # CloudFront証明書用 } # CloudFront向け証明書(us-east-1で発行) resource "aws_acm_certificate" "cloudfront" { provider = aws.us_east_1 # このリソースだけus-east-1を使う domain_name = var.domain_name subject_alternative_names = ["*.${var.domain_name}"] validation_method = "DNS" lifecycle { create_before_destroy = true } } # DNS検証レコード(Route 53はリージョン非依存なのでデフォルトプロバイダーでOK) resource "aws_route53_record" "cloudfront_cert_validation" { for_each = { for dvo in aws_acm_certificate.cloudfront.domain_validation_options : dvo.domain_name => { name = dvo.resource_record_name record = dvo.resource_record_value type = dvo.resource_record_type } } allow_overwrite = true name = each.value.name records = [each.value.record] ttl = 60 type = each.value.type zone_id = data.aws_route53_zone.this.zone_id } # CloudFront向け証明書発行待ち(プロバイダーはus-east-1を指定) resource "aws_acm_certificate_validation" "cloudfront" { provider = aws.us_east_1 certificate_arn = aws_acm_certificate.cloudfront.arn validation_record_fqdns = [for record in aws_route53_record.cloudfront_cert_validation : record.fqdn] } # CloudFrontディストリビューション(証明書ARNはvalidation完了後を参照) resource "aws_cloudfront_distribution" "this" { # ... 他の設定 ... viewer_certificate { acm_certificate_arn = aws_acm_certificate_validation.cloudfront.certificate_arn ssl_support_method = "sni-only" minimum_protocol_version = "TLSv1.2_2021" } }

ALB(東京リージョン)とCloudFront(us-east-1)の両方でHTTPSを使う構成では、証明書を2枚発行する設計が一般的です。「東京用のaws_acm_certificate」と「CloudFront用のaws_acm_certificate」をそれぞれ定義し、同じDNS検証レコードを共有させます(allow_overwrite = trueが活きてくるポイントです)。

トラブルシュート:DNS検証が完了しない

1. aws_acm_certificate_validationがタイムアウトする

terraform applyを実行して数十分が経過してもaws_acm_certificate_validationが完了しない場合、まずAWSコンソールのACM画面で証明書の状態を確認します。

「検証保留中」のまま:Route 53にCNAMEが登録されているか確認する
CNAMEは登録済みだが検証が進まない:ドメインのNameserverとRoute 53ゾーンのNameserverが一致しているか確認する
「成功」になっている:Terraformのタイムアウト設定(デフォルト45分)が短い場合は延長する

タイムアウト時間を延長する場合はtimeoutsブロックで指定します。

resource "aws_acm_certificate_validation" "this" { certificate_arn = aws_acm_certificate.this.arn validation_record_fqdns = [for record in aws_route53_record.cert_validation : record.fqdn] timeouts { create = "75m" # デフォルト45分を75分に延長 } }

2. Nameserverのズレが最多原因

DNS検証が進まない最も多い原因は、ドメインのレジストラに設定されているNameserverとRoute 53ゾーンのNameserverが一致していないことです。Route 53でホストゾーンを新規作成したとき、AWSが4つのNameserverを自動割り当てしますが、これをドメインのレジストラ側に反映しなければなりません。

以下のコマンドでドメインの現在のNameserverを確認できます。

# ドメインの現在のNameserverを確認 $ dig example.com NS +short ns-123.awsdns-45.com. ns-678.awsdns-90.net. ns-1234.awsdns-56.org. ns-789.awsdns-01.co.uk. # Route 53ゾーンのNameserverと照合 $ aws route53 list-resource-record-sets --hosted-zone-id Z1234567890EXAMPLE --query "ResourceRecordSets[?Type=='NS'].ResourceRecords[].Value" --output text ns-123.awsdns-45.com. ns-678.awsdns-90.net. ns-1234.awsdns-56.org. ns-789.awsdns-01.co.uk.

上記の2つが一致していない場合、ドメインレジストラの管理画面でNameserverをRoute 53の値に更新してください。NSレコードの変更は世界中のDNSキャッシュに伝播するまで最大48時間かかります(詳しいDNSの仕組みはLinux DNS 設定の基本を参照してください)。

3. allow_overwrite未設定でレコード競合エラーが出る

既存の証明書(手動で発行した証明書など)と同じドメインで再発行すると、Route 53に同じCNAMEレコードが既に存在してエラーになることがあります。

エラーメッセージ例:

Error: [ERR]: Error building changeset: InvalidChangeBatch: [RRSet of type CNAME with DNS name _abc123.example.com. is not permitted as it conflicts with other records with that DNS name]

対処はaws_route53_recordにallow_overwrite = trueを追加するだけです。CNAMEの値はACM側で同一ドメインに対して同じ値を使うため、上書きしても問題ありません。

4. 段階的applyで依存関係エラーを回避する

aws_acm_certificate、aws_route53_record、aws_acm_certificate_validationを同時に新規作成するとき、Terraform 1.5未満ではfor式の中でまだ存在しないリソースを参照してエラーになることがあります。-targetを使って段階的にapplyする方法で回避できます。

# ステップ1:証明書リソースだけ作成 $ terraform apply -target=aws_acm_certificate.this # ステップ2:DNS検証レコードを作成 $ terraform apply -target=aws_route53_record.cert_validation # ステップ3:残り全部(aws_acm_certificate_validation含む)を適用 $ terraform apply

Terraform v1.5以降では同時applyでも問題なく動作します。古いバージョンを使っている場合はアップグレードを検討してください。

本記事のまとめ

TerraformでACM証明書のDNS検証を安全に自動化するには、3つのリソースの依存関係設計が核心です。
やりたいこと 設定ポイント
DNS検証を完全自動化する for_each + domain_validation_optionsでRoute 53 CNAMEを生成
ALBにHTTPS証明書を安全にアタッチする certificate_arnにはaws_acm_certificate_validation.*.certificate_arnを使う
CloudFrontで証明書を使う provider alias "us_east_1"を使ってus-east-1で証明書を発行
証明書差し替え時のダウンタイムを防ぐ lifecycle { create_before_destroy = true } を必ず付ける
検証レコード競合エラーを防ぐ aws_route53_recordにallow_overwrite = trueを付ける
DNS検証がタイムアウトする まずNameserverの一致を確認し、timeouts { create = "75m" }で延長
aws_acm_certificate_validationのcertificate_arnをALBやCloudFrontで参照するパターンを確立しておけば、証明書のライフサイクル(発行・差し替え・削除)をTerraformが一括管理してくれます。

TerraformでACMを含むAWSインフラを体系的に自動化するノウハウを学びたい方はこちらも参考にしてください。

TerraformでACMやALBをコード化するには、Linuxサーバー設計の基礎が必要

TerraformでACM・ALB・Route 53を組み合わせてHTTPS化を自動化するには、VPC・サブネット・セキュリティグループといったAWSインフラの設計を理解していることが前提になります。ツール操作の前に、現場で実際に使われるサーバー設計の「型」を身につけることが近道です。
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、『Linuxサーバー構築入門マニュアル(図解60P)』を完全無料でプレゼントしています。

「独学の時間がもったいない」「TerraformとLinuxを含む現場のサーバー構築スキルを最短で習得したい」という本気の方には、2日で実務レベルのスキルが身につく【初心者向けハンズオンセミナー】も開催しています。

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録
宮崎 智広

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。