Originally published on graycloudarch.com.
You know what's the worst part of launching a new site?
SSL certificate validation.
Not creating the cert—that's one click in AWS ACM. It's the validation dance:
- AWS gives you a CNAME record:
_abc123extremely-long-string-here.graycloudarch.com - The value is equally ridiculous:
_xyz789another-massive-string.acm-validations.aws. - You copy it (pray you don't miss a character)
- Switch to Cloudflare (or Route 53, or wherever)
- Paste it in
- Wait 5-10 minutes
- Refresh AWS console
- Still pending...
- Refresh again
- Finally validated!
Now do it again for www.graycloudarch.com.
And then repeat the whole thing for your second domain.
This is "DNS hell."
There's a Better Way
Terraform can read AWS validation records and create them in Cloudflare automatically.
Zero copy-paste. Zero browser tab switching. Zero waiting and refreshing.
Here's the whole thing:
# Request certificate
resource "aws_acm_certificate" "site" {
domain_name = "graycloudarch.com"
validation_method = "DNS"
subject_alternative_names = ["www.graycloudarch.com"]
}
# Create validation records in Cloudflare
resource "cloudflare_record" "cert_validation" {
for_each = {
for dvo in aws_acm_certificate.site.domain_validation_options :
dvo.domain_name => {
name = dvo.resource_record_name
value = dvo.resource_record_value
type = dvo.resource_record_type
}
}
zone_id = data.cloudflare_zone.site.id
name = each.value.name
value = each.value.value
type = each.value.type
proxied = false # Critical - ACM validation breaks with proxy
}
# Wait for validation
resource "aws_acm_certificate_validation" "site" {
certificate_arn = aws_acm_certificate.site.arn
validation_record_fqdns = [
for record in cloudflare_record.cert_validation : record.hostname
]
}
Run terraform apply. Go make coffee. Come back to a validated certificate.
The Magic: for_each
The key is this part:
for_each = {
for dvo in aws_acm_certificate.site.domain_validation_options :
dvo.domain_name => { name = dvo.resource_record_name, ... }
}
AWS generates validation records dynamically (one for apex domain, one for www). Terraform reads them, loops over them, and creates each one in Cloudflare.
You never see the records. You never copy anything. It just works.
What I Screwed Up
First time I ran this, ACM validation timed out after 30 minutes.
The problem:
proxied = true # Wrong!
Cloudflare's proxy rewrites DNS responses. ACM's validation servers hit Cloudflare's IP instead of seeing your validation record.
The fix:
proxied = false # Correct
DNS-only mode. No proxy. ACM validation works.
Cost me 30 minutes of debugging. Now it's in code so I never hit it again.
Why This Matters
I'm running two brands: graycloudarch.com and cloudpatterns.io.
Manual approach: 15 steps per domain = 30 steps total. 30 minutes minimum. High chance of typos.
Terraform approach: One terraform apply. 5 minutes to write the code (once), 10 minutes for AWS to validate. Then copy-paste the pattern for the second domain.
When I launch my third brand (and I will), it'll take 5 minutes and one terraform apply.
That's the bet: upfront automation for long-term velocity.
The Part People Miss
Most Terraform tutorials stop at requesting the certificate. They don't show you the validation loop or the waiting resource.
Without aws_acm_certificate_validation, Terraform exits immediately after creating the cert. It's still "Pending Validation" in AWS. When you try to use it in CloudFront, it fails.
You'd have to run terraform apply again later, after manually checking that validation completed.
That's not automation—that's just documentation.
The waiting resource makes it truly hands-off.
Scaling It
Adding a second domain is 10 lines of code:
resource "aws_acm_certificate" "cloudpatterns" {
domain_name = "cloudpatterns.io"
validation_method = "DNS"
subject_alternative_names = ["www.cloudpatterns.io"]
}
resource "cloudflare_record" "cloudpatterns_validation" {
for_each = { /* same pattern */ }
# ...
}
resource "aws_acm_certificate_validation" "cloudpatterns" {
# ...
}
Same pattern, different names. No clicking. No switching between consoles. No remembering which validation record goes where.
The Real Win
It's not the time savings (though 30 minutes per deployment adds up).
It's the mental overhead.
Manual DNS configuration requires focus. "Did I copy the whole string? Did I add the trailing dot? Is it DNS-only mode?"
Terraform requires running one command. That's it.
I get my focus back. I can write this blog post while Terraform validates certificates.
Want the full code? It's not open source (yet), but if you're building something similar and want to talk through it, reach out.
Or if you just want to tell me I'm overthinking this and should've clicked through Cloudflare like a normal person, that's cool too.
Top comments (0)