i wrote a powershell script that benchmarks 17 public dns resolvers and then sets the best one on your machine. cloudflare, quad9, opendns, adguard, mullvad, a few of the family-filtered ones. it times real lookups against 10 domains, scores each provider, and applies the winner after you confirm.
the part i want to talk about isn't the benchmark itself. it's the flag i added last week that made it faster and, on purpose, a little less accurate.
by default the script tests one resolver at a time. 17 servers, 5 queries each across 10 domains, that's a couple minutes. fine, but slow enough that i didn't want to sit through it every time. so i added -Parallel, which uses ForEach-Object -Parallel on powershell 7 to measure a batch of them at once.
$results = $providers | ForEach-Object -Parallel {
Measure-DnsProvider -Provider $_ -TestCount $using:TestCount
} -ThrottleLimit $ThrottleLimit
it works. cut the wall-clock time way down. then i looked at the latency numbers and they'd crept up a few milliseconds across the board.
took me a second to get why. the whole point of the script is measuring how fast each resolver answers. when you run them concurrently, they're all sharing your one connection at the same moment their latency is being timed. so you're not really measuring cloudflare's speed, you're measuring cloudflare's speed while seven other lookups fight for the same pipe. every number reads a little slow, and worse, it reads slow unevenly depending on what else happened to be in flight.
so parallel mode is honest-fast but dishonest-precise. i left it off by default. the readme says it plain: use -Parallel when you want a fast answer, leave it off when you want the most precise ranking. felt weird shipping a speed feature with a "this makes the results worse" note attached, but that's the actual tradeoff and hiding it would've been worse.
the scoring is the other thing people ask about. each resolver gets a composite out of 100:
Score = (Speed x 0.40) + (Reliability x 0.25) + (Security x 0.25) + (Consistency x 0.10)
speed is 40% because that's what most people are here for. reliability is the percent of queries that actually resolved. consistency is jitter, the standard deviation of response times, because a resolver that averages 12ms but spikes to 90 feels worse than a steady 20. and security is 25%, which is the one design choice i'd defend hardest and also the one i'm least happy with.
here's the catch. security isn't measured. it's a pre-assigned number per provider based on stuff you can look up: does it do dnssec, does it support doh/dot, what's its logging policy, does it block known malware domains, has it been audited. quad9 scores high because it's a non-profit that threat-blocks and doesn't log. a plain legacy resolver scores low. i hand-built that table.
which means the security axis is really "what's publicly known about this operator," not "what did i observe." two resolvers with identical measured latency can rank differently entirely because of a table i typed. that's fine for picking a daily-driver dns. it's not fine if you thought the whole score was empirical. the readme's honest about it but i still don't love it.
one consequence of weighting security at 25%: the fastest resolver doesn't always win. a provider that's a few ms slower but does dnssec and threat-blocking can out-score a faster one that does neither. for a security-leaning tool that's the behavior i want. for someone who just wants the lowest ping it's mildly annoying, which is why -SkipApply exists so you can read the full table and pick yourself.
two other things i had to fix that weren't obvious up front.
first, ipv6. the script only touched ipv4 dns for a while. on a dual-stack network that's a quiet hole. you set cloudflare on ipv4, feel good, and the machine keeps resolving over whatever ipv6 dns the router handed out. so you didn't actually switch resolvers, you switched half of them. -IncludeIPv6 sets both families now, but only when the adapter has ipv6 bound and the provider publishes ipv6 anycast. off by default so it never changes an ipv6 config you didn't ask it to.
second, offline machines. early on, if you ran it with no connectivity, every query failed and the script happily built a ranking out of nothing but failures and picked a "winner." a ranking of all-failures still sorts. now there's a pre-flight check that probes a couple of known resolvers first and bails with a clear message instead of ranking garbage.
what's still not great: the security table is static, so a provider that changes its logging policy won't show up until i edit the file. and i'd like to measure doh/dot handshake time as part of speed instead of only timing plain udp/53 lookups, since that's what a lot of people actually resolve over now. haven't done it yet.
it's a small tool. it works, it backs up your old dns before touching anything, and -Restore puts it back. mostly i wanted a repeatable way to answer "is the dns i'm on actually good" instead of guessing. repo's here if you want it: https://github.com/TiltedLunar123/DNS-Benchmark
Top comments (0)