Skip to content
Back to docs
DevOps 7 min read

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:

  1. An ACM validation CNAME proving ownership of the hostname.
  2. 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.com in 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:

  1. Complete the ACM validation resource.
  2. Change ALB security-group ingress from port 80 to 443.
  3. Change the ALB listener from HTTP to HTTPS.
  4. 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.com twice.
  • The CNAME value points to acm-validations.aws exactly 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 /32 ALB 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

References