# dnsleaktest: finding the resolver your VPN did not cover

> dnsleaktest is a small, dependency free tool that reports your external IP address and the DNS servers your connection is actually using, so a VPN that only routes some traffic can be caught out. It ships as a shell script, a Python script, a Windows batch file and Go binaries, and the interesting detail is the Docker invocation, where host networking is the difference between a valid test and a useless one.

**macvk/dnsleaktest** — An open source script tests VPN connection for DNS Leak.

- Repository: https://github.com/macvk/dnsleaktest
- Website: https://bash.ws/dnsleak
- Stars: 590 · Forks: 70
- Language: Batchfile
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/macvk-dnsleaktest

## A leak is a resolver you did not choose

The tool does one thing, and the README states it in two sentences. It reports your external IP address and the DNS servers used by your connection, and if their network providers differ unexpectedly, your DNS traffic may be leaking outside the intended connection or VPN.

That framing matters, because the failure it looks for is subtle. A VPN client that routes your traffic through a tunnel is not necessarily routing your DNS queries through it. If the resolver stays with your ISP, then every domain name you look up is visible to the network you were trying to leave, and the addresses you resolve can be correlated with the connections you make. The external IP and the resolvers together are the evidence, because a mismatch between the two is the leak.

The project is MIT licensed, the homepage is bash.ws/dnsleak, and the topic list is short and to the point: dns, dns-server, leak-detection, security and vpn. The repository itself is four files, dnsleaktest.bat, dnsleaktest.go, dnsleaktest.py and dnsleaktest.sh, plus a LICENSE, a README and the .github directory, which is the shape of a utility rather than a framework.

The audience is therefore broad but specific: anyone who has just paid for a VPN and wants to know whether it is doing what the vendor claimed, and support staff who need to reproduce a user's report on a machine they do not control.

One property is worth naming early, because it is a design decision rather than a limitation of your setup. The test works by sending your queries to an external service, and the service reports back which resolvers handled them. Your address is therefore visible to that service by construction. That is the price of an independent check, and it is also why the tool is a script you run yourself rather than something you embed in an application.

## Four routes to the same check

The README documents four ways to run the tool, and the differences between them are mostly about how you get the file rather than about what it does.

The shell version covers Linux and macOS, the Python version is a second implementation for the same platforms, and the batch file covers Windows. Beyond those, the project publishes prebuilt Go executables built by GitHub Actions, which is the route for anyone who would rather not run a script they did not write.

The shell route has a stated prerequisite, and it is the only dependency in the whole project. You need curl and ping installed before you use it, which the README asks you to check first.

The Python version has no such requirement listed, and it is the one the Docker instructions use, because a python:alpine image has everything it needs already. The batch file is downloaded with PowerShell rather than curl, since Windows has no curl by default on every version.

The Go executables are the widest set. The v1.4 release page carries builds for Linux on amd64, arm64 and ARMv7, for macOS on Intel and on Apple Silicon, and for Windows on amd64, arm64 and 32-bit, and the README explains that the macOS executables are unsigned, so macOS may ask you to confirm that you want to run a downloaded executable.

That last point is the kind of detail that saves a support ticket, and it is also the reason the shell and Python routes exist. A script you download, read and run is a different trust decision from a binary you run, and the project offers both.

## The shell route is three commands

Installing and running the shell version takes three steps, and the first one pins the version in the URL rather than tracking the default branch.

```sh
curl -fLO https://raw.githubusercontent.com/macvk/dnsleaktest/v1.4/dnsleaktest.sh
```

That fetches the file from the v1.4 tag, and the -f flag makes curl fail rather than save an error page.

The file then has to be made executable, which the README does as a separate step.

```sh
chmod +x dnsleaktest.sh
```

And then it runs.

```sh
./dnsleaktest.sh
```

The Python version follows exactly the same three step shape, with the .py file instead.

```sh
curl -fLO https://raw.githubusercontent.com/macvk/dnsleaktest/v1.4/dnsleaktest.py
```

```sh
chmod +x dnsleaktest.py
```

```sh
./dnsleaktest.py
```

The Windows route downloads the batch file with PowerShell, using the basic parsing flag, and then runs it.

```powershell
Invoke-WebRequest -UseBasicParsing https://raw.githubusercontent.com/macvk/dnsleaktest/v1.4/dnsleaktest.bat -OutFile dnsleaktest.bat
```

```bat
dnsleaktest.bat
```

Note what every URL in those commands has in common. Each one names the v1.4 tag explicitly, so a future change to the default branch cannot change what you run today. That is a deliberate design choice by the project, and it is the upgrade mechanism as well, since there is no package manager involved.

## Building the Go binary for your own target

If you would rather build the executable than download it, the README gives the environment variables and the command. Go is required, and the target operating system and architecture are selected with GOOS and GOARCH.

```sh
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o dnsleaktest-linux-amd64 dnsleaktest.go
GOOS=darwin GOARCH=arm64 CGO_ENABLED=0 go build -o dnsleaktest-darwin-arm64 dnsleaktest.go
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -o dnsleaktest-windows-amd64.exe dnsleaktest.go
```

CGO_ENABLED=0 is set on every line, which produces a binary that does not depend on the C library of the build machine. That is what makes the resulting file portable to a target that has a different libc, or none.

The three examples cover the three families of download in the release list, and the naming convention matches the released asset names exactly, so a locally built file and a downloaded one are interchangeable.

The prebuilt binaries are published on the v1.4 release page and are built by GitHub Actions, which means the artefacts come out of the same automated pipeline rather than a maintainer's laptop. The repository layout confirms it, with a .github directory alongside the four source files.

## Host networking is the whole point of the Docker command

The Docker instructions are short, and the difference between the two commands is the interesting part.

