DNS resolution latency is invisible until it is not. A resolver that answers in 5 ms and one that answers in 2 seconds produce identical application code and wildly different page load times. Because the delay appears in name lookups rather than in your database or PHP, it hides from application profiling. This guide shows how to measure resolver latency directly, find the slow path, and tune the settings that cause it.
Measure resolver latency directly
dig reports query time in its footer. Run several lookups against your configured resolver and against a public one to isolate local configuration from upstream latency.
dig +stats example.com @127.0.0.53 | grep -i "Query time"
dig +stats example.com @1.1.1.1 | grep -i "Query time"
A large gap means the problem is local — the stub resolver, the resolving library, or ndots behaviour. A uniformly slow result on both means upstream latency.
Understand the ndots search-path trap
The default ndots:1 means any name with fewer than one dot in it is tried against every search domain before being tried as absolute. With two search domains, api becomes two failed lookups followed by the real one — up to three round trips where one should do. For applications resolving many short internal names, this multiplies latency.
# /etc/resolv.conf
options ndots:2 timeout:1 attempts:2 rotate
nameserver 127.0.0.53
Raising ndots to 2 means names with fewer than two dots skip the search list. Use a fully-qualified name with a trailing dot (api.internal.example.com) when you want no search expansion at all.
Trace where the time actually goes
When dig is fast but the app is slow, capture the resolver traffic and read the timings. tcpdump on port 53 shows whether a query is retried, duplicated, or sent to a second nameserver after a timeout.
tcpdump -ni any -ttt port 53 -c 20
The -ttt flag prints the delta between packets. A repeated query to a second nameserver followed by a long gap is the classic signature of the primary resolver timing out and the client failing over. Reducing timeout and attempts in resolv.conf shortens that worst case.
Run a local caching resolver
A local resolver caches answers in memory and answers repeat lookups without network round trips. On a VPS, a small caching resolver bound to the loopback interface turns a 30 ms upstream lookup into sub-millisecond cache hits. Point /etc/resolv.conf at it and let it forward.
# after installing a local resolver
# /etc/resolv.conf
nameserver 127.0.0.1
options ndots:2 timeout:1 attempts:2
| Signal | Meaning | Action |
|---|---|---|
| dig slow, public DNS fast | Local stub/library issue | Check ndots and stub config |
| Both slow | Upstream latency | Use a closer/caching resolver |
| tcpdump shows retries | Timeouts and failover | Lower timeout/attempts |
| First query slow, rest fast | Cold cache | Expected; keep resolver warm |
Test the resolver the way the application does
dig is a clean-room test; your application uses the system resolver through getaddrinfo, which obeys /etc/nsswitch.conf and the search list. Reproduce the application’s path so you measure what it actually experiences, including any hosts-file lookup and any search-domain expansion.
getent ahosts example.com
getent ahosts api
strace -f -e trace=sendto,recvfrom -T getent ahosts api 2>&1 | head -30
The -T flag prints the time spent in each syscall. If the same name produces several sendto calls with multi-second gaps between them, you have found the retry-and-failover path that application-level profiling never shows.
Pick the right resolver for the workload
| Workload | Resolver choice | Reason |
|---|---|---|
| Single web server | Local caching resolver on 127.0.0.1 | Sub-ms cache hits, no extra hosts |
| Many containers | Host resolver + container DNS forwarding | One cache shared across namespaces |
| Inside a private network | Local resolver forwarding to internal DNS | Split-horizon names resolve correctly |
Avoid pointing every host at the same public resolver with no cache: upstream rate limits and added latency both bite under sustained load. A local cache absorbs the repeat lookups that dominate real traffic.
Does anything else depend on the resolver?
- Cron jobs and backup scripts that resolve S3 or database hostnames fail silently when resolution is slow but not broken.
- Health checks that use hostnames inherit the same latency; prefer IPs or keep the cache warm.
- Container stacks run their own stub resolver — check the container’s resolv.conf separately, not just the host’s.
A quick before-and-after check
Record a baseline before you touch anything, then compare. Ten lookups to the same name reveal both cold and warm latency.
for i in $(seq 1 10); do
dig +short +stats example.com | tail -1
done | grep -i "Query time"
After adding a local cache, the first query stays slow and the next nine should drop to under a millisecond. That pattern is the confirmation that the resolver — not the application — was the source of the delay.
Measure first with dig, then trace with tcpdump, then cache locally. Most “random slowness” incidents that resist application profiling are a resolver issue sitting one layer underneath. Pair this with the environment review on the cloud VPS benefits page, and see VPS tutorials for other diagnostic walkthroughs.




Leave a Reply
You must be logged in to post a comment.