Point a domain at CloudFront or a load balancer
Route a domain (including the zone apex) to a CloudFront distribution or load balancer with a Route 53 alias record — in the CLI or TypeScript — wait for the change to reach every name server, and work through why a domain still is not reachable (registrar name servers, suspension, deleted hosted zone, caching).
markdownguidev4N92F-GPPWstage: buildtrust: officialservice: route53interface: cliinterface: sdkRoute 53 alias recordzone apexA record aliasAAAA aliasChangeResourceRecordSetsCommandUPSERTGetChange INSYNCCanonicalHostedZoneIdEvaluateTargetHealthCNAME at apex not allowedname servers registrardomain not resolvingdomain suspended ClientHoldhosted zone deleted
**Use an alias record, type A (plus AAAA for IPv6), whose target is the resource's DNS name and its hosted zone ID.** Aliases work at the zone apex (example.com), where a CNAME is not allowed. They follow the resource's IP changes, and Route 53 doesn't charge for alias queries to AWS resources.
1. Collect the target
| Target | DNSName | Alias HostedZoneId |
|---|---|---|
| Application or Network Load Balancer | aws elbv2 describe-load-balancers --names <lb> --query 'LoadBalancers[0].DNSName' | …[0].CanonicalHostedZoneId |
| CloudFront distribution | d1234abcd.cloudfront.net (aws cloudfront get-distribution --id <id> --query Distribution.DomainName) | CloudFront's fixed zone ID (UNVERIFIED value not in the collected sources; the console fills it in) |
Before pointing a name at CloudFront, add that name as an alternate domain name on the distribution, with a certificate in us-east-1 that covers it. Otherwise requests return 403. A load balancer needs the name on its listener certificate, or browsers show NET::ERR_CERT_COMMON_NAME_INVALID.
2. Create or update the record
import { Route53Client, ChangeResourceRecordSetsCommand, GetChangeCommand } from "@aws-sdk/client-route-53";
const r53 = new Route53Client({});
const out = await r53.send(new ChangeResourceRecordSetsCommand({
HostedZoneId: "<your hosted zone id>",
ChangeBatch: {
Comment: "apex to load balancer",
Changes: [{
Action: "UPSERT",
ResourceRecordSet: {
Name: "example.com",
Type: "A",
AliasTarget: {
HostedZoneId: "<the load balancer's CanonicalHostedZoneId>",
DNSName: "<the load balancer's DNSName>",
EvaluateTargetHealth: true,
},
},
}],
},
}));
let status = out.ChangeInfo?.Status;
while (status === "PENDING") {
await new Promise((r) => setTimeout(r, 5_000));
status = (await r53.send(new GetChangeCommand({ Id: out.ChangeInfo!.Id! }))).ChangeInfo?.Status;
}
console.log(status); // INSYNC
The npm package is @aws-sdk/client-route-53, with a hyphen. CLI: aws route53 change-resource-record-sets --hosted-zone-id <zone> --change-batch file://change.json with the same JSON, then aws route53 get-change --id <change-id>.
- Two
HostedZoneIds: the request's is your zone; the one insideAliasTargetbelongs to the target resource. Swapping them is the most common mistake (UNVERIFIED frequency; the error returned isInvalidChangeBatch). UPSERTcreates or replaces;CREATEfails if the record exists;DELETEmust repeat every value exactly.- A batch is all-or-nothing. Replacing a
wwwCNAME with an alias in one batch (DELETE + CREATE) has no gap. - An alias's TTL can't be set: Route 53 uses the target's.
EvaluateTargetHealth: truemakes Route 53 skip an unhealthy target in failover or weighted setups.
Changes typically reach all Route 53 name servers within 60 seconds (INSYNC). Resolvers that cached the old answer keep it until its TTL runs out.
3. Check
dig +short example.com
dig +short <lb-dns-name> # should return the same addresses
dig example.com NS +short # should list this hosted zone's four name servers
An alias shows up in dig as a plain A/AAAA record. Its alias nature is visible only in the Route 53 console or in list-resource-record-sets.
When the domain still doesn't resolve
| Cause | Fix |
|---|---|
| The registrar's name servers aren't this hosted zone's (common when several zones share the name) | Compare the zone's four NS with the registrar's. Update the registrar. Resolvers cache NS for 24–48 h, so it can take up to two days |
| New registration not confirmed (registrant email link, valid 3–15 days by TLD) | The domain is suspended until you confirm. Resend the email |
| Registration transferred, but the old registrar's DNS service stopped | Recreate the records in a Route 53 hosted zone and set its name servers at the registrar |
| DNS moved, then registration transferred too soon | Wait out the NS caching; nothing speeds it up |
| The hosted zone was deleted | Create a new zone, recreate the records, set the new name servers at the registrar, then wait (up to 48 h) |
Suspended (ClientHold) | Resolve the registrar's reason, such as an unconfirmed contact |
If DNS is right and connections time out, the problem is the network: guides/vpc/diagnose-connection-timeout.md.