Hands-On: Point a Domain at Your Server
What We Are Building
I have a domain registered at Namecheap and an Express API running on an EC2 instance with an Elastic IP. By the end of this walkthrough, example.com resolves to that instance, www.example.com follows it, and both are verified from the terminal.
Replace example.com with your real domain and 3.110.25.140 with your Elastic IP throughout.
Step 1: Create the Hosted Zone
Console path: Route 53 → Hosted zones → Create hosted zone.
- Domain name:
example.com - Type: Public hosted zone
Or with the CLI:
aws route53 create-hosted-zone \
--name example.com \
--caller-reference "$(date +%s)"
The response includes the zone ID and the four name servers:
{
"HostedZone": {
"Id": "/hostedzone/Z0123456789ABCDEFGHIJ",
"Name": "example.com."
},
"DelegationSet": {
"NameServers": [
"ns-1234.awsdns-12.org",
"ns-567.awsdns-34.com",
"ns-890.awsdns-56.net",
"ns-2001.awsdns-78.co.uk"
]
}
}
Save the zone ID — every record command needs it. To retrieve it later:
aws route53 list-hosted-zones-by-name \
--dns-name example.com \
--query "HostedZones[0].Id" --output text
Step 2: Update Name Servers at the Registrar
At the registrar (Namecheap in my case): Domain List → Manage → Nameservers → Custom DNS, then paste the four awsdns servers from Step 1. Enter them without the trailing dot if the registrar rejects it.
Route 53 is not authoritative for the domain until this delegation is in place. Registrars usually apply the change within minutes, but resolvers can take longer to notice — that is fine, keep going.
Step 3: Create the A Record for the Root Domain
The A record maps the bare domain to the Elastic IP. Write the change batch to a file:
{
"Changes": [
{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": "example.com",
"Type": "A",
"TTL": 300,
"ResourceRecords": [{ "Value": "3.110.25.140" }]
}
}
]
}
Apply it:
aws route53 change-resource-record-sets \
--hosted-zone-id Z0123456789ABCDEFGHIJ \
--change-batch file://a-record.json
UPSERT creates the record or overwrites it if it already exists, which makes the command safe to re-run. Use an Elastic IP here, not the default public IP — the default IP changes every time the instance stops and starts, and the record would silently break.
Step 4: Create the CNAME for www
Point www.example.com at the root domain so both names hit the same server:
{
"Changes": [
{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": "www.example.com",
"Type": "CNAME",
"TTL": 300,
"ResourceRecords": [{ "Value": "example.com" }]
}
}
]
}
aws route53 change-resource-record-sets \
--hosted-zone-id Z0123456789ABCDEFGHIJ \
--change-batch file://cname-record.json
Each change command returns a change ID with status PENDING. Route 53 syncs records to its name servers within about 60 seconds; check with:
aws route53 get-change --id /change/C0987654321ZYXWVUTSRQ
Status INSYNC means all Route 53 name servers are serving the record.
Step 5: Verify with dig
First, ask a Route 53 name server directly — this bypasses caching and delegation entirely, so it works immediately:
dig @ns-1234.awsdns-12.org example.com A +short
Expected output:
3.110.25.140
Then test the public resolution path through Google's resolver:
dig @8.8.8.8 example.com A +short
dig @8.8.8.8 www.example.com +short
The www query should show the CNAME chain resolving down to the IP:
example.com.
3.110.25.140
Finally, check the delegation itself — the answer should list the four awsdns servers:
dig example.com NS +short
If it still shows the registrar's default name servers, the delegation from Step 2 has not propagated yet. Wait and re-run; most resolvers pick it up within 30 minutes, worst case 48 hours.
Step 6: Confirm End to End
curl -I http://example.com
curl -I http://www.example.com
Both should return a response from the Express app on the instance. If dig resolves correctly but curl times out, DNS is done — the problem is the EC2 security group (port 80 not open) or the app not listening, not Route 53.
Troubleshooting Notes
- Right answer from awsdns, wrong answer from 8.8.8.8 — old records are cached; wait out the previous TTL.
- NXDOMAIN from the awsdns server — the record name has a typo, or you queried a name server from a different hosted zone. Zones for the same domain get different name server sets, so a stale duplicate zone is a classic trap. List zones and delete the one the registrar does not point to.
- Planning an IP change? Drop the TTL to 60 a day in advance, switch the IP, then raise it back to 300 once stable.