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:

  1. wiki — no answer
  2. wiki.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.conf over the container's.
  • Kubernetes sets /etc/resolv.conf from the pod's dnsPolicy and dnsConfig (custom nameservers, searches, and options including ndots).

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 like wiki get 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:

Conclusion

  • A trailing dot is a behavior change, not a formatting tweak. name and name. 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 search and ndots settings do their job.