# WireHole: a self-hosted WireGuard VPN with Pi-hole filtering and Unbound DNS

> WireHole bundles WireGuard, Pi-hole and Unbound in one docker-compose stack so a single Linux box becomes your VPN server, ad blocker and recursive resolver. The setup is short, but the host requirements and the 2026 layout change are where adopters get stuck.

**IAmStoxe/wirehole** — WireHole is a combination of WireGuard, Pi-hole, and Unbound in a docker-compose project with the intent of enabling users to quickly and easily create a personally managed full or split-tunnel WireGuard VPN with ad blocking capabilities thanks to Pi-hole, and DNS caching, additional privacy options, and upstream providers via Unbound.

- Repository: https://github.com/IAmStoxe/wirehole
- Website: https://iamstoxe.com
- Stars: 4,971 · Forks: 344
- Language: Shell
- License: NOASSERTION
- Published: 2026-09-23 · Updated: 2026-09-23 · Language: en
- Canonical page: https://hysenlabs.com/projects/iamstoxe-wirehole

## The problem WireHole solves, and who feels it

Running a personal VPN is normally three separate problems. You need a tunnel, you need DNS that does not leak to a public resolver, and you need something to strip ad and tracker domains before they resolve. Each piece has its own container, its own config volume and its own way of pointing at the next one. WireHole exists to collapse that into a single docker-compose project with one .env file and a defined chain: the client's DNS queries go to Pi-hole, Pi-hole blocks the advertisement domains, Pi-hole forwards the rest to Unbound, and Unbound asks the authoritative name servers directly and validates the answer with DNSSEC.

The audience is narrow and specific. You need a Linux machine that stays on: the README lists a Raspberry Pi model 3 or later, an old desktop, a home server or a rented server. You need Docker and git. You need one reachable address for clients, either a public IP or a domain name, and one open UDP port on your router. If you have those four things, the README puts the setup at about 20 minutes. If you do not, no amount of documentation will help, because the constraints are structural rather than procedural.

The README also positions the stack against a public VPN provider. The point of running WireGuard yourself is that your traffic does not pass through someone else's service, and the point of Unbound is that your queries do not go to Google or Cloudflare. Those two sentences describe a trade rather than a feature list: you take on the operation of the box in exchange for not depending on a third party for either the tunnel or the resolution.

## How the tunnel, the filter and the resolver chain together

The architecture is a straight line, and the README draws it that way: WireGuard client, then VPN server, then Pi-hole, then Unbound, then the root servers. The private Docker network is named wirehole and defaults to the subnet 10.2.0.0/24, overridable with WIREHOLE_SUBNET. The DNS services take static addresses on that network, and the VPN clients are configured to use those addresses. That detail matters: it is why the filtering works for every connected device without per-device DNS settings.

The compose file carries two VPN back ends behind Docker Compose profiles, selected with COMPOSE_PROFILES in .env. The wg-easy profile is the default and gives a VPN server with a web interface. The wireguard profile gives a server with configuration files and QR codes only, no web UI. Pi-hole and Unbound sit outside the profiles, so they always start regardless of which back end you pick. That split is a deliberate design choice: the DNS half of the stack is not optional, only the way you manage the tunnel is.

Shared settings such as the restart policy and log rotation are defined once in an x-common anchor and merged into services with the << key, with LOG_MAX_SIZE defaulting to 10m and LOG_MAX_FILE to 3. Docker Compose copies that block into each service, which caps how much disk the container logs can consume. RESTART_POLICY defaults to unless-stopped. The logging driver is json-file. None of these settings change the filtering behaviour; they exist so a long-running stack does not fill the disk or die silently after a reboot.

The stack also supports full tunnel and split tunnel modes, and the README gives them their own section. Full tunnel sends all traffic through the server; split tunnel sends only what you select. The DNS chain is the same in both cases, which is worth noting because a split tunnel that still routes DNS through Pi-hole gives you the blocking without the bandwidth cost.

## Installing WireHole and getting a first client connected

The quick start is five steps, and the README is explicit that step 4 takes a minute because Pi-hole must be ready before the VPN starts.

```bash
git clone https://github.com/IAmStoxe/wirehole.git
cd wirehole
./scripts/generate-secrets.sh
nano .env
docker compose up -d
docker compose ps
```

The secrets script writes strong passwords into .env and also detects your public IP address. The README says to open .env afterwards and check the value of VPN_HOST, setting it to your public IP or your domain name. The script prints the new passwords, and the README tells you to store them in a password manager at that point because you need them in the next step.

If you would rather not use the script, the README gives the manual path: copy .env.example to .env and edit it.

