AWS ACM and ALB Custom Domains
A practical guide to exposing an AWS Application Load Balancer through a custom HTTPS hostname using ACM, Terraform, GoDaddy DNS, and GitHub Actions.
This guide explains how to expose an AWS Application Load Balancer through a custom HTTPS hostname using:
- AWS Certificate Manager (ACM)
- An AWS Application Load Balancer (ALB)
- Terraform
- GoDaddy DNS
- GitHub Actions with AWS OIDC
The example hostname is:
signal-tester.cultofdev.com
The example AWS region is:
ap-southeast-1
Architecture
Client
→ https://signal-tester.cultofdev.com
→ GoDaddy application CNAME
→ AWS Application Load Balancer HTTPS listener
→ ECS service target over HTTP
Two DNS CNAME records are required:
- An ACM validation CNAME proving ownership of the hostname.
- An application CNAME routing the hostname to the ALB.
These records serve different purposes and both should remain in DNS.
Prerequisites
Before starting, confirm:
- The ALB and ACM certificate are in the same AWS region.
- You can modify DNS records for
cultofdev.comin GoDaddy. - The Terraform stack and remote backend are initialized.
- The GitHub Actions IAM role can manage ACM and the ALB.
- The ALB security group permits HTTPS from the intended CIDRs.
- The application is healthy through the ALB target group.
An ACM certificate used by an ALB must be created in the same region as that ALB. The certificate does not need to be in us-east-1; that region-specific requirement applies to services such as CloudFront.
Terraform-managed ACM flow
The Terraform configuration uses a staged process because GoDaddy DNS is managed manually.
Stage 1: Request the certificate
Configure these GitHub dev environment variables:
ACM_CERTIFICATE_DOMAIN_NAME=signal-tester.cultofdev.com
ACM_CERTIFICATE_VALIDATION_COMPLETE=false
Leave this variable unset or empty when Terraform should create the certificate:
ACM_CERTIFICATE_ARN
Run the Terraform GitHub Actions workflow manually:
Workflow: Terraform
Action: apply
Environment: dev
The first apply creates the ACM certificate but leaves the ALB on HTTP while validation is pending.
The workflow prints the acm_dns_validation_records Terraform output. A typical result looks like:
{
"signal-tester.cultofdev.com" = {
name = "_example-token.signal-tester.cultofdev.com."
type = "CNAME"
value = "_example-token.acm-validations.aws."
}
}
You can also read this output locally:
AWS_PROFILE=<profile> \
terraform -chdir=infra/terraform output acm_dns_validation_records
Stage 2: Add the ACM validation record in GoDaddy
In GoDaddy DNS management, add the record returned by Terraform:
Type: CNAME
Name: _example-token.signal-tester
Value: _example-token.acm-validations.aws
TTL: Default
Use the exact token returned by ACM. Do not copy the example token from this guide.
GoDaddy normally appends the zone name automatically. If its interface expects only the host portion, entering the complete record name may accidentally create:
_example-token.signal-tester.cultofdev.com.cultofdev.com
Verify the resulting fully qualified record before continuing.
Do not delete the validation CNAME after the certificate is issued. ACM uses it for managed renewal.
Stage 3: Confirm that ACM issued the certificate
Find the certificate ARN by domain:
aws acm list-certificates \
--region ap-southeast-1 \
--query 'CertificateSummaryList[?DomainName==`signal-tester.cultofdev.com`].CertificateArn | [0]' \
--output text
Check the overall certificate status:
aws acm describe-certificate \
--region ap-southeast-1 \
--certificate-arn "<certificate-arn>" \
--query 'Certificate.Status' \
--output text
Common results include:
PENDING_VALIDATION
ISSUED
FAILED
EXPIRED
The expected result before enabling HTTPS is:
ISSUED
Display the domain validation details and expected DNS record:
aws acm describe-certificate \
--region ap-southeast-1 \
--certificate-arn "<certificate-arn>" \
--query 'Certificate.DomainValidationOptions[*].{Domain:DomainName,Status:ValidationStatus,Record:ResourceRecord}' \
--output table
Inspect additional certificate details:
aws acm describe-certificate \
--region ap-southeast-1 \
--certificate-arn "<certificate-arn>" \
--query 'Certificate.{Domain:DomainName,Status:Status,IssuedAt:IssuedAt,NotBefore:NotBefore,NotAfter:NotAfter,InUseBy:InUseBy}' \
--output json
Stage 4: Enable certificate validation and HTTPS
After the validation CNAME exists, update the GitHub dev environment variable:
ACM_CERTIFICATE_VALIDATION_COMPLETE=true
Run the Terraform workflow again:
Workflow: Terraform
Action: apply
Environment: dev
Terraform will:
- Complete the ACM validation resource.
- Change ALB security-group ingress from port 80 to 443.
- Change the ALB listener from HTTP to HTTPS.
- Attach the issued ACM certificate to the listener.
The target group can continue forwarding HTTP to the ECS container. TLS terminates at the ALB.
Find the ALB URL
Using Terraform output
Read the configured ALB URL from the main Terraform state:
AWS_PROFILE=<profile> \
terraform -chdir=infra/terraform output -raw alb_url
With HTTPS enabled, the output resembles:
https://signal-tester-dev-1001343125.ap-southeast-1.elb.amazonaws.com
Using the AWS CLI
Get the raw ALB DNS hostname:
aws elbv2 describe-load-balancers \
--region ap-southeast-1 \
--names signal-tester-dev \
--query 'LoadBalancers[0].DNSName' \
--output text
Get the ALB ARN, DNS hostname, scheme, and state:
aws elbv2 describe-load-balancers \
--region ap-southeast-1 \
--names signal-tester-dev \
--query 'LoadBalancers[0].{Arn:LoadBalancerArn,DNSName:DNSName,Scheme:Scheme,State:State.Code}' \
--output table
List the ALB listeners:
ALB_ARN=$(aws elbv2 describe-load-balancers \
--region ap-southeast-1 \
--names signal-tester-dev \
--query 'LoadBalancers[0].LoadBalancerArn' \
--output text)
aws elbv2 describe-listeners \
--region ap-southeast-1 \
--load-balancer-arn "$ALB_ARN" \
--query 'Listeners[*].{Port:Port,Protocol:Protocol,Certificates:Certificates}' \
--output table
Add the application CNAME in GoDaddy
After retrieving the raw ALB hostname, add this GoDaddy record:
Type: CNAME
Name: signal-tester
Value: signal-tester-dev-1001343125.ap-southeast-1.elb.amazonaws.com
TTL: Default
Replace the example ALB hostname with the hostname returned by AWS.
Do not include any of the following in the CNAME value:
https://
/health
A trailing slash
The final public URL is:
https://signal-tester.cultofdev.com
Configure the GitHub dev environment variable:
ALB_URL=https://signal-tester.cultofdev.com
Verify DNS
Check the application CNAME:
dig +short CNAME signal-tester.cultofdev.com
Expected result:
signal-tester-dev-1001343125.ap-southeast-1.elb.amazonaws.com.
Check the ACM validation CNAME using the exact validation hostname:
dig +short CNAME _example-token.signal-tester.cultofdev.com
Inspect the complete DNS response:
dig signal-tester.cultofdev.com
Public DNS propagation may take time depending on the previous TTL and GoDaddy propagation.
Test HTTPS and the application
Test the health endpoint through the custom hostname:
curl --fail --show-error \
https://signal-tester.cultofdev.com/health
Test application metadata:
curl --fail --show-error \
https://signal-tester.cultofdev.com/api/info
Test scenario status without starting a destructive scenario:
curl --fail --show-error \
https://signal-tester.cultofdev.com/api/scenarios/status
Inspect the TLS certificate presented by the ALB:
openssl s_client \
-connect signal-tester.cultofdev.com:443 \
-servername signal-tester.cultofdev.com \
</dev/null 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates -ext subjectAltName
Why the raw ALB HTTPS URL produces a certificate error
This command normally fails TLS hostname validation:
curl https://signal-tester-dev-1001343125.ap-southeast-1.elb.amazonaws.com/health
The ACM certificate is valid for:
signal-tester.cultofdev.com
It is not valid for the AWS-generated ALB hostname. A typical error is:
SSL: no alternative certificate subject name matches target host name
This is correct and expected TLS behavior. Use the custom hostname for HTTPS testing. Avoid curl -k because it disables certificate verification.
Troubleshooting
Certificate remains PENDING_VALIDATION
Check:
- The validation CNAME name exactly matches the ACM output.
- GoDaddy did not append
cultofdev.comtwice. - The CNAME value points to
acm-validations.awsexactly as provided. - A conflicting DNS record does not exist at the same hostname.
- Public DNS resolvers can see the record with
dig.
Terraform cannot request the certificate
The GitHub Actions role requires ACM permissions. Verify the deployed inline policy includes acm:* or the narrower ACM actions required by the stack:
AWS_PROFILE=<bootstrap-admin-profile> \
aws iam get-role-policy \
--role-name github-actions-signal-tester-dev \
--policy-name signal-tester-workflows \
--query 'PolicyDocument.Statement[?Sid==`SignalTesterInfrastructure`].Action' \
--output json
If the bootstrap source changed, apply it separately before rerunning the main Terraform workflow:
AWS_PROFILE=<bootstrap-admin-profile> \
terraform -chdir=infra/bootstrap/github-oidc-role apply
Terraform cannot change the ALB rule from port 80 to 443
The workflow role requires:
ec2:ModifySecurityGroupRules
Apply the bootstrap configuration after adding that permission, then rerun the main Terraform apply.
HTTPS works on the custom hostname but not on the ALB hostname
This is expected. The certificate contains the custom hostname, not the AWS-generated hostname.
DNS works but the application is unavailable
Check the target group health:
TARGET_GROUP_ARN=$(aws elbv2 describe-target-groups \
--region ap-southeast-1 \
--names signal-tester-dev \
--query 'TargetGroups[0].TargetGroupArn' \
--output text)
aws elbv2 describe-target-health \
--region ap-southeast-1 \
--target-group-arn "$TARGET_GROUP_ARN" \
--output table
Also inspect the ECS service:
aws ecs describe-services \
--region ap-southeast-1 \
--cluster signal-tester-dev \
--services signal-tester-dev \
--query 'services[0].{Status:status,Running:runningCount,Desired:desiredCount,Deployments:deployments,Events:events[0:5]}' \
--output json
Operational notes
- Keep the ACM validation CNAME permanently so ACM can renew the certificate.
- Keep the application CNAME pointing to the ALB DNS hostname, not an ALB IP address.
- ALB IP addresses can change and should not be placed directly in GoDaddy DNS.
- Use a trusted
/32ALB ingress CIDR when public access is not required. - The certificate and ALB must remain in the same AWS region.
- Terraform apply remains manual.
- Neither Terraform nor deployment workflows invoke destructive application scenarios.
Quick command reference
# Terraform ALB URL
terraform -chdir=infra/terraform output -raw alb_url
# Raw ALB hostname
aws elbv2 describe-load-balancers \
--region ap-southeast-1 \
--names signal-tester-dev \
--query 'LoadBalancers[0].DNSName' \
--output text
# Find certificate ARN
aws acm list-certificates \
--region ap-southeast-1 \
--query 'CertificateSummaryList[?DomainName==`signal-tester.cultofdev.com`].CertificateArn | [0]' \
--output text
# Check certificate status
aws acm describe-certificate \
--region ap-southeast-1 \
--certificate-arn "<certificate-arn>" \
--query 'Certificate.Status' \
--output text
# Check custom-domain DNS
dig +short CNAME signal-tester.cultofdev.com
# Test HTTPS
curl --fail --show-error https://signal-tester.cultofdev.com/health