TL;DR

  • Fifteen minutes of live port-53 traffic on this resolver: 17,424 queries over UDP, 309 over TCP (1.7%), and 5 truncated replies out of 17,735. The median answer was 100 bytes against a 1,232-byte ceiling.
  • Six public resolvers advertise five different UDP buffer sizes: 1232, 1410, 512 and 0. None advertises the 1400 that RFC 9715 has recommended since January 2025.
  • The TCP fallback costs one round trip and nothing else: +6.4 ms against a resolver measured 6.31 ms away, UDP faster in 41 of 50 paired runs. On the same host it is a coin flip at 30 of 50.
  • dig's own Query time says the fallback is free. It is not counting the handshake.

What a resolver actually sees

The question "does DNS use TCP or UDP" has a textbook answer - both, UDP first - and almost no published numbers behind it. So we counted. A fifteen-minute packet capture on this site's public resolver, reduced to totals with no addresses and no query names, puts 98.3% of arriving queries on UDP and 1.7% on TCP. Five replies in the whole window had the truncation bit set.

That is the useful shape of it. The TCP path is required by RFC 7766 and it does carry real traffic, but on an ordinary client mix it is the exception, which is exactly why a firewall that drops TCP port 53 can look fine for months before something asks for a large answer and hangs.

Five answers from six resolvers

The buffer a resolver advertises decides when the fallback happens, and there is no consensus on the number. Reading it from the reply, five runs each: this resolver, Quad9 and Cloudflare all say 1232, the DNS Flag Day 2020 default. OpenDNS says 1410. Google says 512, the original 1987 floor. AdGuard DNS says 0, which is what a responder sends when it is not offering a UDP size at all.

RFC 9715 recommends 1400, on the grounds that the interior of the internet runs at an MTU of 1500. Nothing we measured uses it.

The measurement trap

Timing the two transports produced a disagreement worth writing down. Wall clock around fifty paired runs says TCP costs 6.4 ms more against Quad9, which matches the 6.31 ms round-trip time we measured to it - the handshake is one extra round trip and nothing else about TCP shows up at this size. But dig's own reported Query time shows no difference at all, and by that number UDP wins only 7 of 30.

The reason is in the BIND 9 source: Query time is time_recv minus time_sent, and time_sent is stamped once the socket is already connected. The figure is honest about what it measures; it is simply not the one to use when comparing transports. Anyone benchmarking UDP against TCP with dig's built-in timer will conclude the fallback is free.

What changed on the guide

Does DNS use TCP or UDP? was a 793-word explainer with no commands in it. It is now a page you can check: four dig sequences that show the transport in use, force the truncation flag, watch the resolver switch to TCP on its own, and count the two packets against ten. The Windows half is tested rather than described: nslookup in set d2 debug mode prints a literal truncated answer line and then returns all 61 records, and set vc to force TCP makes that line disappear. Resolve-DnsName -TcpOnly confirms the same 61 but cannot show truncation at all - no parameter in its set reports the TC bit.

Comparing the two tools turned up one more thing worth keeping. Windows reports the answer as 4,879 bytes and dig reports 4,918 for the identical 61 records. Windows nslookup sends no EDNS record at all; dig sends one and gets one back, and it carries a DNS cookie. +noedns brings dig to exactly 4,879 and +nocookie to 4,890 - which is also what Quad9 and Cloudflare return, because they send the EDNS record without a cookie. Every byte accounted for.