On Linux the container is run with the host network namespace.

```bash
docker run --rm --network host python:alpine sh -c 'wget -q -O- https://raw.githubusercontent.com/macvk/dnsleaktest/v1.4/dnsleaktest.py | python'
```

On macOS and Windows the host network flag is dropped.

```bash
docker run --rm python:alpine sh -c 'wget -q -O- https://raw.githubusercontent.com/macvk/dnsleaktest/v1.4/dnsleaktest.py | python'
```

The reason is the test itself. The whole point is to observe which resolvers the machine on your desk is using, and a container with its own network namespace has its own resolver path, so it would report the container's resolvers rather than yours. Sharing the host network is what makes the observation meaningful, and on the platforms where the container runtimes do not offer that mode, the containerized test cannot answer the question you asked it.

That is also why the tool is a script rather than a service, and why the documentation tells you to check for curl and ping before use. Everything about this project is arranged so that the observation happens from the machine you actually care about, and each packaging choice is judged against that.

One practical note: the command pipes the script straight into the interpreter, so nothing is written to disk and nothing is left behind to delete afterwards.

## What a clean result does and does not prove

The limitations are worth being precise about, because a green result is easy to over-read.

First, this is a point in time measurement. The tool runs once, and it reports the resolvers that answered at that moment. A VPN client that reconnects, or a network that falls back to the local resolver during a handover, will not be caught by a single run. The way to use it well is to run it twice, once without the VPN and once with it, and compare the two sets of resolvers. The difference between those two runs is the information, not either one on its own.

Second, it identifies resolvers, not applications. If a browser with its own built in resolver is configured to bypass the system settings, this test can report clean while the browser is still leaking. The tool cannot see inside individual applications, and nothing in the README claims it can.

Third, it is not a monitor. There is no daemon, no scheduled check and no alerting. The project is a script you run when you want an answer, which is the right shape for a diagnostic and the wrong shape for a control.

And fourth, there is a disclosure in the method itself. The check works by sending your queries to an external service that reports which resolvers handled them, so the service necessarily sees your queries and your address. For a privacy tool that is an uncomfortable trade, and it is worth knowing that the same service that answers the question also learns who asked.

The alternative approach to the same question is to inspect the resolver configuration directly, on the machine, and compare the configured resolvers with the ones in use, which keeps everything local but tells you about configuration rather than about observed traffic. The difference in approach is exactly that, and for a support reply to a user, the observed version is the one that answers the question they asked.

## Version pinning, the licence and the release history

The release history is short and irregular, which is the main thing to know before you rely on this project.

The tags are v1.4 from 2026-09-15, v1.3 from 2021-06-08 and v1.2 from 2018-11-03. So the current release line is a single 2026 tag sitting on top of a 2021 version and a 2018 one. The last push was on 2026-10-18 and the repository is not archived, so the project is still being touched, but the version history is not the steady stream of tags you would expect from a package with a package manager.

That is why every download URL in the README names the tag, and why there is no install command beyond curl and chmod. The tag in the URL is the pin, and upgrading means changing the tag. For a script that talks to a third party service, that is a reasonable trade, since the thing that could change under you is the service rather than the code.

The licence is MIT, with a LICENSE file at the top of the repository, which permits use, modification, distribution and commercial reuse provided the copyright notice travels with the code. The code itself is the Go implementation, the Python port, the shell script and the batch file, and all four are covered by the same terms.

The practical checklist for an adopter is short. Pick the route that matches your platform and your trust model, keep the version tag in the URL, and remember that the test is only meaningful on a machine whose network path is the one you care about.

## Conclusion

dnsleaktest fits a user or support engineer who needs a one shot confirmation that a VPN is carrying DNS traffic, and it does not fit a security monitoring setup, since it is a single check rather than a continuous detector, and it reveals your address to an external service. Before trusting a clean result, run it once with the VPN disconnected and once with it connected, and compare the reported resolvers, because a single green run only tells you about that moment.

## FAQ

### What does dnsleaktest actually check?

It reports your external IP address and the DNS servers used by your connection. If the network providers behind those resolvers differ unexpectedly, the README says your DNS traffic may be leaking outside the intended connection or VPN.

### How do I run dnsleaktest on Linux or macOS?

Make sure curl and ping are installed, then download the shell script from the v1.4 tag, make it executable with chmod +x and run it with ./dnsleaktest.sh. A Python version is available with the same three steps, and Windows has a batch file downloaded with PowerShell.

### Why does the Docker command use host networking on Linux?

The test has to observe the resolvers used by the machine you care about, and a container with its own network namespace would report the container's resolvers instead. The README therefore uses --network host on Linux and drops that flag on macOS and Windows.

### Which platforms have prebuilt dnsleaktest binaries?

The v1.4 release page publishes Linux builds for amd64, arm64 and ARMv7, macOS builds for Intel and Apple Silicon, and Windows builds for amd64, arm64 and 32-bit. The README notes that the macOS executables are unsigned, so macOS may ask you to confirm running them.

### Can I build dnsleaktest myself?

Yes, with Go installed, by setting GOOS and GOARCH and disabling cgo, for example GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o dnsleaktest-linux-amd64 dnsleaktest.go. The naming matches the released asset names, so a local build and a download are interchangeable.

## Sources

- [License: MIT](https://github.com/macvk/dnsleaktest/blob/master/LICENSE)
- [macvk/dnsleaktest on GitHub](https://github.com/macvk/dnsleaktest)
- [Project website](https://bash.ws/dnsleak)
- [README](https://github.com/macvk/dnsleaktest/blob/master/README.md)
- [Releases](https://github.com/macvk/dnsleaktest/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/macvk-dnsleaktest
