# How to check if DNS goes through a proxy: read the CONNECT

> To check if DNS goes through your proxy, read what the proxy is asked for. CONNECT example.com:443 means it resolved the name; a raw IP means you did.

Source: https://mosaicbrowser.com/blog/a-numeric-connect-means-dns-leaked

## A CONNECT to a raw IP means your machine resolved the name

To check if DNS goes through your proxy, read what the proxy is asked for. CONNECT example.com:443 means it resolved the name; a raw IP means you did.

![Asked for a name, never for an address, beside a screenshot: routing Mosaic launches through Proxyman, Charles or mitmproxy to read their traffic](https://mosaicbrowser.com/covers/a-numeric-connect-means-dns-leaked.webp)

To check if DNS goes through your proxy, look at what the proxy is asked for. A browser using an HTTP proxy for an HTTPS site sends `CONNECT example.com:443`. If the proxy receives the name, the proxy did the lookup. If it receives `CONNECT 93.184.216.34:443`, your machine looked the name up first, through its own resolver, and the proxy only ever saw the answer. That one line in the proxy's log settles it.

We built our proxy check around that line, and the reason is a habit browsers have. A browser that is going around its proxy will cheerfully tell you it is not.

## Why the browser cannot be the witness

Most DNS checks ask the browser. A leak test page triggers a few lookups for random subdomains and reports which resolver fetched them. That is useful, and it is also limited. It sees only the lookups that page caused. It cannot see the ones Chrome made a second earlier for a prefetch, a preconnect or a restored tab ([DNS prefetching](https://mosaicbrowser.com/blog/dns-prefetch-and-proxies) is the usual one). And anything read from inside the page is reported by the same software you are trying to test.

The proxy is a better witness because it is the other end of the wire. It records what actually reached it and nothing else. If a lookup went out to the ISP's resolver, the proxy cannot be persuaded that it did not. And if the browser resolved the name and then connected through the proxy by address, the evidence sits in the request itself.

Mosaic is a desktop app

Email yourself the link and open it on your computer.

[How it works: Provider minting](https://mosaicbrowser.com/features/proxy-providers)

## What our check does

`scripts/check-proxy.mjs` stands up a real HTTP proxy of our own on the loopback address, points a fresh profile at it through Mosaic's API, opens `https://example.com/` and waits nine seconds. Then it asks the proxy what it saw. Every assertion is made against the proxy's own record, never against anything the browser reports.

1. The proxy saw at least one connection. If it saw none, the browser is not using it at all.
2. One of those connections asked for `example.com`. Traffic to some other host does not prove the page went through.
3. No CONNECT target is a raw address. The check matches every target against a dotted-quad pattern, and a single hit fails the run with the line "The browser resolved the hostname itself, which means the machine's own resolver saw the site."
4. With credentials configured, the proxy received a `Proxy-Authorization` header and it decoded to the configured user and password.

It runs that twice, once with no credentials and once with a user and password the proxy insists on. An authenticated proxy goes through Mosaic's local relay, which adds the credentials on the way out, and a broken relay is exactly the kind of fault that fails open into "no proxy at all".

The test proxy really forwards traffic, which matters. A proxy that refused every connection would make the page fail to load, and "nothing reached the origin" looks the same whether the proxy worked or the browser gave up. A real tunnel is the only way the result means something.

A third scenario points a profile at a proxy port where nothing is listening, port 47119, and checks that the page did not load. That is the dangerous failure: a browser that quietly falls back to a direct connection behind a dead proxy looks, from the outside, exactly like everything working.

One detail from building it. The first version hung for minutes at the end, with no browser running and nothing on screen. `server.close()` waits for open connections to finish, and a CONNECT tunnel to a browser never finishes on its own. The check now tracks every socket and destroys them on the way out.

## Why the address never appears

A check like this needs something to hold it true, and in Mosaic that is the launch itself. With a proxy attached and `dnsOverProxy` on, which is the default, the browser is pointed at the local relay rather than at the proxy itself, and launched with:

```
--host-resolver-rules=MAP * ~NOTFOUND , EXCLUDE 127.0.0.1

```

Every name the browser tries to resolve for itself now fails, except the loopback address the relay listens on. So the browser has no choice but to hand the name to the relay, and the relay resolves it upstream, at the exit. A part of Chrome we forgot about, which tries to resolve something directly, gets "not found" rather than an answer from the ISP. The rule fails closed. The order inside that rule matters more than it looks, and [the first match wins](https://mosaicbrowser.com/blog/host-resolver-rules-first-match-wins) has the case where it broke our own companion.

A SOCKS4 proxy always goes through the relay too, because SOCKS4 has no field for a hostname. A browser using one directly has to resolve every name itself.

## How to check if DNS goes through your proxy yourself

You do not need our script to make the same measurement. Run any proxy whose log shows the request line, point the browser at it and read the targets.

For an HTTP proxy, four lines of Node print every CONNECT target:

```
require('http').createServer().on('connect', (req, sock) => {
  console.log(req.url)
  sock.end('HTTP/1.1 502 Bad Gateway\r\n\r\n')
}).listen(8899)

```

Start Chrome with `--proxy-server=http://127.0.0.1:8899` and a fresh `--user-data-dir`, open an HTTPS site, and every line printed should be `name:443`. This version refuses the tunnel, so pages will not load, which is fine for reading targets but means it cannot test fallback; add forwarding before you trust a "nothing loaded" result. mitmproxy and Proxyman show the same request line in their flow lists.

For SOCKS5 the question is the address type byte in the connect request: `0x03` is a domain name, `0x01` an IPv4 address and `0x04` an IPv6 address. Chrome sends the name to a SOCKS5 proxy. Many command-line tools do not unless the scheme is `socks5h`, which [socks5 and socks5h in Chrome](https://mosaicbrowser.com/blog/socks5-and-socks5h-in-chrome) explains.

If you would rather watch from the machine's side, `sudo tcpdump -n port 53` while you browse shows every plain DNS query leaving the computer. With the proxy doing the resolving, the browser should cause none. Encrypted DNS hides from that view, so a quiet port 53 is necessary but not sufficient. The CONNECT line in the proxy's log is still the clearest answer, and checking the exit's own answers before you use it is in [how to check a proxy](https://mosaicbrowser.com/guides/how-to-check-a-proxy-before-you-use-it).

## Questions

### How do I know if my DNS goes through my proxy?

Look at the requests the proxy receives. An HTTP proxy asked for CONNECT example.com:443 did the lookup itself. One asked for CONNECT 93.184.216.34:443 was handed an address your machine had already looked up.

### Is a DNS leak test website enough?

It shows which resolver answered the lookups that page caused. It cannot show the lookups the browser made before or around it, and it relies on the browser being honest about its own route.

### Does SOCKS5 send DNS through the proxy?

It can. A SOCKS5 connect request carries either a domain name or an address, marked by its address type byte. Chrome sends the name for socks5 proxies; many command-line tools resolve locally unless you use socks5h.

### Why test at the proxy instead of in the browser?

Because a browser that is going around its proxy will still report that it is using it. The proxy only records what actually reached it, so a check made there cannot be talked out of the answer.

## Related

[DNS prefetching, the lookup that skips your proxy](https://mosaicbrowser.com/blog/dns-prefetch-and-proxies): Chrome's DNS prefetcher resolves names through your own resolver even with a proxy set. Mapping every name to NOTFOUND makes the leak fail closed.

[--host-resolver-rules: the first match wins](https://mosaicbrowser.com/blog/host-resolver-rules-first-match-wins): Chrome's --host-resolver-rules tries MAP rules in order and stops at the first match. EXCLUDE is not ordered. A wildcard put first ate our extension's API call.

[socks5 and socks5h: where Chrome resolves the name](https://mosaicbrowser.com/blog/socks5-and-socks5h-in-chrome): In curl, socks5h sends the hostname to the proxy and socks5 resolves it locally. Chrome's socks5 already sends the name; the leak is elsewhere.

[How to check a proxy before you use it](https://mosaicbrowser.com/guides/how-to-check-a-proxy-before-you-use-it): Check that a proxy answers and tunnels, where its exit really is, whether the address is residential or blocklisted, and that the browser stays behind it.

[A green tick that means the socket opened is worthless](https://mosaicbrowser.com/blog/proxy-checker-says-working): A proxy checker can pass while sites fail, because most test plain HTTP and the browser needs a CONNECT tunnel. Exit and stability are separate answers too.

[A proxy that answers HTTP and refuses CONNECT](https://mosaicbrowser.com/blog/proxy-http-works-https-fails): A proxy that works for HTTP but not HTTPS is refusing CONNECT, the tunnel every HTTPS page needs. A check that fetches a plain-HTTP page cannot see it.

[Our proxy relay told the gateway it was a script](https://mosaicbrowser.com/blog/the-relay-that-looked-like-a-script): Chrome sends Proxy-Connection: keep-alive, lower case, to every proxy. Our relay sent Keep-Alive, no User-Agent and Connection: close, until we copied Chrome.

[Chrome opens connections before your test starts](https://mosaicbrowser.com/blog/chrome-preconnects-before-you-measure): We counted 1, 0 and 0 CONNECTs in three launches and thought Chrome was bypassing the proxy for HTTPS. Our counter was reset after Chrome had started.

## Mosaic, on your own computer

The browser this blog is about: its own Chromium, fingerprints from real machines and a proxy exit per profile. Free holds 3 profiles. Pro is $49 a month for any number.

Mosaic is a desktop app

Email yourself the link and open it on your computer.
