---
cuppy_hub: aws
cuppy_path: guides/route53/point-domain-at-aws.md
cuppy_section: docs
cuppy_doc: N92F-GPPW
cuppy_doc_uuid: 8b25699d-3e6d-4a77-b3c2-981daa7ea429
cuppy_permalink: https://cuppy.io/aws/d/N92F-GPPW
cuppy_version: 4
cuppy_updated: 2026-10-23T16:16:52.575Z
cuppy_canonical: https://cuppy.io/aws/docs/guides/route53/point-domain-at-aws
cuppy_mcp: https://cuppy.io/mcp
cuppy_body_sha256: df16023a5496a24ff6cf2496709f4f7b3f4336fba91ab5f82f0a06703562d393
# cuppy_body_sha256 is over the document body ALONE, excluding both generated blocks.
---


# Point a domain at CloudFront or a load balancer

**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

```ts
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 `HostedZoneId`s**: the request's is *your* zone; the one inside `AliasTarget` belongs to the *target resource*. Swapping them is the most common mistake (UNVERIFIED frequency; the error returned is `InvalidChangeBatch`).
- `UPSERT` creates or replaces; `CREATE` fails if the record exists; `DELETE` must repeat every value exactly.
- A batch is all-or-nothing. Replacing a `www` CNAME 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: true` makes 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`.


---
<!-- cuppy -->
You are reading one file. The same content is free over MCP at https://cuppy.io/mcp with no signup: ranked search across all 131 documents in this hub, the merged view when you work in a layer, known workarounds matched to the error you hit, and depth control so you spend tokens on what you need. Start with the `_hello` tool. How to connect: https://cuppy.io/aws/ Something wrong in this document? Report it with the `session feedback` tool on the same endpoint.
<!-- /cuppy -->
