I built my server infrastructure IPv6-only, on the theory that IPv6 is where the internet is going and I might as well already be there. It was clean, it was cheap, and in my mind it was finished.
Then, the real world came knocking. A friend messaged me, "Hey, I can't seem to load your website."
After a quick back-and-forth, the culprit was clear. My friend's home network, like a surprising portion of the internet, didn't speak IPv6. My server, a purist, refused them by design. I had to go back to the drawing board and figure out how to handle this unexpected new requirement.
The Tollbooth
The obvious solution was to add an IPv4 address to my server. For years, this was effectively free on AWS if the IP was attached to a running instance. But a recent policy change turned this on its head: every public IPv4 address now comes with a small but persistent hourly fee. Seriously? It's not much, but for my lean setup, it would nevertheless be an unwelcome toll. If you read my blogs at all, you know I'm drawn to challenges and driven by principle; and this hits both.
This irked me on two levels. First, there's the principle. Paying for a legacy protocol I was trying to move beyond felt like paying a tax on the past. Surely I was fighting the good fight? Second, and more importantly, this wasn't just about my friend; it was about universal accessibility. If he couldn't connect, neither could I when traveling, nor could anyone else on a similar legacy network.
The goal became clear: find a way to offer an IPv4 on-ramp to my IPv6-only world without paying the toll. Was it even possible?
But How?
My first thought was a dynamic DNS script to update a record whenever my server's non-static IP changed. But a quick check of the fine print revealed the flaw: the new charge applies to any public IPv4 address, not just the static ones. This path was a dead end.
I was stuck. How could I get an IPv4 address without actually having an IPv4 address on my server? The problem, reframed, was that I needed a doorman -- a service that could greet IPv4 visitors and translate for my IPv6-only host.
Doorman... Doorman?
And then, a revelation. I already had a doorman in my toolbelt; I just hadn't recognized it yet: a Content Delivery Network (CDN).
While the primary purpose of a CDN like AWS CloudFront is to cache content closer to users, its most important feature for my purposes is a side effect of its design: all CloudFront edge locations are dual-stack. They speak both IPv4 and IPv6 fluently.
This was the key. I could configure CloudFront not as a cache, but as a clever protocol gateway. The flow would be:
- An IPv4-only user tries to visit my site.
- DNS points them to the nearest CloudFront edge location's IPv4 address.
- CloudFront receives the request and, acting as a translator, forwards it to my origin server using its native IPv6 address.
My server could continue living in its pristine IPv6-only world, completely unaware that the original request came from a legacy network. And because CloudFront's free tier is incredibly generous (1TB of data and 10 million requests per month), this entire translation service would be completely free.
Step 3 is the part that deserves a footnote, because it is younger than it looks. CloudFront has accepted IPv6 from viewers for years, but until recently it would only ever talk to an origin over IPv4 -- which would have made my IPv6-only server unreachable as an origin, and this entire post impossible. AWS shipped IPv6 origin support on September 8th, 2025, nineteen days before I wrote this. I had been vaguely aware of the launch and had not connected it to my own problem at all. The solution I'm about to be pleased with myself for is one that would not have worked in August.
There's Always a Plot Twist
So I did it.
The initial setup was a success, but it revealed a new, more subtle problem. I have a number of internal services -- metrics collectors, authentication requests -- that call each other constantly. Now, all this high-frequency, internal "chatter" was also being routed out to CloudFront and back again.
I ran some back of the envelope calculations: my own automated services could easily burn through those 10 million CDN requests I get for free a month. I had put a doorman on the building and then routed the mailroom, the kitchen, and the cleaning staff through him too.
The solution was to split my DNS into two distinct universes.
*.scottliu.com(Through the Doorman): This wildcard record points to CloudFront. It's the path for all public, user-facing traffic.*.internal.scottliu.com(Straight to the Building): This new, more specific wildcard points directly at my server's IPv6 address, skipping the doorman entirely.
By reconfiguring my service-to-service chatter to use the *.internal domain, I got that traffic off the CDN. It never touches CloudFront now, which leaves the whole free tier for actual visitors.
A Word I Chose Badly
I called that second record "internal," and then spent a long time believing my own naming.
*.internal.scottliu.com is a CDN bypass, not a network boundary. It's a public DNS record resolving to a public IPv6 address. Anything on the internet with an IPv6 stack can look it up and connect to it, and when it does, it arrives at the same nginx on the same box that serves the public name -- the two hostnames are deliberately normalized to one backend mapping, precisely so a route can't be safe on one name and open on the other.
Which means "internal" describes who I expected to use it. Intent is not an access control.
What actually protects those endpoints is what protected them before I split the DNS: every route requires a credential. A session cookie for anything user-facing, a bearer token for service-to-service publishing, checked by the application, on both hostnames, identically. The .internal record changes which path the packets take. It changes nothing about who is allowed to be at the other end of them.
The distinction matters more than a naming quibble, because I did later write an endpoint that leaned on the word. If I were doing it again I'd call the record direct. and remove the temptation.
Takeaways
The useful move was changing the question. "How do I get an IPv4 address" has one answer and it costs money every hour forever. "How do I translate IPv4 requests" has several, and one of them was a tool already sitting in my account.
Worth saying what this costs me, though: I've put a CDN in front of my own site to solve a problem that has nothing to do with caching, which means CloudFront is now a dependency for my site being reachable at all. That's a real trade. I made it because the alternative was a bill that never stops, but it isn't free of consequences -- just free of charges.