Automating ACM Certificates for Tile CDN Endpoints
Your terraform apply has been sitting on aws_acm_certificate_validation.tiles: Still creating... for twenty minutes, the certificate shows PENDING_VALIDATION in the ACM console, and the tile CDN cutover it was supposed to unblock is now blocking a release. This is the canonical failure of automating a certificate for a CloudFront-backed tile CDN: the certificate was requested but domain control was never proven, usually because the DNS validation records did not land in the zone that actually answers for the domain, or because the certificate was created in the wrong region for CloudFront. This walkthrough is the task-level companion to TLS and Certificate Automation for Tile Endpoints within the broader Network Security & Access Control framework; it covers issuing an ACM certificate in us-east-1, wiring Route53 DNS validation so issuance completes unattended, associating the certificate with the distribution, and handling the pending-validation and renewal edge cases that keep tile maps online.
Symptom identification and triage
A stuck or failed certificate for a tile CDN surfaces in a handful of distinct shapes, and each points at a different root cause. Classify the symptom before changing any resource:
aws_acm_certificate_validationhangs, then times out. The certificate isPENDING_VALIDATIONand ACM never sees the CNAME. Either noaws_route53_recordwas created, it was written to the wrong hosted zone, or a name collision was silently dropped becauseallow_overwritewas unset. Check the record actually resolves:dig +short CNAME _x.tiles.example.com.- Apply fails at the distribution with a region error.
The specified SSL certificate ... must be in the US East (N. Virginia) region.The certificate is valid but was issued outsideus-east-1; CloudFront will not attach it. - Handshake works on some hosts, fails on others.
curl -vI https://a.tiles.example.com/...returnsNET::ERR_CERT_COMMON_NAME_INVALIDfor one shard while the apex host succeeds. The failing host is a distribution alias missing from the certificate’s SANs. - Certificate lapses months later with no apply. A previously healthy certificate expires because the validation CNAMEs were removed in a zone refactor and ACM could no longer auto-renew.
Prerequisites and environment assumptions
This walkthrough assumes a CloudFront distribution serving raster or vector tiles from an origin (S3 or a tile server), fronted by one or more hostnames you control in Route53. To run the remediation you will need:
-
Terraform
>= 1.7with the AWS provider pinned. Unpinned providers cause phantom certificate diffs and shifting validation-record shapes, so pin it explicitly:terraform { required_version = ">= 1.7.0" required_providers { aws = { source = "hashicorp/aws" version = "~> 5.60" # acm validation + cloudfront viewer cert schema is version-sensitive } } } -
A
us-east-1provider alias. A CloudFront viewer certificate is only accepted fromus-east-1, independent of where the distribution’s origin or the rest of your stack lives. -
A Route53 public hosted zone that is authoritative for the tile domain — its name servers must be the ones the domain’s registrar delegates to. DNS validation writes CNAMEs here; if the zone is not authoritative, ACM never sees them.
-
A locking-capable state backend so a certificate swap and an alias change cannot interleave. The concurrency hazard is the same one described in State Backend Selection.
-
IAM permissions for the operator:
acm:RequestCertificate,acm:DescribeCertificate,route53:ChangeResourceRecordSetsandroute53:GetChangeon the zone, andcloudfront:UpdateDistributionto attach the certificate.
Step-by-step remediation
Issue and validate the certificate first, then attach it — never point a distribution at a certificate that has not finished validating.
-
Request the certificate in
us-east-1with every tile host as a SAN. Thecreate_before_destroylifecycle avoids a coverage gap when the SAN set later changes and the certificate re-issues. Listing every shard and service host here is what prevents the per-hostNET::ERR_CERT_COMMON_NAME_INVALIDfailure — the map fetches all of them and the browser validates each independently.provider "aws" { alias = "use1" region = "us-east-1" # CloudFront viewer certs are region-locked to us-east-1 } resource "aws_acm_certificate" "tiles" { provider = aws.use1 domain_name = "tiles.example.com" subject_alternative_names = [ "a.tiles.example.com", "b.tiles.example.com", "c.tiles.example.com", "d.tiles.example.com", "wms.example.com", ] validation_method = "DNS" # DNS validation renews unattended; email validation does not lifecycle { create_before_destroy = true } } -
Write one validation CNAME per distinct domain into the authoritative zone. The
for_eachoverdomain_validation_optionsproduces a stable record per host;allow_overwriteprevents a re-issuance from failing on a validation name that already exists. This is the step that most often fails silently, so it is the first thing to verify when an apply hangs.resource "aws_route53_record" "acm_validation" { for_each = { for dvo in aws_acm_certificate.tiles.domain_validation_options : dvo.domain_name => { name = dvo.resource_record_name type = dvo.resource_record_type record = dvo.resource_record_value } } zone_id = var.hosted_zone_id # MUST be the zone that answers for the domain name = each.value.name type = each.value.type records = [each.value.record] ttl = 60 allow_overwrite = true } -
Block on validation so nothing attaches a pending certificate. The
aws_acm_certificate_validationresource waits until ACM confirms every CNAME and moves the certificate toISSUED. Downstream resources reference its output, not the raw certificate ARN, which makes it structurally impossible to attach a still-pending certificate.resource "aws_acm_certificate_validation" "tiles" { provider = aws.use1 certificate_arn = aws_acm_certificate.tiles.arn validation_record_fqdns = [for r in aws_route53_record.acm_validation : r.fqdn] } -
Attach the validated certificate to the distribution. Reference
aws_acm_certificate_validation.tiles.certificate_arn, usesni-only(required for a multi-SAN certificate), and set a modern minimum protocol. Thealiaseslist must be a subset of the certificate SANs or the apply fails at attach time.resource "aws_cloudfront_distribution" "tiles" { enabled = true aliases = [ "tiles.example.com", "a.tiles.example.com", "b.tiles.example.com", "c.tiles.example.com", "d.tiles.example.com", "wms.example.com", ] # ... origin, default_cache_behavior { viewer_protocol_policy = "redirect-to-https" } ... viewer_certificate { acm_certificate_arn = aws_acm_certificate_validation.tiles.certificate_arn ssl_support_method = "sni-only" minimum_protocol_version = "TLSv1.2_2021" } } -
Apply and watch the validation resource resolve. If step 3 hangs, do not cancel and re-run blindly — confirm the CNAME resolves first with
dig +short CNAME <validation-name>. A resolving record means ACM will pick it up within minutes; a non-resolving record means the zone is wrong orallow_overwritedropped it.
Verification
Confirm the certificate is issued, correctly scoped, and live at the edge before closing the change.
-
Confirm ACM shows
ISSUED.aws acm describe-certificate --region us-east-1 --certificate-arn <arn> --query 'Certificate.Status'must return"ISSUED", andDomainValidationOptions[].ValidationStatusmust all beSUCCESS. -
Inspect the served chain with
openssl. Verify the served leaf matches the requested host and the chain is complete for a sharded host, not just the apex:openssl s_client -connect a.tiles.example.com:443 -servername a.tiles.example.com </dev/null 2>/dev/null \ | openssl x509 -noout -subject -ext subjectAltName -datesThe
subjectAltNamelist must includea.tiles.example.com, andnotAftershould be roughly 13 months out for a freshly issued ACM certificate. -
Fetch a real tile and check the redirect and protocol. Prove HTTP is redirected and the tile serves over a modern TLS version:
curl -vI http://tiles.example.com/10/301/384.png 2>&1 | grep -i '< location' # expect https:// 301 curl -vI https://tiles.example.com/10/301/384.png 2>&1 | grep -Ei 'TLSv1|SSL connection|< HTTP'Expect a
301to thehttps://URL, a negotiatedTLSv1.2/TLSv1.3connection, and a200withcontent-type: image/png(orapplication/x-protobuffor vector tiles). -
Spot-check every SAN host. Loop the
openssl/curlcheck across all shard and service hosts; a single missing SAN only shows up on the host it omits, so the apex passing is not sufficient.
Preventing recurrence
Encode the fix so the same certificate incident cannot recur:
- Policy-as-code gate on region and SAN coverage. Add a
checkov/tfsecor CrossGuard rule that fails any certificate feeding a CloudFrontviewer_certificateunless it resolves through theus-east-1provider, and a rule asserting the distributionaliasesare a subset of the certificate SANs. This blocks the two highest-frequency regressions — wrong region and a SAN gap — before merge. - Scheduled drift and expiry detection. Run
terraform plan -detailed-exitcodenightly to catch removed validation records, and add a CloudWatch alarm on the ACMDaysToExpirymetric so a stalled auto-renewal pages the owning team well before the certificate lapses. - Own the validation records for the certificate’s whole life. Keep the
aws_route53_record.acm_validationresources in the same stack as the certificate so a zone refactor cannot orphan them and disarm renewal. Treat their removal as a breaking change. - Keep the origin private. Auto-renewal and a strict edge TLS policy only protect traffic that actually traverses the edge; keep the tile origin reachable only through CloudFront using the routing in VPC Routing for Tile Servers so nobody can bypass the certificate over plaintext.
Frequently asked questions
Why must the certificate be in us-east-1 if my tiles and distribution are elsewhere?
CloudFront is a global service whose viewer certificates are read only from ACM in us-east-1, regardless of where your origin, buckets, or state live. Issue the certificate through a dedicated us-east-1 provider alias. An internal ALB fronting a tile server is the opposite case — its certificate must live in the ALB’s own region.
My aws_acm_certificate_validation apply hangs forever — what do I check first?
Confirm the validation CNAME actually resolves with dig +short CNAME <resource_record_name>. If it does not, the record landed in the wrong hosted zone or was dropped by a name collision. Verify zone_id is the zone the domain’s registrar delegates to and that allow_overwrite = true is set, then re-apply.
Does a wildcard certificate cover all my tile shards?
Only for one DNS label. *.tiles.example.com matches a.tiles.example.com through d.tiles.example.com, but not the bare apex tiles.example.com and not a deeper a.b.tiles.example.com. List the apex explicitly and add any multi-label host as its own SAN.
My certificate expired even though ACM is supposed to auto-renew — why?
DNS-validated ACM certificates renew unattended only while the validation CNAMEs remain resolvable in the zone. A zone cleanup or refactor that removed them leaves ACM unable to reconfirm domain control, and the certificate lapses about 60 days later. Keep the validation records in state and alarm on DaysToExpiry.
Related
- TLS and Certificate Automation for Tile Endpoints — the parent operational guide for certificate automation across tile endpoints
- Network Security & Access Control — the domain framework this task sits within
- CORS & CSP Configuration — the header contract, including the mixed-content directives that assume valid HTTPS
- VPC Routing for Tile Servers — keeping the tile origin private so the edge certificate cannot be bypassed