The issue

Here’s a little tip and snippet to help if you’re trying to use Cloudfront with Terraform. Cloudfront has some peculiarities that can make it difficult to get it working, specifically if you’re using it with an SSL cert.

First off, and this fact is well-documented:

if you’re using ACM (AWS Certificate Manager) to issue an SSL cert for your Cloudfront distribution, you have to issue the cert in the us-east-1 region.

My guess is that the Cloudfront control plane exists in us-east-1, so the ACM cert has to be in the region as well for it to use it.

Secondly - and this fact is not well-documented:

If you’re creating an Alias record in Route53 to point to your distribution (which you most likely are if you’re using an SSL cert with it), the record must be set to a very specific zone id Z2FDTNDATAQYW2.

I’m guessing this is the zone id for that route53 zone in us-east-1 that Cloudfront uses internally. If you create the record on the console it basically fills that fact in for you.

cloudfront-a-record

The fact that the place this is documented is in the Cloudformation doc page for AliasTarget in Route53 is rather strange. My suspicion is that because Cloudfront is one of the older services, they didn’t properly abstract away some of these properties, and haven’t updated it since.

Terraform Code

When you’re actually trying to set this up via Terraform, you can use something like these snippets.

SSL Cert

For the ACM cert needing to be in us-east-1, it’s possible (likely?) you’ll be building other resources in a different region. Because the aws_route53_record resource doesn’t inherently support a region parameter, you need to create another provider to use with that region. First, create a new provider block somewhere in your code that makes sense to you (remember, Terraform doesn’t care as long as it’s in the same directory)

provider "aws" {
  alias   = "us_east"
  region  = "us-east-1"
  profile = var.profile
}

resource "aws_acm_certificate" "cloudfront" {
  provider                  = aws.us_east
  ...
}

In this example, I’m giving it an alias of “us_east”. This is just the name I’ll use to refer to it later on. For the region parameter - the important one here - I’m telling it to use “us-east-1”. For the profile, I’m passing in a variable that I’m already using with other resources so that I’m hitting the correct account on AWS. Then, on the aws_acm_certificate resource, I’m passing in the us_east provider to the provider parameter. This way, Terraform knows to use the same profile (from var.profile) that I’ve been using elsewhere, but in the us-east-1 region.

A record

For the A record, I’m using something like this

resource "aws_route53_record" "a_records" {
  ...
  type    = "A"
  alias {
    name                   = module.cloudfront.domain_name
    zone_id                = "Z2FDTNDATAQYW2"
    evaluate_target_health = false
  }
}

Use whatever other parameters you need for the record, but the important ones for this solution are to use the zone_id of Z2FDTNDATAQYW2. This is the super-special zone id that all A records pointing to a Cloudfront distribution need to use. The name parameter is just the actual URL name you want to point to Cloudfront, and evaluate_target_health must be set to false when pointing to a Cloudfront distribution (see the same AliasTarget doc page as above). And, also, make sure you set the type of the record to A for an Alias record.

These are an example of some…peculiarities of the AWS platform. Hopefully that helps if you’re trying to setup Cloudfront.