Why a Trailing Dot in DNS Can Break Your Customers
If you build software that runs in someone else's environment, anything that isn't a SaaS you operate yourself, the following code can break your customers:
1if not hostname.endswith("."):
2 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.
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: you 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 search domains the customer relies on
Imagine your software connects to a managed database by resolving 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 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 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 → OK
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.
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. - Customer environments frequently resolve hostnames only via search-suffix append. A trailing dot silently disables that path.
- In containers, 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). Don't bake it into software that lands in arbitrary customer environments.
- Resolve the name the way the customer's resolver is configured to resolve it. Don't append the dot.