Your Pi-hole, anywhere, exposed to no one
A Pi-hole at home answers the home network's DNS queries and refuses the ones for ad and tracker domains, so every device on the network gets the blocking without installing anything. Take the laptop out of the house and the blocking stays behind: the café's network hands out its own DNS server.
The obvious fix is the wrong one. Forwarding the router's port 53 to the Pi-hole makes it a DNS server that answers anyone, an open resolver, and open resolvers are what attackers use to amplify denial-of-service floods: a small query with a forged source address, a large answer sent to the victim. Pi-hole's own documentation warns that careless setups have created "an open resolver, which aids in DNS Amplification Attacks" (read November 1, 2026). So the home's DNS should answer the laptop and no one else.
We built that twice, the usual way and with nfltr, around the official Pi-hole image, and ran both through the same test.
The usual setup: a VPN home
Pi-hole's own documentation points to a VPN. PiVPN or wg-easy install WireGuard on the Pi-hole machine; the router forwards one UDP port (51820) to it; a dynamic DNS name follows the home's changing address; each device gets a key pair and a configuration whose DNS server is the Pi-hole's tunnel address.
It works, costs nothing, and the forwarded port belongs to WireGuard, not to a DNS server. It still needs a public IPv4 address (behind carrier-grade NAT nothing can be forwarded), a forwarded port, and the VPN connected on the laptop whenever it is away. Our version sent only the tunnel's subnet home; a full tunnel, the other common choice, sends every byte the laptop sends through the home's upload.
The same with nfltr
nfltr runs one small agent on each machine, and each agent connects out to a relay. nfltr's services carry TCP, and DNS has a TCP form that every DNS server answers, Pi-hole included (it is standard, RFC 7766). On the Pi-hole machine, nfltr up publishes Pi-hole's DNS port and its web interface, end-to-end encrypted:
agent: pihole
services:
dns: { tcp: 53, e2ee: true }
web: { tcp: 80, e2ee: true }
On the laptop, nfltr up maps them to local ports, pinned to the box's certificate:
connect:
dns: { target: pihole/dns, listen: "127.0.0.1:5353", pin: "<the box's pin>" }
web: { target: pihole/web, listen: "127.0.0.1:8080", pin: "<the box's pin>" }
The operating system asks its resolver over UDP, so one more piece sits on the laptop: a stub resolver (CoreDNS here) that answers the system on 127.0.0.1:53 and asks Pi-hole over TCP through the mapping, keeping that connection open between queries:
.:53 {
bind 127.0.0.1
forward . 127.0.0.1:5353 {
force_tcp
}
}
Point the laptop's DNS at 127.0.0.1 and its queries go to the home Pi-hole; its admin page is at http://127.0.0.1:8080/admin. The relay carries TLS records it cannot read, and a session opens only with your account key and the box's pin. Nothing at home accepts a connection from outside, and the home needs no public address.
We considered two other ways. nfltr's WireGuard mode carries DNS over UDP end to end, but its client side is Linux only and needs a kernel network device, like any VPN. DNS over HTTPS through a share link would reach a browser, but anyone holding the link could use it as a resolver, and Pi-hole would see every query from one client. We tested neither.
Phones are not covered. There is no nfltr app for iOS or Android, and Android's Private DNS speaks DNS over TLS to a hostname, on port 853 (Android's developer blog and RFC 7858, read November 1, 2026), which a share link (HTTPS on 443) cannot be. For a phone, the VPN is still the answer.
What we measured
Both setups ran as containers on one Mac: the Pi-hole machine behind a home router that adds 10 ms each way between home and the outside, a stand-in for the internet's DNS outside (Pi-hole's upstream, answering every name), a laptop outside, and in the nfltr setup a private relay outside. The laptop sent 100 cold queries (a name nobody had asked for, so Pi-hole asks its upstream) and 100 cached ones through each path.
| path | cold p50 / p95 | cached p50 / p95 |
|---|---|---|
| on the Pi-hole machine itself | 25.3 / 29.3 ms | 0.1 / 0.1 ms |
| WireGuard VPN | 49.6 / 55.0 ms | 23.3 / 27.9 ms |
| nfltr with the stub | 50.8 / 57.9 ms | 24.9 / 28.9 ms |
| nfltr, a new TCP connection per query | 123.7 / 129.6 ms | 74.4 / 81.9 ms |
Through either setup a query costs the round trip home, 20 ms of it the delay we added; a second run agreed within 2 ms at the median. The last row is why the stub keeps its connection: without it, each query opens a session and an end-to-end TLS handshake through the relay. CoreDNS closes an idle connection after 10 seconds (its documented default, read November 1, 2026), so the first query after a pause pays that once.
Each check below passed, judged by a program's exit code:
- Blocking works through the path. Two ad and tracker domains answered
0.0.0.0from Pi-hole's blocklist, and a normal domain answered with its upstream address, in both setups. - Nothing answers DNS at home. In both setups, DNS over UDP and over TCP to the home's public address got no answer, and its ports 80 and 443 were closed. Before, the router had one forwarding rule (UDP 51820); after, none, and nothing on the laptop listened beyond loopback.
- The admin page is yours only. The laptop reached Pi-hole's admin page over the tunnel before and through the mapping after. After, a session opened with a wrong account key was refused by the relay and one with the wrong pin failed closed.
- A restart recovers. The box's side (WireGuard before, the box's agent after) was killed and started. The laptop's next answer came 18.4 s later through WireGuard, which kept using its old session until its handshake timed out, and 0.18 s later through nfltr.
The setup, counted
The VPN setup is 32 lines, most of them the two WireGuard configurations a tool such as PiVPN writes for you. The nfltr one is 34 lines with a private relay and 30 with the hosted one, including the stub's six. The VPN has five moving parts, nfltr six: the stub is the extra one.
| before | after | |
|---|---|---|
| on the router | a forward for UDP 51820 | nothing |
| on the Pi-hole machine | WireGuard, a dynamic DNS updater | nfltr up, a certificate whose pin the laptop holds |
| on the laptop | WireGuard, connected while away | nfltr up, a stub resolver, the pin |
| keys | a key pair per device | the account key on both machines |
| outside | a dynamic DNS name | the relay |
Two things change for Pi-hole: every laptop query reaches it from the box's own agent, so its statistics show one client for the laptop, and the laptop resolves nothing while the box is down, as with a VPN whose DNS points home.
What it costs
nfltr is free until further notice. To show what relaying this traffic costs whoever runs the relay, we priced it with our cost ledger at public cloud list prices. A laptop away from home all month at 150,000 queries, 145 bytes each through the relay, is 0.02 GiB. Relaying that end-to-end encrypted, plus two agents connected all month, comes to 0.017 USD a month at Google Cloud's list prices, almost all of it the connected agents, and 0.0005 EUR at Hetzner's.
The alternatives, at list prices read on October 31 and November 1, 2026:
| alternative | price |
|---|---|
| WireGuard home (PiVPN, wg-easy) | free software; needs a public IPv4 address and a forwarded port |
| dynamic DNS name | free (DuckDNS), or 30 USD a year (No-IP's paid tier) |
| behind carrier-grade NAT: a VM as the VPN's public end | 4 USD a month (DigitalOcean, smallest) |
| a mesh VPN instead (Tailscale Personal) | free, up to 6 users, non-commercial use |
| a hosted filtering resolver instead of Pi-hole (NextDNS) | free up to 300,000 queries a month, then it stops blocking; Pro 159 INR a month (the page showed rupees when read from India) |
Evidence: the home Pi-hole from a laptop away from home
- Setup
- Both setups as Linux containers on one Mac (Apple Silicon) with Docker, runs on October 31 and November 1, 2026; the numbers are from the last run, the one before agreed within 2 ms at every median. The official Pi-hole image (2026.10.1, pinned by digest) on a home network behind a router with 10 ms of added delay each way (tc netem), a CoreDNS stand-in for the internet's DNS as Pi-hole's upstream, a laptop network outside, a private relay outside in the nfltr setup. Kernel WireGuard configured as PiVPN and wg-easy do, with a split tunnel. No internet: Pi-hole's blocklist was a small file in the hosts format its default lists use, and the laptop resolved the home's name without a real dynamic DNS service.
- Numbers
- Cached query p50 0.1 ms on the Pi-hole machine, 23.3 ms over WireGuard, 24.9 ms through nfltr with the stub, 74.4 ms with a new TCP connection per query; cold p50 25.3, 49.6, 50.8 and 123.7 ms; ad and tracker domains 0.0.0.0 and a normal domain answered in both setups; no answer to DNS over UDP or TCP on the home's public address in both; router forwarding rules 1 and 0; a wrong account key refused, a wrong pin failed closed; next answer 18.4 s after WireGuard restarted and 0.18 s after the box's agent restarted; 32 and 34 configuration lines (30 with the hosted relay); 145 bytes per query through the relay; 0.017 USD a month at Google Cloud list prices, 0.0005 EUR at Hetzner's, for 150,000 queries and two agents connected.
- Reproduce
- (a) With the released binaries: run
nfltr upwith the first file above beside Pi-hole,nfltr upwith the second and CoreDNS with the third on the laptop (the pin is in the box'snfltr upoutput), then set the laptop's DNS server to 127.0.0.1. (b) Our check, in the closed nfltr repository, runs both setups in containers and judges each claim by an exit code. - Limits
- One host, so the only network distance is the delay we added and there is no loss; the relay sat next to the laptop. The stub and DNS settings were tested on Linux only. Phones, nfltr's WireGuard mode and DNS over HTTPS were not tested. Two runs are no distribution. The cost covers relaying only, at list prices, for an assumed month of queries; prices change, and each one names the date it was read.
Is it for you?
Fits if you run a Pi-hole (or any DNS server) at home and want its blocking on a laptop away from home without forwarding a port, behind carrier-grade NAT or not, with your queries unreadable to every machine in between, the relay included. The relay still sees which machines talk, when, and how many bytes; it can refuse or cut a session but cannot read or change one.
Not ready for phones: there is no nfltr app, and Android's encrypted DNS setting needs a DNS-over-TLS server on port 853, which nfltr does not provide. If every device you take out is a phone, a VPN home or a hosted filtering resolver is the better fit. The laptop also needs a stub resolver beside nfltr up, one more program to install.
Try next: install nfltr and sign in on both machines, then run the three files above. The nfltr up reference covers services: and connect:. Part 4 of this series does the same for Home Assistant, and part 7 sets nfltr beside a mesh VPN.