```bash
cp .env.example .env
nano .env
```

Three variables are marked REQUIRED in that file. VPN_HOST is the public address clients connect to. PIHOLE_PASSWORD is the Pi-hole web interface password. WG_EASY_PASSWORD is the VPN web interface password, and the file notes that you must set it even when you choose the wireguard profile, because Docker Compose reads every service in the file and the stack will not start without it. That is a real trap for anyone who assumes unused profile settings are optional.

The .env.example file also documents how to produce a strong password by hand, using openssl rand -base64 24, and how to find your public address with curl -s ifconfig.me. If your address changes, the README has a section on keeping the stack working, and it suggests using your dynamic DNS name as the value of VPN_HOST.

After the stack is up, docker compose ps should show three containers. Pi-hole and the VPN report Up (healthy). Unbound reports only Up, with no health status. The README explains this is normal: the Unbound image is small enough that it has no shell in which to run a health check. Do not read that missing status as a failure, and do not restart the container chasing it.

The README notes that the web interfaces are closed to the network on purpose after startup, and that a separate section explains how to open them. That is a deliberate default rather than an oversight: an exposed Pi-hole admin panel on a public address is a login page anyone can reach, and the project makes you take a step before that happens.

## The host requirements rule out more machines than you expect

This is the section to read before you buy anything. A VPN needs Linux kernel features that Docker Desktop does not provide, so a Mac or a Windows PC cannot host the stack even with Docker installed. The README is clear that those machines make fine clients and poor servers. WSL2 on Windows is also called out as a bad host: it puts Linux behind a private network, so other devices cannot reach the VPN port. Windows can forward TCP ports into WSL2, but WireGuard uses UDP and the Windows port forwarding tool does not forward UDP. WSL2 is described as usable for development and for this project's tests, not as a real server.

The second constraint is availability rather than capability. The server stays on all the time. When it is off, any device with the VPN switched on has no internet at all until the VPN is switched off again. That is the cost of routing DNS and traffic through your own box, and it is worth stating plainly because it changes how you think about a home-lab power cut. A device that depends on your server for name resolution fails closed, not open.

The third constraint is network access. The router must forward UDP port 51820 to your server. The README calls this the step that stops most people, which is why it gets its own section, and notes you can skip it if your server is a rented host with a public address. If you cannot open that port, the rest of the stack is irrelevant.

There is also a prerequisite around Docker itself. The README requires Docker 20.10 or later and Docker Compose v2 or later, checked with docker --version and docker compose version. The quick installer it shows adds your user to the docker group, and the README warns that this gives your account root-level power on that machine, so it should be used only on a machine you control. It also notes that you must log out and back in for the group change to apply, and that skipping this produces a permission denied error when connecting to the Docker daemon socket.

## The 2026 rewrite and what upgrading actually costs

The README opens with a warning: the 2026 rewrite changed the layout, and you should not simply pull and restart. UPGRADING.md is the document to read, and the README says one script keeps your devices working. That is a maintenance cost worth naming, because it is the kind of change that breaks a working setup if you treat the repository as a passive artifact. A compose stack that quietly reorganises its services will not tell you it has done so; the containers will simply fail to come up in the shape you expect.

On the routine side, the stack gives you knobs for the things that usually fill a disk or a log file, and the README points to an operation section covering logs, updates and backups. That is where the upgrade procedure lives. I would treat UPGRADING.md as required reading on every major pull, not only on the first one, because the warning is written for people who have already been running the older layout.

The licence is reported as NOASSERTION, which means the repository does not declare a standard SPDX identifier in the metadata I can see. That is not a statement about your rights; it means you should read the LICENSE file in the repository root yourself before you build anything commercial on top of it. The repository also ships a SECURITY.md and a CONTRIBUTING.md, which is a reasonable sign that the project has thought about how problems and patches arrive, though neither tells you anything about the licence terms. I am not giving legal advice, and this is exactly the kind of question where the file, not a summary, is the source.

## Where WireHole is the wrong tool

If you want a VPN without owning the server, WireHole is not that. The README frames the whole point as traffic that does not go through a public VPN provider, which means you are the provider. You inherit the uptime, the port forwarding, the dynamic address problem and the patching.

If your goal is only ad blocking, the VPN half is overhead. Pi-hole and Unbound start in every configuration, so you could run just those, but then you are maintaining a compose project whose distinguishing feature you are not using, and a simpler Pi-hole deployment would do. The related searches around WireHole versus Pi-hole point at the same question, and the honest answer is that WireHole is Pi-hole plus a tunnel plus a recursive resolver, not a different kind of blocker.

