Lesson 13 · 20 min read
Route 53: Production Domain
Hosted zones, alias records to your load balancer and CDN, and health-checked routing for a real domain.
What you'll learn in this lesson
- Concept: from a long AWS name to yourapp.com
- What Lesson 3 gave you, and what is new here
- Alias records — the AWS-only record type
- Point the domain at the ALB and CloudFront
- Routing policies: simple, weighted, latency, failover
- Health checks and DNS failover
- Private hosted zones — names that only exist inside the VPC
- Email records: MX, SPF, DKIM, DMARC
- Moving a live domain without downtime
- Protecting the domain itself
- What this costs
- Troubleshooting table
- Interview corner
- Practice task before Lesson 14
- Conclusion
Concept
Your load balancer (an ALB, Application Load Balancer, is an AWS service that shares incoming web requests between several servers) answers at a name like myapp-alb-1234567890.ap-south-1.elb.amazonaws.com. Your CDN (CloudFront is the AWS content delivery network, a service that keeps copies of your files on servers around the world) answers at d1234abcd.cloudfront.net. No customer will ever type either name. This lesson connects your real domain to them with Route 53, the AWS DNS service. DNS (Domain Name System) is the system that turns a name such as yourapp.com into the IP address of a server. We also add features for production: routing that checks health, private internal names, email records, and a way to move a live domain without anyone noticing.
Lesson 3 described DNS as the phone book of the internet. Route 53 is a phone book with an operator. The operator can give a different number depending on who is calling, which numbers work right now, and where the caller is. The operator can also point to AWS things by name. When the number behind the name changes, the book updates itself.
What Lesson 3 gave you, and what is new here
If any of these words feels unclear, read Lesson 3: DNS Deep Dive: From Domain Name to Server again first. This lesson builds on it:
- Hosted zone: the container for all the DNS records of one domain. You already created one and pointed your registrar's nameservers at it. (The registrar is the company where you bought the domain.)
- Records are the entries in a zone. An
Arecord points a name to an IP address. ACNAMErecord points a name to another name. AnMXrecord says where to deliver mail. ATXTrecord holds free text, often used as a proof. AnNSrecord says which servers hold the zone. TTL (time to live) is how many seconds an answer may be saved (cached). - Wildcard record:
*.yourapp.comanswers for any subdomain, so each customer (tenant) can have one.
New in this lesson:
| New idea | Why you need it |
|---|---|
| Alias records | Point the bare domain at an ALB or CloudFront (a CNAME is not allowed there) |
| Routing policies | Send 10% of users to a new setup, or switch to a backup |
| Health checks | Stop giving out the address of something that is down |
| Private hosted zones | Give the database an internal name that does not change |
| Email records | Make mail from your domain reach the inbox, not spam |
| Cutover procedure | Move a live domain with no downtime |
Alias records — the AWS-only record type
A load balancer's address is a name, so the obvious record is a CNAME: yourapp.com → myapp-alb-….elb.amazonaws.com. But DNS has a strict rule: a CNAME cannot exist next to any other record with the same name. The bare domain (also called the “apex”, yourapp.com) always has other records. It must have an SOA record (basic facts about the zone) and NS records, and it usually has MX too. So the bare domain cannot be a CNAME.
Route 53 solves this with the Alias record. To the outside world it looks like an A record (or an AAAA record, which is the same thing for IPv6 addresses, the newer, longer kind). But Route 53 works it out inside, and it returns the current IP addresses of the AWS resource that it names.
| CNAME | Alias (A / AAAA) | |
|---|---|---|
| Allowed at the apex | No | Yes |
| Can point at | Any domain name | Certain AWS resources (ALB, CloudFront, S3 website, API Gateway…) or another record in the same zone |
| Query cost | Charged | Free for AWS targets |
| Follows IP changes | Yes (extra lookup) | Yes, automatically, in a single step |
| TTL | You choose | Set by AWS (60 s for most targets) |
| Can check target health | No | Yes (EvaluateTargetHealth) |
Do you point to an AWS resource? Use Alias. Do you point to something outside AWS (a SaaS product, your old host)? A subdomain can use a CNAME. The bare domain needs an A record with an IP address.
Point the domain at the ALB and CloudFront
This is what we connect: yourapp.com and *.yourapp.com go to the ALB (the app). assets.yourapp.com goes to CloudFront (Lesson 11).
Create three alias records
Go to Route 53 → your hosted zone → Create record. For each record, turn on Alias and pick the target from the dropdown list. The console fills in the target's address and its hosted-zone ID for you.
Record name Type Alias to Evaluate target health yourapp.comA Application Load Balancer → myapp-albYes *.yourapp.comA Application Load Balancer → myapp-albYes assets.yourapp.comA CloudFront distribution (Lesson 11) No - The wildcard record does not cover the bare domain, so you create both records.
- All CloudFront distributions use the same fixed hosted-zone ID. The ID of an ALB is different in each region. So it is better to pick from the dropdown list than to type it.
- A change goes from PENDING to INSYNC on all Route 53 servers, usually within a minute.
Check it from the outside
Verifydig +short acme.yourapp.comdig is a command-line tool that asks DNS servers questions. Any subdomain should answer through the wildcard with the ALB's IPs. There are several, one per AZ. You never type them, because they can change
curl -sI https://yourapp.comYou should see HTTP/2 200 and a valid certificate. The answer comes from your Auto Scaling group through the ALB
Redirect www to the bare domain (or the reverse)
Create
wwwas an alias to the ALB as well. Then add one rule on the ALB's HTTPS listener. The rule says: if the host header iswww.yourapp.com, redirect (301) tohttps://yourapp.comand keep the path and query. Search engines treat the two names as separate sites, so pick one as the official name.
Routing policies: simple, weighted, latency, failover
A normal record always gives the same answer. Route 53 can choose the answer depending on conditions. You choose a routing policy for each record.
| Policy | What it does | Typical use |
|---|---|---|
| Simple | One record gives one answer (or several values, returned in random order) | The default. One site, one destination |
| Weighted | Splits traffic by percentage, such as 90 / 10 | Canary releases (a small test group goes first), moves from an old setup, A/B tests |
| Latency | Answers with the AWS region that is fastest for the user | The same app runs in several regions |
| Failover | The primary record answers while it is healthy. The secondary takes over if the health check fails | A maintenance page, or a standby region |
| Geolocation | Answers by the user's country or continent | Content for legal or regional reasons, sites in different languages |
| Geoproximity / IP-based | Chooses by distance (you can adjust it), or by the caller's IP range | Advanced traffic control |
Weighted routing — the safest way to migrate
Say you have an old setup (the app on Vercel) and a new one (the ALB). Instead of moving everyone at once, create two records with the same name. Give each one its own record ID (the API calls it SetIdentifier) and a weight. Traffic is split in that ratio:
| Record | Record ID | Weight | Points to |
|---|---|---|---|
yourapp.com A | new-aws-stack | 10 | alias → the ALB |
yourapp.com A | old-stack | 90 | the old host's IP, TTL 60 |
Start at 10. Watch the error rate and the delay for an hour. Then go to 50, then 100, and then remove the old record. If anything looks wrong, set the weight to 0. A rollback takes as long as your TTL. This is the DNS version of a canary release (a small group of users tries the new version first).
Weights apply to each DNS answer, not to each request, and resolvers save answers. A big office that uses one resolver all goes to the same side until the TTL ends. The percentages even out across many users, but they are not exact. For exact splits inside one setup, use ALB weighted target groups instead.
Health checks and DNS failover
A Route 53 health check is a robot that runs outside your VPC, in several AWS locations. It requests a URL every 10 or 30 seconds. If enough of these robots see failures, the check turns unhealthy. Any record that is tied to it is then removed from the answers. Your endpoint must be reachable from the internet for this to work.
Create one in Route 53 → Health checks. Use HTTPS, yourapp.com, the path /api/health, an interval of 30 seconds, and unhealthy after 3 failures. Then attach it to a record.
Failover routing uses the health check. You make two records. One is marked PRIMARY (with the health check) and one is marked SECONDARY. While the primary is healthy, everyone gets it. When it fails, DNS switches to the secondary. A common cheap secondary is a simple “we'll be right back” page in S3 (the AWS file storage service) behind CloudFront. The site shows a simple page instead of failing completely, and it costs very little.
It is not instant. Detection takes about 90 seconds (3 failures × 30 s). Then the saved answers must also expire (the TTL). So expect two to five minutes. Fast failover inside one region is the ALB's job (it takes seconds). Use Route 53 failover when a whole setup or a whole region fails, not for one sick server. And test it: switch off the primary and time the change. A failover that you have not tested is only a hope, not a plan.
Private hosted zones — names that only exist inside the VPC
A private hosted zone is a zone that is attached to a VPC. Its records answer only to resources inside that VPC, and the internet cannot see them. It solves a problem from Lesson 8. The RDS hostname (myapp-db.abc123xyz.ap-south-1.rds.amazonaws.com) is pasted into .env and SSM. When you restore a snapshot or the database fails over, the hostname changes, and you must edit every config.
The fix is to give the database a name that you control.
Create a private hosted zone internal.yourapp.com and attach it to the myapp VPC. Add one CNAME in it: db.internal.yourapp.com points to the RDS endpoint, with a TTL of 60. The app's connection string now uses @db.internal.yourapp.com:5432.
Do you restore a snapshot? Update one record instead of the config of every server. The VPC needs the settings enableDnsHostnames and enableDnsSupport (we set the first one in Lesson 6). There is one catch. The RDS certificate contains the AWS hostname, not your custom name. If your database driver checks that the name matches, the check fails. With sslmode=require the traffic is still encrypted, but the name is not checked. Or you can give the driver the AWS name as the TLS server name.
Email records: MX, SPF, DKIM, DMARC
When your domain sends email (welcome messages, password resets), DNS decides whether the mail reaches the inbox or the spam folder. The MX record says where to deliver mail that is sent to you. Three more records prove that mail that claims to come from yourapp.com is real. SPF is a record that lists the servers allowed to send mail for your domain. DKIM is a record that holds a public key, so receivers can check that a message was signed by you. DMARC is a record that tells receivers what to do with mail that fails SPF or DKIM. Amazon SES is the AWS service for sending email:
| Record | Says | Example |
|---|---|---|
| MX | Where to deliver mail that is sent to you | 10 inbound-smtp.ap-south-1.amazonaws.com or your Google/Zoho mail servers |
| SPF (TXT at the apex) | Which servers may send mail as your domain | "v=spf1 include:amazonses.com ~all" |
| DKIM (CNAMEs from your sender) | A public key, so receivers can check the digital signature of each message | three CNAMEs that SES (or your mail provider) gives you |
DMARC (TXT at _dmarc) | What to do when SPF or DKIM checks fail, and where to send reports | "v=DMARC1; p=none; rua=mailto:dmarc@yourapp.com" |
Start DMARC with p=none (only watch). Read the reports for a few weeks to find every sender that is allowed. Then change to quarantine (send failing mail to spam) and finally to reject (refuse failing mail). If you jump straight to reject, your own invoices can disappear without a sign. Amazon SES and similar services give you the exact records to paste. Your job is to know where they go and why.
Moving a live domain without downtime
Here is the situation. The site runs on Vercel (or an old host) and you are moving it to this AWS setup. DNS is where downtime can sneak in, because old answers stay in caches. A runbook (a list of steps to follow) for this move looks like this:
Two days before: copy everything and lower the TTL
Export the existing zone. Recreate every record type in the Route 53 hosted zone: MX, TXT, and verification CNAMEs, not only the A record. Then lower the TTL of the records that will change to 60 seconds. Wait at least as long as the old TTL, so that the change reaches everywhere. This one step makes the switch fast and easy to undo.
Test the new setup for real before anyone is sent to it
Bypass DNS and talk to the ALB directlycurl -sI --resolve yourapp.com:443:<ALB-IP> https://yourapp.com/Sends the request for yourapp.com to one of the ALB's IPs, and the certificate still checks out. Test login, uploads and every important page. No DNS change is needed
Cut over — weighted first if you can
Change the nameservers at the registrar to the four Route 53 NS values only if the zone is completely ready. If it is not, change only the records. Use the weighted move from above (10, then 50, then 100), or switch the record at a quiet hour.
Watch, keep the old stack, then raise the TTL
Keep the old host running for at least the old TTL plus one day. Users behind resolvers with old saved answers still reach it. Watch the 5xx rate and the delay on the ALB. When all is stable, raise the TTL again to 300–3600 seconds. This lowers query costs and speeds up lookups. Only then turn off the old setup.
A low TTL means changes work fast, but there are more queries and the first lookups are a little slower. A high TTL is the opposite. Use a high TTL most of the time and a low TTL around changes.
Protecting the domain itself
A perfect setup is worth nothing if someone else controls the domain. Real incidents come from the boring parts:
- Expiry. Turn on auto-renew and keep a valid payment method. Companies have lost domains because a card expired. Also put the expiry date in a calendar.
- Security of the registrar account. The account that owns the domain has the highest power. Use MFA (a second login step, such as a code from an app). Use a shared team mailbox, not one person's address, as the contact. Do not reuse passwords. Whoever controls this account controls all your DNS.
- Domain lock and privacy. Turn on the transfer lock, which stops anyone from moving the domain away. WHOIS privacy hides your personal details from the public registration database. Route 53 Domains includes it for free on most TLDs (a TLD is the ending, such as .com).
- Dangling records. A dangling record points to something that no longer exists. For example, it may point to an S3 bucket, an ELB or a third-party service that you deleted. Someone else can claim that name and take over your subdomain. This is called a “subdomain takeover”. Delete the DNS record when you delete the resource behind it.
- CAA record. This is a short record that limits which certificate authorities (the companies that issue certificates) may issue certificates for your domain. Examples:
0 issue "amazon.com"and0 issue "letsencrypt.org". - DNSSEC adds digital signatures to your DNS answers, so nobody can fake them. Route 53 supports it. But it adds risk: one mistake with a key can make the domain stop working. Turn it on on purpose, after you understand it. Do not turn it on just because it exists.
What this costs
| Item | Approx. price |
|---|---|
| Hosted zone (public or private) | $0.50 per month each |
| Standard queries | $0.40 per million |
| Alias queries to ALB / CloudFront / S3 | Free |
| Health check (basic) | ~$0.50–$2.00 per month each, depending on options (HTTPS and fast checks cost extra) |
| Domain registration | Depends on the TLD: .com is about $14–15 a year. Check the current price for others |
The whole DNS part of this project costs roughly $1–3 a month plus the domain fee. It is the cheapest part of the setup, and it gives you a lot of control.
Troubleshooting table
| Symptom | Likely cause | Fix |
|---|---|---|
| dig shows the old IP hours after the change | DNS resolvers (the servers that look up names for users) still keep the old answer until its old TTL ends | You should have lowered the TTL a day earlier. Run dig @8.8.8.8 and dig @ns-xxx.awsdns-xx.com to see the real (authoritative) answer |
| Works on your phone, not on your laptop | Your laptop or your internet provider's resolver has a saved answer (or a saved “does not exist” answer) | Clear the cache: sudo dscacheutil -flushcache and sudo killall -HUP mDNSResponder (macOS), or ipconfig /flushdns (Windows). Or test with another resolver |
| Cannot create a CNAME at the bare domain | DNS does not allow a CNAME next to the other records that every bare domain has (SOA, NS) | Use an alias A record (Route 53) on the bare domain. Use a CNAME only for subdomains |
| Certificate error on the new domain | The ACM certificate does not cover that name, is not validated, or (for CloudFront) is not in us-east-1 | Check the certificate's domain list and status. A wildcard does not cover the bare domain |
| ERR_NAME_NOT_RESOLVED for a whole domain | The nameservers at the registrar do not match the four NS values of the hosted zone, or the domain has expired | Compare the registrar's NS list with the NS record of the hosted zone. Check the expiry date |
| The bare domain works, www does not (or the other way round) | Only one of the two records exists | Create both. Pick one as the official address and redirect the other to it |
| Weighted routing sends everything to one side | A weight is 0, a health check is failing, or resolvers keep the old answer (caching) | Check the weight, the record ID and the attached health check of each record |
| Email stopped working after the DNS move | The MX, SPF and DKIM records were not copied to the new hosted zone | Export the old zone before you move. Recreate every record type, not only A |
Two lookups that solve most DNS problems
dig NS yourapp.com +shortWhich nameservers does the world think are in charge? The answer must equal the NS record of the hosted zone. A mismatch is the #1 cause of “my records don't work”
dig @ns-123.awsdns-45.org yourapp.comAsk Route 53 directly. You get the real answer and no cache is used. If this answer is right but users still see old answers, the cause is the TTL. Wait
Use these together with dig +trace and a question to a public resolver (Lesson 3). They solve almost every DNS problem.
Interview corner
Why can't you put a CNAME on the root domain, and what does Route 53 do instead?
A CNAME cannot exist next to other records with the same name, and the bare domain must have SOA and NS records (and often MX). Route 53 offers alias records instead. They look like A or AAAA records, and Route 53 works out the current IPs of the AWS resource inside. They are allowed on the bare domain, and queries to them are free.
How would you migrate a production site to a new host with no downtime?
I recreate all records and lower the TTLs well before the move. I test the new setup directly with curl --resolve. I move traffic step by step with weighted records and watch the results. I keep the old setup running for at least the old TTL. Then I raise the TTLs again and switch the old setup off.
What is a private hosted zone used for?
It holds internal names that work only inside the VPCs that are attached to it, such as db.internal.example.com. Applications no longer depend on endpoint names that AWS generates, and the internal layout stays out of public DNS.
Explain Route 53 failover routing and its limits.
There is a primary and a secondary record, with a health check on the primary. If the check fails, Route 53 answers with the secondary. The time it needs is the detection time plus the TTL of the saved answers. That means minutes, not seconds. So it suits the failure of a whole site or region, not a problem with one server.
Users in one office still reach the old server after your DNS change. Why?
Their resolver keeps the old answer until the previous TTL ends (some resolvers also ignore low TTLs). I ask the authoritative server (the nameserver that holds the real records, not a cache) to confirm that the change is live. For future changes, I lower the TTL beforehand.
What are SPF, DKIM and DMARC?
SPF lists the servers that may send mail for the domain. DKIM signs messages with a key that is published in DNS. DMARC sets the policy (none, quarantine or reject) and the reports for mail that fails those checks. Together they stop faked mail (spoofing) and help mail reach the inbox.
What is a subdomain takeover?
A DNS record still points to a cloud resource that was deleted (a bucket, an app, an ELB). An attacker claims that resource name and can then serve content on your subdomain. You prevent it by deleting DNS records together with the resources they point to.
Practice task before Lesson 14
- Create alias records for the apex,
*andwwwpointing at the ALB, andassetspointing at CloudFront. Verify each withdigandcurl. - Add the ALB rule that redirects
wwwto the bare domain and confirm the 301 withcurl -I. - Create two weighted records for a test subdomain (
canary.yourapp.com). Point one to the ALB and one to a plain IP, with weights 50/50. Look it up many times in a row and watch the split (and the caching). - Create a Route 53 health check on
/api/health. Stop the app on every server (or temporarily change the target group path), and watch the health check turn unhealthy in the console. - Create the private hosted zone, add
db.internal.yourapp.com, and from an EC2 server rundig +short db.internal.yourapp.com. From your laptop the same command should return nothing. - Write down the cutover runbook for your own domain in six bullet points. Include the TTL you would set and when.
Add a CAA record and an SPF + DMARC (p=none) record. Then check the result with a public tool such as mxtoolbox.com or Google's Admin Toolbox.
Conclusion
Route 53 is where the long AWS names become your own brand. It also gives you safe tools for changing things in production.
- Alias records for anything in AWS, including the bare domain, where a CNAME is not allowed.
- Routing policies turn DNS into a traffic tool: weighted records for step by step moves, failover for outages of a whole site.
- Private zones give internal resources names that stay the same after restores and failovers.
- Email needs its own records (MX, SPF, DKIM, DMARC), and every record type must move with the zone.
- TTL is your rollback speed. Keep it low around changes and high the rest of the time. Test with
--resolvebefore you switch.
The whole setup is now live on your own domain. But you built it by hand, and you deploy by logging in over SSH. Now it is time to automate the release with CI/CD.
End of Lesson 13. Next: Lesson 14 — CI/CD with GitHub Actions.
Published by Jainik · Last updated · How these lessons are made
Keep learning
Related series