The Unexpected Nightmare of a Simple Domain Redirect in Route 53


The internal product we built last year—and are still improving—has lived under a domain like this:

https://stage.frontend.servicename.orgname.aws.dev.

Needless to say, it’s long, ugly, and impossible to remember. So during a backlog grooming session, I raised a story to add a more manageable domain and redirect the long one to something like this instead:

https://servicename.aws.dev

As with any backlog grooming session, we agreed that we need to do it but couldn’t decide whether it’s a 1 or a 3 point story, so we put a timebox on it for one day. Little did we know.

One of the newest team members picked it up because we thought it would be done by lunch. But if that were true, I wouldn’t be writing this newsletter.


Why We Couldn’t Just Make the Switch

You may be wondering why we couldn’t just do what we’d already done, and fully replace it. The answer is simple - we have tens of thousands of internal customers who use the product already, and all of our links in the wiki and the product’s website use the long URL.

We needed the old and new domains to work in parallel to avoid breaking anything, at least in the beginning.

We initially considered testing on beta and gamma stacks first—but quickly abandoned the idea after two days of cross-account permission headaches.


Why Beta/Gamma Didn’t Make the Cut

  1. Apex domains require A-record aliases, not CNAMEs. They behave differently, and managing them across multiple AWS accounts only complicates things further.
  2. Route 53 Hosted Zones live in prod. Beta/gamma subdomains would be managed separately—they’d need cross-account IAM configurations just to update DNS properly.
  3. CDK/Cross-stack limitations with CloudFront. CDK cross-stack references to CloudFront distributions only resolve at deployment time, not during synthesis—so you can’t cleanly reference them across accounts/stacks prior to deployment.

The result: we focused only on production.


What We Learned the Hard Way

1. You Can’t Use CNAMEs for Apex Domains

An apex domain is just a fancy way of saying the base domain, like service.aws.dev. When I tried adding a CNAME record for the apex domain in Route 53, it was rejected — because DNS standards prohibit CNAMEs at the root level.

Route 53 supports only certain alias targets for apex A-records (whatever some of these are):

  • API Gateway API
  • AppRunner
  • AppSync
  • CloudFront distributions
  • Elastic Beanstalk environments
  • Application, Classic, and Network Load Balancers
  • Global Accelerator
  • LightSail container service (whatever that is)
  • OpenSearch endpoints
  • S3 website endpoints

We chose CloudFront. Here’s how we set it up in CDK:


2. TLS Certificate Requires Multi-Zone DNS Validation

Our existing cert worked for the old domain—but the new domain never validated until we updated our configuration.

ACM requires DNS validation for each domain; since they belonged to different hosted zones, we had to use fromDnsMultiZone() in CDK.


3. Don’t Forget CORS and CSP

We had originally added a wildcard like *.servicename.aws.dev, but that does not cover the apex domain (servicename.aws.dev). Wildcards only match subdomains.

Requests from the new domain were being blocked either client-side (CSP) or server-side (CORS). The fix:

  • Explicitly include apex domain in your CORS allowlist
  • Add it to your CSP
  • Update any custom backend allowlists as needed

4. Webpack’s publicPath Broke Asset Loading

Even with DNS, CDN, and certs in place, the new domain still failed to load assets unless they had already been cached under the old domain. Because assets were hardcoded to the old domain, they only worked if CloudFront already had them cached there. Fresh visits via the new domain (e.g. incognito) would fail.

We found in webpack.config.js:

This hardcoded domain caused asset requests to always point to the old domain, which failed if the cache was cold or empty.

We changed it to:

Now assets load relative to whichever domain is being used—clean, flexible, and reliable.


Final Reflection

We thought this would be trivial. It wasn’t.

Changing a domain involves far more than DNS—it touches certs, security policies, asset loading, CDN caching, and deployment toolchains.

The problem wasn’t that it was hard—it was that we thought it would be easy.

Now we know better—hopefully, you will too.

Cheers!

Evgeny Urubkov (@codevev)

600 1st Ave, Ste 330 PMB 92768, Seattle, WA 98104-2246
Unsubscribe · Preferences

codevev

codevev is a weekly newsletter designed to help you become a better software developer. Every Wednesday, get a concise email packed with value:• Skill Boosts: Elevate your coding with both hard and soft skill insights.• Tool Tips: Learn about new tools and how to use them effectively.• Real-World Wisdom: Gain from my experiences in the tech field.

Read more from codevev

Last week, I ran into this tweet: the tweet It kinda triggered me. Why would someone pay $0.40 per secret per month when you could just use AWS Parameter Store and store them as SecureStrings FOR FREE? That’s what I use for oneiras.com, so I was determined to find out if I’d missed something. Am I unknowingly paying per secret? Or is there actually a reason to use AWS Secrets Manager instead? Turns out, there are a couple, but only if you really need them. The Big One: Automated Secrets...

Well, the global AWS outage happened just four days after I sent a newsletter about COEs and how “nobody gets blamed.” Great timing, right? I wish I could’ve been in the weekly global ops meeting to see the temperature in the room. That’s the one where teams present their recent issues and learnings. I can only imagine how lively that one must’ve been. Turns out the culprit was a DNS failure in the Amazon DynamoDB endpoint in the us-east-1 region. And while that sounds region-specific, it...

Someone pushes a new feature to prod the same day you go on-call. Hours later, your phone goes off - not a gentle buzz, but a full-blown siren that could wake up the entire neighborhood. You open the alert, and it’s for a feature you didn’t even touch. Maybe it’s unhandled NPEs, maybe something else. Doesn’t matter. You’re the one on-call, so it’s your problem now. When Things Break In those moments, it’s usually faster to just debug and fix it - even without full context. I’m pretty good at...