If you need a web interface for the VPN, note that it depends on which profile you chose. The wireguard profile is described as configuration files and QR codes only. Someone who picks it expecting the wg-easy panel will be disappointed, and the panel is not something you can bolt on afterwards without changing COMPOSE_PROFILES and restarting the stack.

There is a subtler failure mode in the dependency order. The README says step 4 takes a minute because Pi-hole has to be ready before the VPN starts, and that you should let it finish. A stack brought up and immediately inspected can show a VPN container that has not yet settled, which looks like a fault and is not one. The same applies to a container that restarts repeatedly: the README routes that to its troubleshooting section rather than treating it as expected.

## Alternatives and how their approach differs

The obvious alternative is running Pi-hole on its own and pairing it with a separate VPN. That keeps the two concerns independent: you can restart the DNS container without touching the tunnel, and you can replace either half without reading an upgrade guide for the combination. The cost is that you wire the DNS addresses into the VPN client configuration yourself, which is precisely the manual step WireHole removes by putting Pi-hole and Unbound on static addresses inside the wirehole network.

A second alternative is Unbound alone, without Pi-hole. Unbound already caches and validates, and the README lists DNS caching, additional privacy options and upstream providers as its contributions to the stack. What you lose is the blocking layer that sits in front of it. If your reason for adopting WireHole was the advertisement and tracker filtering, dropping Pi-hole removes the feature you came for.

A third path is a hosted VPN with a hosted DNS filter. You get none of the host requirements and none of the port forwarding, and you give up the property the README leads with: your traffic does not go through a public VPN provider. That trade is the whole decision, and it is a trade about who holds the box, not about which software is better.

The difference between WireHole and plain Pi-hole is worth stating precisely, because the two get compared often. Pi-hole filters DNS. WireHole filters DNS and also carries your traffic, with the DNS path pinned to static addresses inside a private Docker network. If you already have a tunnel you trust, adding Pi-hole to it is less work than replacing the tunnel with WireHole.

## What to check before you commit

Confirm you have an always-on Linux host that is not a Mac, a Windows PC or a WSL2 instance. Confirm you can forward UDP port 51820 from your router, or that your server already has a public address. Check docker --version and docker compose version against the 20.10 and v2 minimums before you start, and log out and back in after adding your user to the docker group.

Run ./scripts/generate-secrets.sh rather than hand-writing passwords, then open .env and set VPN_HOST. Set WG_EASY_PASSWORD even if you intend to use the wireguard profile, because the stack will not start without it. After docker compose up -d, expect three containers with Pi-hole and the VPN healthy and Unbound merely Up. If you are migrating from an earlier WireHole, read UPGRADING.md before you pull, and read the LICENSE file before you plan anything beyond personal use.

## Conclusion

Adopt WireHole if you already keep a Linux machine running around the clock and you want VPN, ad blocking and recursive DNS from one compose file rather than three hand-wired containers. Do not adopt it if your only always-on hardware is a Mac, a Windows PC or a WSL2 instance, because the README states those cannot host the stack; use them as clients instead. Before you commit, verify three things on your own host: that UDP port 51820 reaches the server from outside your network, that you have read UPGRADING.md if you are coming from an older WireHole layout, and that you accept that a powered-off server means a connected device has no internet until the VPN is switched off.

## FAQ

### Can I use WireGuard with a Pi-hole?

Yes, and that combination is what WireHole is built from. The VPN clients are pointed at Pi-hole's static address on the wirehole network, Pi-hole filters the queries, and the remaining queries go to Unbound.

### Can you use a VPN with pihole?

The README describes exactly that arrangement: connected devices send all DNS queries to Pi-hole, which blocks advertisement domains and forwards the rest to Unbound, which resolves them against the authoritative name servers.

### What are the disadvantages of WireGuard in the WireHole setup?

The README does not discuss WireGuard's protocol trade-offs. It does state that WireGuard uses UDP, which is why WSL2 cannot serve as the host: Windows can forward TCP ports into WSL2 but not UDP, so the VPN port stays unreachable.

### Is pihole still relevant in 2026?

The README does not make an argument about Pi-hole's relevance. It positions Pi-hole as the filtering layer of the stack, blocking advertisement and tracker domains for every connected device, with Unbound handling resolution behind it.

## Sources

- [IAmStoxe/wirehole on GitHub](https://github.com/IAmStoxe/wirehole)
- [Issues](https://github.com/IAmStoxe/wirehole/issues)
- [Project website](https://iamstoxe.com)
- [README](https://github.com/IAmStoxe/wirehole/blob/master/README.md)

---

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