Don't Append a Trailing Dot to Domain Names in Software You Ship to Customers
If you build software that runs in someone else's environment, anything that isn't a SaaS you operate yourself, there's a small, innocent-looking line of code you should think twice about writing:
1hostname = hostname + "."
Appending a trailing dot to a domain name before you resolve it feels "more correct" when you own the domain name. It can even be a performance optimization (more on that later). But in software you don't control the runtime for, it can result in a hard failure, and you won't see it in testing, because it only breaks in DNS environments that look nothing like yours. This is a lesson I learned the hard way recently.
What a trailing dot does
A domain name ending in a dot is a fully qualified domain name (FQDN), an absolute name, anchored at the DNS root:
1www.example.com. ← absolute (with trailing dot)
2www.example.com ← relative (no trailing dot)
Most people never type the trailing dot, and most of the time it doesn't matter. But to a DNS resolver, the trailing dot is a semantic switch. It tells the resolver: this name is complete, resolve it exactly as written, do not try appending any of my search domains.
Without the dot, the name is relative, and the resolver is free to expand it using the search domain list before (or after, depending on the ndots option) trying it as-is.
What search domains do
A search domain (or "search suffix") is client-side resolver configuration. It lives on the machine doing the lookup. On Linux it's in /etc/resolv.conf:
1nameserver 10.0.0.2
2search corp.example.com
The search line says: "If someone gives me a name that doesn't resolve on its own, try appending these suffixes and resolve again."
So if an application looks up wiki, the resolver tries:
wiki— no answerwiki.corp.example.com— ✅ resolves
This is a convenience: people (and programs) can use short names, and the environment fills in the rest. It's extremely common in corporate networks, Active Directory environments, Kubernetes clusters, and anywhere DHCP hands out a domain suffix.
A failure: the trailing dot opts out of a mechanism you didn't know the customer was relying on
Imagine your software connects to a managed database by resolving a hostname, a hostname you own:
1mydb-cluster-01.db.us-east-1.example.com
In your test environment, that name resolves directly. Your DNS server holds the record as an absolute name. Everything works. Adding a trailing dot changes nothing:
1$ dig mydb-cluster-01.db.us-east-1.example.com.
2;; status: NOERROR ← resolves fine, dot or no dot
Ship it. Now it runs in a customer's environment. Their setup is different: they run their own DNS servers, and the record for that db doesn't exist as an absolute name. It only exists inside their internal zone, reachable via a search suffix:
1/etc/resolv.conf:
2 search internal.customer.example
So on the customer's box, the name only resolves like this:
1$ dig +search mydb-cluster-01.db.us-east-1.example.com
2;; -> tries mydb-cluster-01.db.us-east-1.example.com.internal.customer.example
3;; status: NOERROR ← found, via the search suffix
But your code appended a trailing dot. So the resolver treats the name as absolute, skips the search suffix entirely, and queries only the bare name, which their DNS server has never heard of:
1$ dig mydb-cluster-01.db.us-east-1.example.com.
2;; status: NXDOMAIN ← "this name does not exist"
NXDOMAIN is a definitive "no such name". Your getaddrinfo() call raises, the connection fails, the customer files a ticket, and you get paged.
It gets worse in containers
If your code is used as part of a container image, the resolver config isn't even the host's. It's whatever the customer (or their orchestrator) injects into the container. /etc/resolv.conf inside a container is routinely overridden:
- Docker lets customers set
--dns,--dns-search, and--dns-option, or bind-mount their own/etc/resolv.confover the container's. - Kubernetes sets
/etc/resolv.conffrom the pod'sdnsPolicyanddnsConfig(customnameservers,searches, andoptionsincludingndots).
So the search list your code runs against can be a runtime decision made by the customer, completely invisible to you. You have even less basis than usual for assuming a name is fully qualified. A trailing dot hard-codes an assumption about a file you don't own, on a machine you don't own, generated by a system you don't control.
Where ndots comes in
There's a second, related knob that decides whether the search list gets applied: options ndots:N in resolv.conf. It sets a threshold: how many dots must a name already contain before the resolver tries it as absolute first, instead of applying the search domains first?
ndots:1(the glibc default): any name with at least one dot is tried directly first. Only truly bare names likewikiget the search treatment.ndots:5(the Kubernetes default): a name needs 5+ dots before it's tried directly. Anything with fewer dots gets search domains appended first.
Worked example under ndots:5 with a Kubernetes search list:
1search my-ns.svc.cluster.local svc.cluster.local cluster.local
2options ndots:5
Looking up google.com (1 dot, below the threshold), the resolver dutifully tries:
1google.com.my-ns.svc.cluster.local → NXDOMAIN
2google.com.svc.cluster.local → NXDOMAIN
3google.com.cluster.local → NXDOMAIN
4google.com → ✅
Four lookups (eight if you count A + AAAA) to resolve one public name. This is the well-known ndots:5 latency problem. The standard fix for that latency problem is to append a trailing dot. Making google.com. absolute causes the resolver to skip all that search-domain fan-out and query once. For software that runs inside your own cluster, where you know the resolver config, that's a legitimate optimization.
That's why the trailing dot is tempting, and why it's dangerous in the wrong context. The same trick that's a smart optimization in an environment you control is a latent outage in an environment you don't.
Further reading
The Kubernetes community has written extensively about this, mostly from the performance angle (too much search-domain appending), which is the mirror image of the correctness failure above. All of it builds the right mental model:
- Marco Pracucci, Kubernetes DNS resolution (ndots:5) and why it may affect application performances: the canonical writeup on the ndots:5 latency problem and using FQDNs (trailing dots) to fix it.
- Karan Sharma, DNS Lookups in Kubernetes: the best step-by-step walk through the resolver flow: ndots, search, and when a name is treated as an FQDN.
- Adam Brodziak, DNS performance issues in Kubernetes cluster: a debugging narrative (
getaddrinfo EAI_AGAIN) that shows how confusing these failures look in practice. - Julia Evans, Some ways DNS can break: great general DNS intuition from someone who explains this stuff better than anyone.
Conclusion
- A trailing dot is a behavior change, not a formatting tweak.
nameandname.can resolve to different answers, or one resolves and the other returns NXDOMAIN. - Search-domain reliance is invisible until you remove it. Plenty of customer environments resolve your hostnames only via search-suffix append. A trailing dot silently disables that path.
- In containers, the resolver config is the customer's runtime decision. You don't own
/etc/resolv.conf, the host, or the orchestrator that generates it. - Using FQDNs for performance is fine when you own the resolver config (your own cluster, your own fleet). It is not fine to bake into software that lands in arbitrary customer environments.
- If you ship software that runs on customers' machines: resolve the name the customer gave you, the way their resolver is configured to resolve it. Don't append the dot. Let their
searchandndotssettings do their job.