Diagnosing DNS Resolver Latency on a VPS with dig and tcpdump

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
SignalMeaningAction
dig slow, public DNS fastLocal stub/library issueCheck ndots and stub config
Both slowUpstream latencyUse a closer/caching resolver
tcpdump shows retriesTimeouts and failoverLower timeout/attempts
First query slow, rest fastCold cacheExpected; 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

WorkloadResolver choiceReason
Single web serverLocal caching resolver on 127.0.0.1Sub-ms cache hits, no extra hosts
Many containersHost resolver + container DNS forwardingOne cache shared across namespaces
Inside a private networkLocal resolver forwarding to internal DNSSplit-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