DNS Leak Prevention: Resolver Paths, Proxies, and Leak Tests
How DNS leaks expose browsing activity when a proxy is used, where common mitigations leave gaps, and how to verify resolver paths with a leak test.
BotBrowser Team
Prefer the maintained product doc?
This article has a matching page in the docs center. Use the docs for the canonical setup flow, current flags, and long-term reference.
A proxy changes where a browser's page traffic appears to come from, but the browser still has to turn each hostname into an address before it connects. If that lookup goes to a resolver outside the proxy route, the resolver learns which domains were requested and from which network, even though the page itself loaded through the proxy. That gap is a DNS leak, and it is one of the most common ways a proxy setup ends up inconsistent.
Fixing it is a question of ownership: for each lookup, who resolves the name, where does that resolver sit, and who can read its logs. The sections below answer those questions for SOCKS5, SOCKS5H, and HTTP CONNECT, compare the usual mitigations and the gap each one leaves, and finish with a leak test that treats any mismatch as a failed check.
Who resolves the hostname
The MDN glossary entry for DNS describes the system that maps domain names to IP addresses. A browser needs that mapping before it can open a connection, so every navigation, embedded resource, and background request raises the same question: which component performs the lookup. With a proxy in the path there are several candidates, and the proxy scheme decides which one answers.
Three common arrangements behave differently:
- SOCKS5. The browser resolves the hostname with a local resolver and hands the proxy an IP address. The lookup happens outside the proxy route, so the local network resolver sees the name.
- SOCKS5H. The browser hands the proxy the hostname itself and the proxy resolves it. The local resolver is not asked for names that travel through that connection.
- HTTP CONNECT. The browser names the target host in the tunnel request, and the proxy server connects to it. Resolution for the target normally happens on the proxy side.
A fourth case sits beside the proxy schemes: the browser can resolve names itself with its own resolver instead of the operating system's. That choice changes which resolver is asked and which servers it contacts, but it does not by itself say whether the answer is used for a direct connection or handed to the proxy. Treat browser-side resolution and proxy-side resolution as two separate switches, and write down the setting of each.
The SOCKS5 specification, RFC 1928, lets a client send either an address or a domain name to the proxy. The socks5h:// scheme used by browsers and command-line tools is a client convention for choosing the second form. Read the scheme in your configuration literally: socks5:// and socks5h:// are different decisions, and a single letter moves the lookup from your machine to the provider.
Moving the lookup to the proxy does not make it private. Once the proxy resolves a name, the provider decides which upstream resolver answers, which region that resolver sits in, whether answers are cached or filtered, and what is logged. Those choices are provider-specific and outside your control, and most providers do not publish them in a form you can check. The proxy configuration article covers the schemes in more detail, but a scheme label alone is never proof of where a lookup went. Verification is the only evidence.
What a leak reveals
A resolver on your local network or at your internet provider sees the domain names it is asked to resolve, the time of each query, and the network address that asked. If page traffic goes through the proxy while names are resolved locally, that resolver can still reconstruct a list of sites visited, even though it never sees page contents. HTTPS protects the page; it does not hide the name lookup that comes before it.
The second exposure is geographic. Resolvers are operated in specific regions, and many networks hand out resolvers close to the customer. If the proxy exit is in Germany while lookups arrive from a resolver that serves your home region, the two signals disagree. Whether any particular service compares them varies, and nobody can promise that it will or will not. A mismatch is still a reason to treat the setup as inconsistent, because the purpose of the route is that its signals agree.
The third is logging that you do not control. A resolver may keep records of the names it answered. Pointing the browser at a different resolver changes who holds those records without removing them. The useful question is therefore never whether DNS is hidden, but which operator can see these names, in which region, and whether that is the operator and region you chose.
Address families matter too. A browser that can use both IPv4 and IPv6 may reach a destination over a family that the proxy route does not cover, so a test that only looks at one family can miss a leak. Check which address families your proxy and the leak test support, and compare them to the behavior you expect.
Prefetch and other lookups that need separate attention
Browsers resolve names before they are asked to load them. A page that contains many links can trigger lookups for those link targets in the background, and the address bar can resolve a hostname while a person is still typing. These speculative lookups follow the browser's resolution path, not the path of the connection you opened on purpose. If that path leaves the proxy route, the leaked names include sites that were never visited.
This is why a proxy that handles every explicit connection correctly can still show a local resolver in a leak test. Page-level fetches, prefetch hints, and background lookups each need to be checked, and they are not interchangeable with a single page load. WebRTC raises a related but separate question about addresses rather than names, which the WebRTC leak prevention article covers.
You can test speculative lookups without trusting a public test page. Use a domain you control whose authoritative name server records queries, publish a page that links to a unique hostname under it, open the page through your configured route, and then look at which resolver addresses appear in the server's query log. A query from an address that belongs neither to your provider nor to the resolver you chose is a leak that a single-page leak test would not have shown.
Caching complicates the picture. A name resolved earlier in the same session can be answered from memory later, so a clean result on the second visit proves little. Start the browser fresh, use hostnames that have not been resolved before, and repeat the check after each configuration change.
Failure modes also differ. When a configured resolver is unreachable, some setups fall back silently to another one, and a fallback is a new path that the test described below has to catch. Prefer configurations that fail visibly over ones that quietly route lookups elsewhere, and confirm which behavior you have by blocking the resolver in a test environment you own and watching what the browser does.
Choosing between SOCKS5H, local DNS, system DNS, DoH, and VPN DNS
No single control closes every gap, so the useful comparison is the residual gap each one leaves. Four questions sort the options: who resolves the name, in which region, who can log it, and whether the setting applies to this browser only or to the whole machine.
SOCKS5H. It keeps names that travel through the proxy connection away from the local resolver, and it needs no browser feature beyond the scheme. The gaps are the provider's own upstream resolver, which you cannot inspect, proxies that do not support remote resolution, and lookups the browser makes outside the proxied connection.
Browser local DNS with --bot-local-dns. BotBrowser's built-in resolver keeps resolution independent of the provider's DNS behavior, which helps when a provider blocks, rewrites, or slows lookups. The gap is that it governs the browser side only. It does not change what a provider does for a connection that the provider resolves, and if you point it at a specific DNS server, that server becomes a new observer you have to trust.
System DNS. Setting the operating system to a chosen resolver replaces your internet provider's resolver with another one. It does not remove the observer, and the new resolver is usually in a region unrelated to the proxy. The setting also says nothing about whether the browser or the proxy resolves a given name.
DNS over HTTPS. RFC 8484 carries DNS messages over HTTPS, which stops observers on the network path from reading or altering them. The resolver still sees every name, its region still has to match the route, and the setting does not decide who performs the lookup. The DNS over HTTPS article covers browser controls and resolver limits.
VPN DNS. A VPN usually carries operating system DNS through its tunnel, which covers every application at once. That is coarse for per-browser or per-context routing, the VPN operator's resolver sees the names, and the resolver region follows the VPN exit rather than a separate proxy exit.
Combinations help where gaps do not overlap, for example SOCKS5H plus a browser-side resolver you chose deliberately. They also add decisions, and each new element is another observer or another place for a mismatch. Pick the smallest set whose observers you can name, then test.
A short rule of thumb helps when the choice is not obvious. If the provider supports SOCKS5H and you trust its resolver, start there and verify. If the provider blocks, rewrites, or slows lookups, or only offers SOCKS5, a browser-side resolver gives you a policy that does not depend on the provider's DNS. If other applications on the machine also need protection, that is a job for the operating system or a VPN, not for a browser flag.
Running a leak test and reading the result
A leak test is useful when it is run the same way every time and read against a fixed expectation. A repeatable sequence:
- Record the proxy exit address, its country, and the operator the provider states for it.
- Start the browser fresh with the exact flags you intend to run.
- Open a leak test such as dnsleaktest.com and run its extended test, which makes several lookups instead of one.
- Write down each resolver the test reports, with its operator and region.
- Compare that list to step 1. Every resolver should belong to an operator and a region you can explain.
Treat any difference as a failed check. A resolver run by your internet provider, a resolver in a country that does not match the exit, or an operator you did not choose means the route and the lookups disagree. Do not explain the difference away with a guess; change one variable, such as the proxy scheme or the local DNS flag, and run the test again. Compare operator and region rather than exact addresses, since large operators answer from many addresses in the same region.
The pattern in the result points to the cause. Your own provider's resolver usually means a name was resolved locally, so check the scheme and whether prefetch is covered. A well-known public resolver in the wrong region points to operating system DNS, a DoH setting, or a VPN. A resolver in the right region but run by an operator you did not expect points to the provider's upstream choice, which you can only change by changing providers.
A leak test has limits. It shows the resolvers that the test's own servers saw at that moment for the hostnames the test used. It does not observe lookups for other names, does not show provider-side logs, and cannot prove anonymity. A clean result is evidence that one path matched on one day.
A second limit is scope. Name resolution is one of several network paths. Address exposure through WebRTC, the proxy's own exit address, and the transport behavior of the connection are separate checks, and a passing DNS test says nothing about them.
The flags below are the documented way to run BotBrowser with a SOCKS5H proxy and the local resolver:
chrome --bot-profile="path/to/profile.enc" \
--proxy-server="socks5h://user:pass@proxy.example.com:1080" \
--bot-local-dns
The same configuration from Playwright, where the executable and profile paths come from the environment:
import { chromium } from 'playwright-core';
const browser = await chromium.launch({
executablePath: process.env.BOTBROWSER_EXEC_PATH,
headless: true,
args: [
`--bot-profile=${process.env.BOT_PROFILE_PATH}`,
'--proxy-server=socks5h://user:pass@proxy.example.com:1080',
'--bot-local-dns',
],
});
const page = await browser.newPage();
await page.goto('https://www.dnsleaktest.com');
await browser.close();
When different browser contexts use different proxies, the expectation is per context. Each context has its own exit, its own region, and therefore its own expected resolver operator, so run the sequence once per route and keep the results side by side. A single clean result for one route says nothing about a second route that uses another provider.
Decide in advance what happens after a failed check. A bounded response works best: stop the run, keep the configuration and the observed resolver list, and change one variable at a time. Avoid retry loops that keep sending traffic while the lookups are still unexplained, and avoid collecting more than the operator and region you need.
Repeat the check whenever the proxy provider, the proxy scheme, the flags, or the browser build changes, and keep the record to operator and region. Browsing history does not belong in a test log. For the wider question of keeping address, DNS, and WebRTC signals consistent, see proxy, DNS, and WebRTC consistency.
What BotBrowser supports and what stays outside it
The --bot-local-dns flag is documented as an ENT Tier1 feature. A bare flag or true resolves through the browser's built-in resolver, false turns it off so the proxy resolves names, and an IP or IP:port value sends lookups to that DNS server only, with port 53 as the default. An invalid value is treated as false. When a custom server returns no answer, BotBrowser does not fall back to the system resolver. For supported proxy connections, the documentation also states that dual-stack target selection stays aligned with the active proxy route without another flag.
BotBrowser documents --bot-local-dns (ENT Tier1) as a built-in DNS resolver that keeps name resolution independent of the proxy provider's DNS behavior, and it routes DNS prefetch queries through the same proxy path, so a team can run a repeatable DNS policy and verify it with a leak test. BotBrowser cannot control or guarantee the proxy provider's upstream DNS, the DNS servers or logs outside the browser, or how a leak test is interpreted, and it does not replace choosing SOCKS5H or a trustworthy provider.
In practice that means two separate jobs. The browser side is configurable and repeatable, and the provider side is a decision about whom to trust that no flag can make for you. Write the chosen scheme, the local DNS setting, the expected resolver operator, and the expected region next to each configuration, so that a later change shows up as a difference against a recorded expectation. Check the DNS leak prevention documentation for the current value semantics before changing a production configuration.
Sources
Related Articles
Take BotBrowser from research to production
The guides cover the model first, then move into cross-platform validation, isolated contexts, and scale-ready browser deployment.