DEV Community

Madialagan Ajith kumara
Madialagan Ajith kumara

Posted on

The Portal Doesn't Open": Fixing a Missing DNS Record and an Internal ALB on AWS

TL;DR: A customer portal on AWS "didn't open" even though the app and the load balancer were healthy. Two problems were stacked: the domain's DNS lived in Cloudflare (not Route 53) with no record for the hostname, and the Application Load Balancer was internal, so it only had private IPs. Here's how I found both and fixed them, with every AWS CLI command.


🧩 The problem

A customer-facing portal on a new hostname (let's call it portal.example.com) did not load from the internet. In the AWS console, everything looked green:

  • ✅ Application running
  • ✅ Targets healthy
  • ✅ ALB active

But from outside the office: nothing.

# Problem Why it breaks
1 No DNS record in the authoritative DNS provider (Cloudflare) The hostname resolves to nothing
2 The ALB scheme was internal Internal ALBs get only private IPs, unreachable from the internet

Let's fix them one at a time.


🔍 Step 1: Find out who is authoritative for the domain

Don't assume Route 53. Ask DNS directly:

dig +short NS example.com
Enter fullscreen mode Exit fullscreen mode

If you see names like *.ns.cloudflare.com, then Cloudflare answers for this domain. Any Route 53 hosted zone for it is ignored by the internet.

Check whether a Route 53 zone exists anyway (a common source of confusion):

aws route53 list-hosted-zones-by-name --dns-name example.com \
  --query 'HostedZones[*].[Name,Id,Config.PrivateZone]' --output table
Enter fullscreen mode Exit fullscreen mode

Now check the hostname itself:

dig +short portal.example.com
Enter fullscreen mode Exit fullscreen mode

💡 No answer = no record. That's problem one.


🔍 Step 2: Check if the ALB is internal or internet-facing

aws elbv2 describe-load-balancers \
  --query 'LoadBalancers[*].[LoadBalancerName,Scheme,DNSName]' --output table
Enter fullscreen mode Exit fullscreen mode

If Scheme is internal, resolve its DNS name:

dig +short internal-portal-alb-1234567890.REGION.elb.amazonaws.com
Enter fullscreen mode Exit fullscreen mode

You'll get private IPs (10.x, 172.16-31.x or 192.168.x). Nobody outside the VPC or connected networks can reach them. That's problem two.

⚠️ The ALB scheme can't be changed after creation. You need a new, internet-facing ALB.


🌐 Step 3: Confirm you have public subnets

An internet-facing ALB needs at least two public subnets in different Availability Zones. A public subnet has a route to an Internet Gateway:

aws ec2 describe-route-tables \
  --filters Name=association.subnet-id,Values=SUBNET_ID \
  --query 'RouteTables[*].Routes[?starts_with(GatewayId, `igw-`)]' --output json
Enter fullscreen mode Exit fullscreen mode

An empty result means that subnet is private.


🔐 Step 4: Create a security group for the public ALB

SG_ID=$(aws ec2 create-security-group \
  --group-name portal-public-alb-sg \
  --description "Public ALB for portal" \
  --vpc-id VPC_ID --query GroupId --output text)

aws ec2 authorize-security-group-ingress --group-id $SG_ID \
  --protocol tcp --port 443 --cidr 0.0.0.0/0

aws ec2 authorize-security-group-ingress --group-id $SG_ID \
  --protocol tcp --port 80 --cidr 0.0.0.0/0
Enter fullscreen mode Exit fullscreen mode

💡 If Cloudflare proxies the traffic (orange cloud), you can restrict ingress to Cloudflare's published IP ranges instead of 0.0.0.0/0.

Also allow this security group in the targets' security group on the app port.


📜 Step 5: Request an ACM certificate and validate it in Cloudflare

CERT_ARN=$(aws acm request-certificate \
  --domain-name portal.example.com \
  --validation-method DNS --query CertificateArn --output text)

aws acm describe-certificate --certificate-arn $CERT_ARN \
  --query 'Certificate.DomainValidationOptions[0].ResourceRecord'
Enter fullscreen mode Exit fullscreen mode

Create that CNAME in Cloudflare with the proxy off (grey cloud), then wait until it's issued:

aws acm wait certificate-validated --certificate-arn $CERT_ARN
Enter fullscreen mode Exit fullscreen mode

⚙️ Step 6: Create the internet-facing ALB, target group and listeners

Create the ALB:

ALB_ARN=$(aws elbv2 create-load-balancer \
  --name portal-public-alb \
  --scheme internet-facing --type application \
  --subnets PUBLIC_SUBNET_A PUBLIC_SUBNET_B \
  --security-groups $SG_ID \
  --query 'LoadBalancers[0].LoadBalancerArn' --output text)
Enter fullscreen mode Exit fullscreen mode

Create the target group and register targets:

TG_ARN=$(aws elbv2 create-target-group \
  --name portal-tg --protocol HTTP --port 8080 \
  --vpc-id VPC_ID --target-type ip \
  --health-check-path /health \
  --query 'TargetGroups[0].TargetGroupArn' --output text)

aws elbv2 register-targets --target-group-arn $TG_ARN \
  --targets Id=TARGET_IP_1 Id=TARGET_IP_2
Enter fullscreen mode Exit fullscreen mode

HTTPS listener + HTTP to HTTPS redirect:

aws elbv2 create-listener --load-balancer-arn $ALB_ARN \
  --protocol HTTPS --port 443 --certificates CertificateArn=$CERT_ARN \
  --ssl-policy ELBSecurityPolicy-TLS13-1-2-2021-06 \
  --default-actions Type=forward,TargetGroupArn=$TG_ARN

aws elbv2 create-listener --load-balancer-arn $ALB_ARN \
  --protocol HTTP --port 80 \
  --default-actions 'Type=redirect,RedirectConfig={Protocol=HTTPS,Port=443,StatusCode=HTTP_301}'
Enter fullscreen mode Exit fullscreen mode

Use the port, target type and health check path your app actually uses. Then check target health:

aws elbv2 describe-target-health --target-group-arn $TG_ARN \
  --query 'TargetHealthDescriptions[*].[Target.Id,TargetHealth.State,TargetHealth.Reason]' \
  --output table
Enter fullscreen mode Exit fullscreen mode

🌍 Step 7: Add the DNS record in Cloudflare

Get the ALB's DNS name:

aws elbv2 describe-load-balancers --load-balancer-arns $ALB_ARN \
  --query 'LoadBalancers[0].DNSName' --output text
Enter fullscreen mode Exit fullscreen mode

In Cloudflare, create a CNAME record:

Type Name Target
CNAME portal portal-public-alb-xxxx.REGION.elb.amazonaws.com

⚠️ Never use an A record with the ALB's IPs. They change.

If you turn the Cloudflare proxy on, set SSL/TLS mode to Full (strict), since the ALB has a valid ACM certificate.


✅ Step 8: Test from outside

dig +short portal.example.com
curl -sv https://portal.example.com/health 2>&1 | grep -E "HTTP/|subject:|issuer:"
Enter fullscreen mode Exit fullscreen mode

🛡️ Optional: protect it with AWS WAF

A public endpoint should have a web ACL. Attach an existing one:

aws wafv2 associate-web-acl \
  --web-acl-arn WEB_ACL_ARN --resource-arn $ALB_ARN
Enter fullscreen mode Exit fullscreen mode

🎯 Lessons learned

  1. Check dig NS first. The authoritative DNS provider is the only one that matters.
  2. Check the ALB scheme second. internal means private IPs only, and it can't be changed later.
  3. Keep internal and public ALBs separate. Internal for back-office users over VPN, internet-facing for customers, each with its own security group.
  4. Always CNAME to the ALB DNS name, never to its IPs.

📋 Quick "site won't open" checklist

  • [ ] Who is authoritative? (dig NS)
  • [ ] Does the record exist there? (dig +short)
  • [ ] Is the ALB internet-facing?
  • [ ] Are the ALB subnets public?
  • [ ] Do the security groups allow 443 in, and ALB to targets?
  • [ ] Are targets healthy?
  • [ ] Is the certificate issued and attached?

Have you ever spent an hour debugging DNS in the wrong provider? Tell me in the comments. 👇

I write about AWS and DevOps from real production work. Follow me for more hands-on posts.

Top comments (0)