「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一致を最初に疑う
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
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 }
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で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 } }
# 複数ゾーンを扱う場合の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" } }
トラブルシュート: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.
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]
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で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" }で延長 |
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日で実務レベルのスキルが身につく【初心者向けハンズオンセミナー】も開催しています。
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら
- 前のページへ:TerraformとCloudFormationを比較する設計判断|HCLとYAML・状態管理・マルチクラウド対応とロールバックの選択基準
- この記事の属するカテゴリ:Terraformへ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。
登録無料・いつでも解除できます