Anyone clicking into this article has probably already heard of the concept of DNS leaks, and plenty of people go pale at the mere mention of it. Since several people have raised related questions in the comments and issues, I’m writing this explainer to try to lay the problem out as clearly as possible, clear up some common misunderstandings about the concept, and give everyone a reference point for writing their own proxy configurations.
This concept has already been done to death by various VPN vendors and a whole pile of YouTubers. The basic idea is: while you’re using a proxy, your computer instead sends a DNS request over the local network to your local DNS server for the address you’re actually visiting, which lets your ISP work out your browsing history from its DNS query logs.
Getting clear on what we actually need
The first thing worth thinking through properly is what we’re actually trying to achieve by going to such lengths to prevent DNS leaks. Most people’s gut reaction is probably: “Obviously, so the censors can’t see which sites we’re visiting through the proxy!”
That’s not entirely accurate, actually, and it depends on the situation. Think about it for a moment: would there be any real harm in the censors knowing you’re scrolling through Douyin1 or chatting on WeChat? Probably not. But if they find out you’re browsing Wikipedia, checking X (Twitter), or looking at other content with a clear political slant that’s flagged as sensitive, then you’ve got a real problem. The point of preventing DNS leaks was never to score a full row of green ticks on some so-called “DNS leak test” website, nor to hide the fact that you use commonly-used domestic services. It’s to stop the censors from finding out that you’re accessing blocked sites and services that are considered sensitive.
For those of us living behind the Wall, working out what counts as sensitive content is actually pretty simple: just check the GFWList23 directly. Whatever the Great Firewall (GFW) specifically bothers to block is, by definition, sensitive content.
The random domains used by these DNS leak test sites don’t even come close to touching the GFWList; they carry zero sensitivity to begin with. With a well-optimised proxy configuration, that kind of domain doesn’t get a second glance, it’s typically skipped straight through to the final catch-all rule (something like MATCH, DIRECT), and that process naturally triggers a local DNS resolution. So the test site lights up red as if it’s struck gold, warning you that “a DNS leak exists”. But once you understand the actual mechanism, it’s obvious that this so-called “leak” is often completely inconsequential. As long as your configuration is sound, the sensitive access history you actually need to protect has already matched an earlier routing rule and been safely sent down the encrypted tunnel.
What’s worse, commercial overseas VPN vendors like Surfshark and ExpressVPN, along with a bunch of unscrupulous YouTubers cashing in on sponsorships, keep using these utterly meaningless test results to whip up privacy anxiety. The ironic part is that these traditional commercial VPN clients often don’t even have the most basic domain-based routing rule functionality, and instead crudely shove all network traffic through the virtual network adapter in one go. Even more ironic still: even with that crude blanket-proxy approach, they’ve genuinely caused real DNS leaks in the past due to flaws in their own client software. So rather than taking these anxiety-peddling marketing pitches at face value, you’re better off understanding how proxy software actually works.
Where leaks actually happen
Mainstream proxy software these days is quite mature, and with a properly set-up configuration there’s really nothing to worry about regarding DNS leaks. The problem is that a lot of configurations simply aren’t set up properly. Next I’ll briefly explain where DNS leaks can occur in proxy software under system proxy mode and transparent proxy (virtual network adapter) mode.
The proxy software itself
When using a system proxy (say the proxy address is socks5://127.0.0.1:78904) and visiting https://www.example.com in a browser, the request goes through the following process:
- Step one: the browser packages up the network request and sends it to
socks5://127.0.0.1:7890 - Step two: the proxy software receives the request and matches it against a node using the configured routing rules
- Step three: the proxy software forwards the entire network request to the matched node
Throughout the whole request process, the browser hands the network request off to the proxy without sending a request to the local DNS, so no leak occurs at this stage. The proxy software forwards the network request to the server, and that whole process is encrypted, so no leak occurs there either. The stage where the actual DNS leak occurs is during routing rule matching.
Issuing a domain lookup to local DNS isn’t necessarily a bad thing in itself, but a poorly configured routing rule can make the proxy software prematurely query local DNS the moment it gets the domain, leaking privacy information that shouldn’t have been leaked. Suppose, in some extreme hypothetical scenario, the Gestapo5 pulled everyone’s DNS query logs from the ISP, and anyone who’d queried a domain on the GFW’s blocklist got taken out and shot. In that kind of scenario, to protect ourselves, we’d need to configure our routing rules properly.
Take the widely-used Mihomo core as an example: when it’s acting as a client in system proxy mode, and Chrome is at the second step described above, the information the core already has on hand is:
- Host6:
example.com - Target port: 443
- Network protocol: TCP
- Name and full path of the requesting process:
C:\Application\chrome.exe - Inbound IP:
127.0.0.1 - Inbound name:
DEFAULT-MIXED - Inbound port: a random high port
The core doesn’t need to make any extra DNS requests. Based on the information it already has, it can match all routing rules based on domain, port, process name, and network protocol without triggering any DNS resolution at all:
| |
So none of the rule types above cause DNS leaks, because the core has no need to trigger DNS resolution to match against them.
For the remaining rule types, the target IP information needs to be known at step two before matching can proceed:
| |
By default, to ensure matching accuracy, when the core reaches step two it doesn’t know the target IP, only the target host address www.example.com, so it has no choice but to look up the IP address for www.example.com, and that’s what causes the DNS leak. If you append no-resolve7 to the end of the rule, matching skips straight over the IP-based rule entirely, which makes no-resolve well suited to routing bare-IP requests from software like Telegram.
The problem with transparent proxying (TUN mode8)
Transparent proxying is, as the name suggests, a way of proxying where the system/software being proxied has no idea it’s being proxied at all. This approach is very convenient. Flip on TUN mode and everything just works, no need to laboriously configure every single piece of software that doesn’t respect the system proxy setting one by one. The cost is that very convenience: since the target system/software has no idea it’s being proxied, it carries on behaving exactly as it would on a normal network, and that naturally includes DNS resolution. But the target system/software doesn’t know it’s being proxied, and it still needs to get hold of an IP before it can establish a connection to the remote end. To solve this, Mihomo, for example, offers two DNS hijacking modes: redir-host and fake-ip.
redir-host mode:
fake-ip mode:
redir-host is the honest one of the two: since the target system/software issued a DNS query, it dutifully goes off and queries upstream DNS, and returns the real IP it gets back to the target system/software. Because this mode is bound to trigger a genuine DNS resolution lookup, it carries an inherent structural flaw that inevitably causes DNS leaks. The original Clash core actually dropped redir-host mode entirely in a later version.
That said, Mihomo, which inherited from the Clash Meta core, chose to keep it around, and bolstered it with sniffer9. Under redir-host mode, sniffer inspects features within the data packets (such as the SNI field10 in TLS) to try to recover the connected domain from traffic that’s already been resolved down to a bare IP, then runs that recovered domain back through the routing rules for matching, which patches up many of redir-host mode’s shortcomings when it comes to routing. But it’s worth being very clear on this point: sniffer solves the problem of routing accuracy, it does not eliminate the DNS leak itself. The DNS query has already gone out before sniffer even gets involved; whatever the ISP was going to log, it’s already logged.
One thing worth flagging here: if you still insist on using redir-host mode in a transparent proxy setup, you must set up a set of uncontaminated DNS servers. See my other article for a concrete example, otherwise your DNS access history will still leak.
By contrast, fake-ip mode takes an entirely different approach to solving this, and not only does it neatly sidestep the DNS leak problem under transparent proxying, it can even improve browser page load responsiveness as a side benefit.
How to avoid DNS leaks
Optimise the routing rules fed to the proxy software
As mentioned earlier, poorly designed routing rules cause the proxy software to trigger resolution prematurely. Here’s a classic example of what not to do:
| |
The problem is in the first line, GEOIP,cn,DIRECT. When the device requests the domain telegram.org, the core matches from top to bottom, and the very first entry is an IP rule. But a domain isn’t an IP, so to work out whether it falls within a mainland Chinese IP range, the core has no choice but to resolve telegram.org first. And that resolution is exactly when the DNS query goes out, even though the second rule down, GEOSITE,telegram, could have matched directly without needing to know the IP at all.
Once the resolution request goes out, the ISP has already logged it. Whichever rule it ends up matching, or whether it goes through the proxy at all after that, no longer matters, the ISP already knows you’re accessing Telegram.
Rule matching proceeds top to bottom in order: every time the core receives a request, it checks each rule one by one until it finds a match. In other words, once a domain matches an earlier rule, none of the rules after it, whether it’s the second, the third, or even the ten-millionth, ever get a chance to run.
That’s exactly why, when writing rules, you should follow the principle of domain rules first, IP rules last. Even for a dedicated rule set targeting a specific service, any IP rules placed near the front should be tagged with no-resolve to tell the core to skip that rule if it doesn’t match, rather than resolving to try it. A classic example:
| |
The same logic applies when using GeoSite and GeoIP databases: any IP rule near the front should carry no-resolve.
| |
Compare this rule set against the earlier bad example, and the difference comes down to exactly when DNS resolution gets triggered:
Same request for telegram.org, but this time the very first rule is GEOSITE,telegram, the domain matches directly, the request gets handed to the proxy, and DNS never comes into it at all. Visiting other sites on the GFW’s blocklist works the same way, hitting GEOSITE,gfw,Proxy before ever reaching that non-negotiable GEOIP,cn,DIRECT rule, sending the request off to the node well before then. Anything that should go through the proxy already has, long before the IP-based rules further down ever get a chance to run, so DNS never gets an opportunity to leak.
Using fake-ip to sidestep the transparent proxy problem
fake-ip mode solves the DNS leak problem under transparent proxying, and also improves browser page load responsiveness as a side benefit.
When using transparent proxying in fake-ip mode, the moment the target software issues a DNS query, the proxy software doesn’t naively go off and query the real IP from a local or remote upstream server. Instead, it immediately returns a fake reserved private-range IP locally on the spot (for example, 198.18.0.211). The target software takes this fake IP at face value and promptly tries to establish a connection using it.
On the proxy software’s side, all it needs to do is intercept every request heading to one of the fake IPs it’s issued. Because the proxy software maintains a detailed mapping table of “fake IP to real domain” internally, even when the target software sends packets addressed to a fake IP, the proxy software still knows perfectly well what domain you’re actually trying to reach. So it extracts the original domain and forwards the network request down the encrypted proxy tunnel to the remote node for processing.
This mechanism solves the structural flaw whereby redir-host mode is bound to expose domain query records to the outside network under transparent proxying. But it’s worth pointing out that fake-ip only bypasses the leak risk that arises when the target software itself issues a DNS query. As mentioned earlier, if the routing rules are poorly configured, the proxy core itself will still proactively issue a genuine DNS resolution upstream, and that will still cause a leak. Given properly configured routing rules, though, fake-ip mode not only leaves censors with no way to peek at your real destinations, it also cuts out the time the local device would otherwise spend waiting for a real DNS response, substantially reducing Time To First Byte (TTFB12) for page loads as a bonus. Two birds, one stone.
TL;DR (summary)
A DNS leak itself isn’t something to fear. What’s actually worth worrying about is being led around by the nose by anxiety-driven marketing without understanding what’s really going on. Once you understand the mechanics, the way forward is quite clear:
- System proxy mode: follow the principle of “domain rules first, IP rules last”, tag every IP-type rule with
no-resolve, and sensitive domains will already have been routed away before they ever trigger DNS resolution. - TUN transparent proxy mode: favour
fake-ip, which eliminates DNS query exposure at the root and also gets you lower TTFB as a bonus. If you insist onredir-host, you must pair it with uncontaminated upstream DNS, and be clear that sniffer can only improve routing accuracy, it can’t prevent the DNS request itself from leaking. - Don’t put blind faith in DNS leak test sites: the random domains they use aren’t on the GFW’s blocklist at all, so triggering local resolution for them is normal and harmless behaviour. A row of green ticks isn’t a measure of configuration quality.
Translator’s note: Douyin is ByteDance’s domestic Chinese short-video app, distinct from TikTok (also ByteDance-owned) which serves international markets. The two are separate apps with separate content and user bases. ↩︎
Translator’s note: GFWList is a publicly maintained list of domains blocked by China’s Great Firewall, commonly used by proxy software to determine which sites need routing through a proxy. ↩︎
A list of domains blocked by mainland China’s Great Firewall (GFW). ↩︎
SOCKS5 is a network proxy protocol (RFC 1928) that operates at the session layer, supports both TCP and UDP forwarding, and allows the client to pass the domain name straight to the proxy server for resolution, rather than resolving it locally first and then connecting. This is precisely the key feature that makes it better than a plain HTTP proxy (outside of CONNECT tunnel mode) at preventing DNS leaks. ↩︎
Translator’s note: this is a deliberately absurd, over-the-top hypothetical for comic effect, not a genuine comparison; the author is mocking VPN marketing fearmongering by exaggerating the stakes to an extreme. ↩︎
You can think of this as the domain name. ↩︎
no-resolveis a flag appended to the end of an IP-type rule. Its job is to tell the core: when the traffic’s target is a domain rather than an IP, skip this rule rather than proactively triggering DNS resolution just to try to match it. But if the traffic itself is a direct connection to an IP (such as a Telegram client connecting straight to a server IP), the rule will still participate in matching normally. ↩︎What we normally call virtual network adapter mode. ↩︎
Traffic sniffing (Sniffer) refers to a technique where proxy software inspects the protocol characteristics of data packets to recover the target domain. For example, HTTPS connections send the target domain in plaintext during the handshake stage (i.e. the SNI), and HTTP request headers also carry a Host field; proxy software can extract the real target domain from these characteristics. ↩︎
Server Name Indication, an extension field in the TLS protocol. Since a single IP might host multiple HTTPS sites, the client needs to state in plaintext during the ClientHello stage of the TLS handshake which domain it wants to reach, so the server can return the correct certificate. This is also why, even over HTTPS, a man-in-the-middle can still see which domain you’re visiting (though not the specific path or content). ↩︎
By default, Mihomo uses the
198.18.0.0/15address block to allocate fake IPs. This block is reserved by IANA under RFC 2544 specifically for network device benchmarking, and never appears in normal internet routing tables, so it never conflicts with any real public address. The full /15 block provides around 131,072 addresses for mapping. ↩︎Time To First Byte, the time elapsed between the client sending a request and receiving the first byte of the server’s response. Under traditional setups, the browser has to wait for DNS resolution to complete (typically 20-120ms) before it can even open a TCP connection; under
fake-ipmode, the DNS response is essentially instant (<1ms), which noticeably shortens overall page load time. ↩︎
Comments are disabled until you accept functional cookies.