Stop Using Real TLDs in Test Environments
Last Updated:
Quick note to my regular readers: You might notice that this article’s tone isn’t as scholarly as some of my other ones. That’s why I’ve categorized it under Rants. This is something that has been on my mind for a long time and the only way to explain it is by doing some light ranting about it.
Hey there, SaaS developer. I want to talk to you for a minute about something that you’re totally doing and you don’t think is a big deal, but it kind of is.
Stop using real domains and hostnames in your test environment. Append .invalid to the end of all of them instead. Or just use .invalid instead of a real top level domain (TLD). That way, while you may inadvertently send a query into the domain name system or an email out of the company server, you will be far less likely to chance your platform’s security or play roulette with something potentially embarrassing as you do your work.
If I have your attention now, I’ll give you a few good reasons why you should stop doing this.
That domain is probably registered
I get it and I’ve done it. I’ve dummied up user email addresses as part of test data or accounts: jane.smith@mail.com, testuser@dev.com, and someone@domain.com.
But have you stopped for a minute to think about what it is you just used? Those three domains are three real, registered domains. mail.com is a free email service. dev.com belongs to a development group. domain.com is a registrar. And all of them have MX records, meaning they’re setup to receive email.
So your dummy data fires off an alert email letting the user know that they successfully logged into their account. Guess what? A mail server on the other side just accepted that message that you intended for no one and made it accessible to someone. Or maybe many someones. Perhaps unscrupulous someones who are watching it to determine what it is they can do with the data.
What was in that email? Just a notification with a date and time? Maybe an IP address you logged in from. Perhaps a 2FA code? Reset links? What if it was more sensitive like health information?
Maybe it isn’t a huge issue for you as you redirect your outbound mail through a function that sinkholes it. Or maybe you skip the send part of your code. But ask yourself… do you feel lucky? Is it even worth the risk if someone forgets to flip the switch or pulls the wrong set of code or misses a configuration?
And we haven’t even begun to talk about your sending reputation. Did you send it via some third party mailing platform like Resend? Is it a mailserver you janked together on a box using a guide from Digital Ocean or AI? Did you setup an SMTP relay on your corporate email? It doesn’t really matter, because it is likely that someone at the other end just received that unsolicited message and it looked suspicious. Now it is possible that your domain and your IP address start popping up on blocklists or take reputational hits. Is it likely that a single instance or a few will trigger this? No. But we’ve all written that while true loop and caused an oopsie.
It isn’t worth it. Just use .invalid instead.
Pre-fetching is real
With today’s need-for-speed combined with a desire to feed the algorithm, browsers are more than willing to engage in pre-fetching. And they won’t discriminate. They’ll pre-fetch and even pre-render pages. So if you’re using dev.com as a placeholder link, your localhost, no matter what it is, is going to send queries and possibly start fetching content from dev.com.
Again, someone owns that site. Now they’re seeing your machine or remote server make attempts at pulling data from your IP address. And not just that. If they’re really savvy and you’re doing HTTP(S) queries, they’ve setup a server to start logging all the URIs that you are trying to access along with the headers you are sending. With a bit of research, they could start piecing information together about your SaaS and how it works as you are trying to build it.
And what might that kind of blueprint be worth to a hacker looking to do reconnaissance on your site.
It isn’t worth it. Just use .invalid instead.
And so is clicking on links!
OK, so maybe you’ve disabled pre-fetching. That doesn’t mean you (or your unit tests) won’t diligently check that when you click that <a> tag, the new tab launches with where you want it to go. And all the above still applies when you do this! The only difference is that you actively sent the data to that domain instead of your browser doing it for you.
Somehow, this feels worse. For you. You actively did this.
It isn’t worth it. Just use .invalid instead.
That made up Top Level Domain might not be made up for long
In case you have been under a rock (or work a job that is outside of the domain name industry like most of the rest of the world), [ICANN is actively working on introducing new TLDs into the root zone. So what does that mean for you? Maybe you were being smart and using something like example.whatever
But what if .whatever is undergoing review to make it into the root? Guess what? It means that people will start registering .whatever domains and your example.whatever domain could be real.
It isn’t worth it. Just use .invalid instead.
“I put my stuff on subdomains, so I’m OK because those are hard to find”
Like hell!
You should watch this video from DNS Made Easy to understand how DNS queries work. You just sent a query into the domain name system. With resolvers logging data (oh yes, they totally do - Cloudflare and Google don’t run those resolvers because they love you and want you to stick it to your ISP) and the advent of passive DNS, there’s a pretty good chance someone, somewhere, just saw that lookup you were doing and now has an idea where that resource lives. Maybe it isn’t a huge deal, but security through obscurity isn’t a thing.
It isn’t worth it. Just use .invalid instead.
What is with .invalid?
When the Internet was created and as it grew, the smart people behind DNS and ICANN identified a bunch of TLDs that really have no business ever being delegated. Ever. So much so, they maintain a list of those domains under RFC 2606 and RFC 6761. At the time of publishing, here are the strings that will never be delegated to the global root zone:
.invalid: Use for seed data, dummy user accounts, and mock payloads where you want requests to fail immediately if executed..test: Use for local software testing environments and integration test suites that require structured hostnames.example.com/example.org/example.net: Reserved specifically for public documentation, user guides, and visual mockups..localhost: Reserved to point safely to local loopback addresses (127.0.0.1)..internal: Formally designated by ICANN for private, internal-only network applications.
In addition to the above, ICANN announced in the 2026 Next Round that there are a handful of TLDs that are also blocked for delegation.
AFRINIC GNSO INTERNIC NRO TLD ALAC GTLDSERVERS INTERNAL PTI WHOIS APNIC IAB IETF RFCEDITOR WWW ARIN IANA IRTF RIPEASO IANASERVERS ISTF ROOTSERVERS CCNSO ICANN LACNIC RSSAC GAC IESG NIC SSAC
So what does this mean? It means that a query for any domain using .invalid will always respond with an NXDOMAIN (Non-Existent Domain) response. Servers won’t send data. Emails won’t be received. Services won’t inadvertently connect to places or send data to resources that they really shouldn’t. The same will go for anything that is blocked by ICANN in their new gTLD programs for the next many years, but might be subject to change. Point is, you have a lot of options and ways to ensure that your data doesn’t accidentally escape.
Most importantly, SaaS developer, you won’t be playing roulette with your job every time you test something that uses a real domain that might resolve to a real place that can do something with that data. And keeping your job, especially with AI doing so much of it, is a good thing.
Automate the Fix: Block Real Domains in CI
Please don't rely on developer memory or hacks. You can add a quick check to your pre-commit hooks or CI pipeline to flag bad domain patterns in test files and seeds before they hit production. This is pseudo code and you will have to adjust to fit your needs.
#!/bin/bash
# Pre-commit check: Block real email domains in test/seed files
FORBIDDEN_DOMAINS="@(gmail|yahoo|hotmail|outlook|mail|test123|domain)\.com"
if git diff --cached --name-only | grep -E '\.(js|ts|py|json|md)\(' | xargs grep -E "\)FORBIDDEN_DOMAINS"; then
echo "ERROR: Real domain detected in staged test/seed data."
echo "Please replace real domains with .invalid (e.g., user@example.invalid)"
exit 1
fi
Conclusion
Now, go to your daily standup and spread this new found knowledge. You’ll look like a hero and receive all sorts of accolades.
And then you can Buy Me A Coffee.

.jpg)