The NordBastion polar-bear mascot standing in a dark Nordic stone hall beside a server rack, one paw raised to a floating cyan holographic runestone listing domain names, with four query beams fanning out through a runic archway toward three distant server monoliths and a fifth beam stopped by an amber crossed-circle shield, aurora visible through the window behind
How-to · Network·15 min read · 40 min hands-on

Your own DNS resolver.
The one log nobody thinks to move.

Every domain your devices look up is a timestamped line in someone else's file — your ISP's, or a free public resolver's. Six steps to move that file onto a machine you own: Unbound resolving from the root, AdGuard Home filtering in front, encrypted DNS on a real certificate, and port 53 that never faces the internet. The box costs $3.90 a month. Tested on Debian 12.

The six steps
  1. 01

    Shape

    Recursive, not forwarding

  2. 02

    Close

    Port 53, before it opens

  3. 03

    Resolve

    Unbound, validating

  4. 04

    Filter

    AdGuard Home, DoH, DoT

  5. 05

    Reach

    Tunnel first, clients next

  6. 06

    Verify

    Leaks, DNSSEC, uptime

Before you start · What is actually leaking

DNS is the index of your life. By default, you do not hold it.

Encrypt the disk, tunnel the traffic, pay in Monero, sign up without an email — and then let the laptop ask a resolver you have never thought about to translate every hostname it touches, one query at a time, in the clear, all day. The name lookup is the part of the stack that people harden last, and it is the part that produces the cleanest record of what somebody actually does.

It is worth being precise about what that record contains, because "DNS leaks your browsing" is both true and lazy. A query log is a list of domain names with timestamps, attributed to the IP address that asked. It is not the pages you read, and it usually is not the exact URL. It is something quieter and, for profiling, better: the shape of a life. The bank. The employer. The dating app checking in every four minutes. The medical site visited once at 02:00. The three domains that only ever appear together because they belong to the same messaging client. Nobody needs the contents when the index is this good.

Four parties can hold that index, and self-hosting moves it from the first two to the fourth:

Setup Who sees every query you make Visible on the wire Anonymity set
ISP resolver, plaintext (the default)Your ISP, by name and accountEverything, to anyone on the pathTheir subscribers
Public resolver, encrypted (DoH/DoT)That one company, tied to your IPNothing but the destinationMillions
Your own resolver, forwarding upstreamThe upstream you chose — unchangedNothing but the destinationOne
Your own resolver, recursing from the rootNobody — each zone sees only its own namesNothing but the destinationOne, on an IP that is not you

Row three is the row that catches people. Installing a resolver on your own server and then telling it to forward everything to 1.1.1.1 changes exactly one thing: your queries now arrive at Cloudflare from a different IP address. The log still exists, it still belongs to somebody else, and you have added a machine to maintain in exchange for nothing.

The last column is the honest cost, and this guide comes back to it twice. A resolver used by one household is a resolver whose queries are unambiguously that household's. What makes the trade worth taking is that the IP doing the asking is a rented Nordic server with no identity document behind it — not a home line with a subscriber name, and not a hyperscaler account with a card on file. The anonymity set shrinks; the link to a person is what actually breaks.

Step 01 · Shape

Recursing, or just relaying? Only one of the two is worth the server.

There are two completely different machines hiding behind the phrase "my own DNS server", and almost every tutorial builds the wrong one. Decide this before you install anything, because it is the difference between a privacy tool and a proxy for somebody else's privacy policy.

A forwarder takes your query and passes it to another resolver — typically Cloudflare, Google, Quad9 or your ISP — then caches whatever comes back. It is simple, it is fast, and it hands your entire query stream to a single third party. Every guide that ends with "now set your upstream to 1.1.1.1" has built one of these.

A recursive resolver does the work itself. To find mail.example.com it asks a root server who runs .com, asks the .com servers who runs example.com, then asks example.com's own nameservers for the record. Three or four short conversations with three or four different operators, none of whom is told anything about the others. With QNAME minimisation switched on it goes further: the root is asked only about .com, and the .com servers only about example.com. The full name is revealed to exactly one party — the one hosting the name, who was going to receive your connection thirty milliseconds later anyway.

Property Unbound, recursive Any forwarder AdGuard Home alone
Single party sees every queryNoYesYes — its upstream
DNSSEC validated locallyYesTrusted from upstreamTrusted from upstream
Blocklists and per-device rulesOnly via local zonesDepends on the daemonYes, with a web interface
DoH / DoT listeners for clientsDoT natively, DoH via a proxyDepends on the daemonBoth, built in
Cold-start latency3–4 round trips1 round trip1 round trip
Right answerThe privacy half of the stackOnly as a deliberate fallbackThe filtering half, in front

The two right-hand columns are not competitors — they are the two halves of the same build. AdGuard Home owns port 53, applies the blocklists and terminates the encrypted transports; Unbound sits behind it on loopback and does the resolving. Neither half does the other's job well, and the chain is about a hundred megabytes of RAM in total.

If you only take one thing from this page, take this: a resolver that forwards to a public DNS provider has not made your DNS private. It has moved the log by one hop and added a server to your maintenance list. The recursion is the whole point.

Step 02 · Close

Shut port 53 before anything opens it. This is not the careful step. It is the necessary one.

Order the box first. In the panel: Order → VPS → Sentinel, image Debian 12, in whichever bastion is closest to the devices that will use it — Helsinki, Stockholm, Oslo or Reykjavík. No email address is needed to open the account and the invoice settles in Monero, Bitcoin, Lightning or any other supported asset, which matters here more than usual: the entire value of a personal resolver rests on the IP address doing the asking not being traceable to a name.

Then run through the first-hour hardening checklist — SSH keys only, no password login, unattended upgrades — because everything below assumes a box that is not already compromised.

Read this before you start the daemon

A recursive resolver that answers the whole internet is called an open resolver, and it will be found by internet-wide scanners within hours of the port opening. DNS runs over UDP, UDP source addresses are trivial to forge, and a 60-byte query can produce a response dozens of times larger — so an open resolver is a free amplifier for a denial-of-service attack against a third party. The sequence that follows is always the same: the traffic starts, the abuse reports arrive, and the IP gets null-routed. It is the single most common way a first DNS server ends. Two rules prevent it entirely: refuse by default in the resolver config, and never allow 53/udp from 0.0.0.0/0 in the firewall.

So the firewall comes first, while nothing is listening yet. The rules below assume a WireGuard tunnel on 10.8.0.0/24 — if you have not built one, the WireGuard guide linked at the bottom of this page is a fifteen-minute detour and it is the transport this whole design is happiest on.

ufw default deny incoming
ufw allow 22/tcp                      # or your moved SSH port
ufw allow 51820/udp                   # WireGuard
ufw allow in on wg0 to any port 53 proto udp
ufw allow in on wg0 to any port 53 proto tcp
ufw enable

# DNS is NOT opened to the world. Confirm it:
ufw status numbered | grep 53

Note what is deliberately missing: there is no rule that opens 53 on the public interface. Port 53 exists only on wg0 and on loopback. If you later add DNS-over-TLS or DNS-over-HTTPS, those are TCP 853 and TCP 443, they are authenticated by a certificate, and they get their own rules in step 05 — the plaintext UDP port stays shut regardless.

Now clear the port. On Ubuntu — and on any Debian where someone enabled it — systemd-resolved is already listening on 127.0.0.53:53, and Unbound will refuse to start with a bind error that people spend twenty minutes misreading. Check first, then disable only the stub listener, keeping systemd-resolved as the local client-side interface:

ss -lunp 'sport = :53'                # who owns UDP/53 right now?

mkdir -p /etc/systemd/resolved.conf.d
cat > /etc/systemd/resolved.conf.d/no-stub.conf <<'EOF'
[Resolve]
DNSStubListener=no
EOF
systemctl restart systemd-resolved
ss -lunp 'sport = :53'                # should now be empty

On a clean Debian 12 the first command prints nothing and there is nothing to do. Run it anyway — it takes a second, and the alternative is diagnosing "address already in use" against a service you did not know was running.

Step 03 · Resolve

Unbound, from the root. One package, one config file, no upstream.

Unbound is a validating, recursive, caching resolver from NLnet Labs. It is packaged everywhere, it is what a large part of the internet already runs, and out of the box it recurses — you have to go out of your way to turn it into a forwarder. Install it together with the root hints package so it knows where the root servers are without fetching a list from anywhere:

apt update && apt install -y unbound dns-root-data
unbound -V | head -2

The dns-root-data package installs the root hints at /usr/share/dns/root.hints and the DNSSEC root trust anchor at /usr/share/dns/root.key, and keeps both updated through the normal package channel. That is the boring, correct way to get them; downloading root.hints from a URL in a cron job is the way that eventually fails silently.

/etc/unbound/unbound.conf.d/nb-resolver.conf

server:
    # ---- Who may ask. Deny first, allow narrowly.
    interface: 127.0.0.1@5335
    interface: 10.8.0.1
    access-control: 0.0.0.0/0 refuse
    access-control: ::0/0 refuse
    access-control: 127.0.0.0/8 allow
    access-control: 10.8.0.0/24 allow

    # ---- Privacy of the recursion itself.
    qname-minimisation: yes
    qname-minimisation-strict: no
    hide-identity: yes
    hide-version: yes
    rrset-roundrobin: yes

    # ---- DNSSEC. The anchor is maintained by the dns-root-data package.
    auto-trust-anchor-file: "/var/lib/unbound/root.key"
    root-hints: "/usr/share/dns/root.hints"
    harden-dnssec-stripped: yes
    harden-glue: yes
    harden-below-nxdomain: yes
    harden-large-queries: yes
    harden-short-bufsize: yes
    aggressive-nsec: yes
    val-clean-additional: yes

    # ---- Do not be a reflector, even by accident.
    refuse-any: yes
    ratelimit: 1000
    ip-ratelimit: 200
    edns-buffer-size: 1232

    # ---- Cache. This is where the 4 GB actually earns its keep.
    msg-cache-size: 128m
    rrset-cache-size: 256m
    cache-min-ttl: 60
    cache-max-ttl: 86400
    prefetch: yes
    prefetch-key: yes
    serve-expired: yes
    serve-expired-ttl: 3600

    # ---- Never chase a name that has no business being on the internet.
    private-address: 10.0.0.0/8
    private-address: 172.16.0.0/12
    private-address: 192.168.0.0/16
    private-address: 169.254.0.0/16
    private-address: fd00::/8
    private-address: fe80::/10

    num-threads: 2
    so-reuseport: yes
    verbosity: 0
    logfile: ""

remote-control:
    control-enable: yes
    control-interface: /run/unbound.ctl

Four of those lines are doing the real work and deserve a sentence each.

  • 01access-control: 0.0.0.0/0 refuse — the default answer to the internet is "no". Everything after it is an exception you wrote on purpose. This single line is what stops the amplification story from step 02 before the firewall even has to.
  • 02qname-minimisation: yes — the root servers are asked about .com, not about the full hostname. RFC 9156, on by default in current Unbound, and the reason a recursive resolver leaks less than the diagram suggests. Leave strict mode off: a small number of misconfigured zones still break under it.
  • 03refuse-any: yes — ANY queries return the largest possible answer for the smallest possible question, which is precisely what an amplification attack wants. Nothing legitimate needs them.
  • 04serve-expired: yes — when an authoritative server is unreachable, hand out the stale answer and refresh in the background instead of failing. This is the setting that stops a distant outage from looking, to everyone in the house, like the internet being down.

Note the two listeners. Loopback on port 5335 is what AdGuard Home will use in the next step; 10.8.0.1 is the tunnel address, so that clients on the VPN can query Unbound directly if you decide to skip the filtering layer. If you are not running WireGuard yet, drop that second interface line for now — Unbound will not start with an address that does not exist on the box.

Start it and prove it works:

unbound-checkconf
systemctl restart unbound && systemctl enable unbound

apt install -y dnsutils
dig @127.0.0.1 -p 5335 nordbastion.com +short

# DNSSEC: the first must come back with the "ad" flag set,
# the second must FAIL — its signatures are deliberately broken.
dig @127.0.0.1 -p 5335 dnssec-tools.org +dnssec | grep -E 'flags:|status:'
dig @127.0.0.1 -p 5335 dnssec-failed.org  | grep 'status:'

The ad flag on the first answer means Unbound checked the chain of signatures from the root down to the record and it held. SERVFAIL on the second means validation rejected a tampered answer rather than passing it to you. If the second one returns an address instead, validation is not on and the trust anchor is the first thing to look at.

Step 04 · Filter

AdGuard Home in front. Blocklists, per-device rules, and the encrypted listeners.

This step is optional in the strict sense — Unbound alone is already a private resolver — and almost everyone wants it anyway. AdGuard Home gives you blocklists, per-client policies, a readable query log while you are debugging, and, most usefully, native DNS-over-TLS and DNS-over-HTTPS listeners so that phones and browsers can talk to your resolver from networks where a VPN is not practical.

The arrangement is a chain, and getting the ports right is the whole trick: AdGuard Home owns 53 and faces the clients, Unbound sits on 127.0.0.1:5335 and faces the internet. Nothing else on the box listens on 53.

curl -fsSL https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scripts/install.sh | sh -s -- -v
# Web interface: http://<vps>:3000 — reach it over the SSH tunnel below,
# never by opening 3000 to the internet.
ssh -N -L 3000:127.0.0.1:3000 root@<vps>

Do the first-run wizard through that tunnel. Set the admin listener to 127.0.0.1 or to the tunnel address, never to 0.0.0.0, and give it a long password — this interface can rewrite every name your network resolves, which makes it as sensitive as the resolver itself.

Then set exactly one upstream: in Settings → DNS settings, clear the default list and enter your Unbound instance. One line, nothing else. If a public resolver is left in that box, everything in step 01 has been undone.

127.0.0.1:5335

Leave "Parallel requests" off — with a single upstream it does nothing but duplicate traffic — and turn "Use private reverse DNS resolvers" off unless you actually run an internal zone. Set the bootstrap servers to something you trust; they are used only to resolve the hostnames of upstream servers, and with a plain IP upstream they will never be consulted at all.

Blocklists: start narrow. The instinct is to enable everything on the list page and enjoy the counter. Resist it. Every list is a set of judgement calls made by somebody else about what your devices are allowed to reach, and the failures are quiet and confusing — a half-loaded page, a button that does nothing, a bank app that refuses to open. Begin with the default AdGuard DNS filter, or with a single well-maintained aggregate such as OISD, live with it for a fortnight, and only then add more. Keep an allowlist and use it without guilt.

The query log is the awkward part

You have just built a machine whose purpose is to stop somebody else from keeping a list of every name your devices resolve — and the first thing AdGuard Home does is keep exactly that list, on by default, for ninety days. It is genuinely useful while you are tuning blocklists, and it is the single most sensitive file on the server. Decide deliberately: 24 hours is enough to answer "what did that device just ask for?", zero is the honest setting once the tuning is done, and ninety days is a decision nobody should make by leaving a default alone. Settings → General → Query log, and set statistics retention while you are there.

Point the firewall at the new arrangement — AdGuard Home now owns port 53 on the tunnel interface, and Unbound is only reachable on loopback. Nothing about the public interface changes: it still has no DNS on it at all.

Step 05 · Reach

Two ways in. The tunnel is the good one; encrypted DNS is the portable one.

A resolver nobody can reach is not useful, and a resolver everybody can reach is the failure mode from step 02. There are exactly two good answers, and most people end up running both — the tunnel at home and on the laptop, encrypted DNS on the phone and on networks where UDP is filtered.

Option A — over WireGuard. The best of the two, and the reason this guide keeps pointing at the WireGuard guide. Port 53 stays invisible to the internet, the query itself never crosses an untrusted network in any form, and there is no certificate to renew. If the VPS is already your VPN exit, this is one line in each peer's configuration:

# On the client, in the [Interface] section of the peer config:
DNS = 10.8.0.1

One subtlety that bites people: on Linux, wg-quick sets that address through resolvconf or systemd-resolved, and if neither is installed it silently does nothing while the tunnel comes up perfectly. Check with resolvectl status or by reading /etc/resolv.conf after the tunnel is up — a working tunnel with the old resolver still configured is exactly the leak this whole exercise is about.

Option B — DNS-over-TLS and DNS-over-HTTPS. Needed when the device cannot always be on the tunnel. Both are authenticated by a certificate, which means a hostname: point an A record at the VPS, then let AdGuard Home hold the certificate directly or place it behind a reverse proxy that already terminates TLS for you. The listeners are 853/tcp for DoT and 443/tcp for DoH.

ufw allow 853/tcp                     # DNS-over-TLS
ufw allow 443/tcp                     # DNS-over-HTTPS
# 53/udp is still NOT open on the public interface. It never will be.
ufw status

Give the DoH endpoint a path that is not guessable — AdGuard Home lets you serve it under /dns-query/<random>, and a different path per person means access can be revoked one person at a time without rotating anything for everyone else. It is not authentication in any strong sense, but it keeps a public listener from being an open resolver with extra steps.

Client Where the setting lives Transport it speaks
Android 9 and laterSettings → Network → Private DNS → hostnameDNS-over-TLS only
iOS and macOSA signed .mobileconfig DNS profileDoH or DoT, system-wide
Windows 11Settings → Network → DNS server assignmentDoH, per adapter
FirefoxSettings → Privacy → DNS over HTTPS → CustomDoH, browser only
Router (whole network)WAN DNS, or DHCP option 6Plain DNS — so tunnel it

The router row is the one to think about. Handing your resolver out over DHCP covers every device in the house at once, including the ones with no settings screen — but the hop from the router to the VPS is then plain DNS across the open internet, which is the thing you set out to stop. Run the tunnel on the router itself, or accept that the last mile is unencrypted and let the recursion be the win.

One last configuration worth knowing about: a browser that has DNS-over-HTTPS enabled will use its own resolver and quietly ignore the system setting, tunnel or not. Firefox and Chrome both ship this on by default in some regions. Either point the browser at your endpoint too, or turn its DoH off so it follows the operating system.

Step 06 · Verify

Four checks. Three of them fail on somebody's first build.

1. Is it open to the internet? Run this from a machine that is not on the tunnel — a phone on mobile data, another VPS, anything. A timeout is the correct and desirable answer. An actual response means every scanner on the internet will find it too, and you should go back to step 02 immediately.

dig @<public-ip-of-vps> +timeout=3 example.com
# Expected: ";; connection timed out; no servers could be reached"

2. Is the device really using it? This is the check that fails most often, and the failure is invisible: the tunnel is up, the setting looks right, and the operating system is still asking the resolver the café handed it over DHCP. Ask the resolver itself who it thinks you are, then confirm from the other side.

# On the client — which resolver is the system actually using?
resolvectl status | grep -A2 'Current DNS'        # Linux
scutil --dns | grep nameserver                    # macOS

# On the server — does the query actually arrive?
tail -f /var/log/syslog | grep -i unbound &
unbound-control stats_noreset | grep total.num.queries

Then load one of the public DNS-leak test pages from the device and read the result. If it names your VPS provider, the path is right. If it names your ISP or a large public resolver, something upstream of the tunnel is still answering.

3. Is validation actually on? Repeat the two dig commands from step 03 through the whole chain this time — client, AdGuard Home, Unbound — rather than against Unbound directly. A broken-signature name must fail here too. If AdGuard Home has been left with a second upstream somewhere in its settings, this is where it shows.

4. Is the cache doing its job? After a day of normal use the hit rate should be somewhere between seventy and ninety per cent. Much lower usually means the cache is too small or cache-min-ttl is being ignored because everything upstream has a very short TTL; much higher on a busy network is a happy sign that prefetch is working.

unbound-control stats_noreset | grep -E 'total.num.(queries|cachehits)'
# hit rate = cachehits / queries

And then the two operational decisions. First: what happens when this box reboots. Every device pointed at it loses name resolution, which everyone in the household will report as the internet being broken. Set the service to restart on failure and to start at boot, then reboot the VPS once on purpose and watch it come back. Second: a fallback. Either list a public resolver as a secondary on your clients and accept that those queries leave your control during an outage, or run a second instance on a second VPS in a different bastion. Both are defensible. Discovering the question at 23:00 on a Sunday is not.

systemctl enable unbound AdGuardHome
systemctl show unbound -p Restart -p RestartUSec
reboot        # then, from a client: dig nordbastion.com +short
The honest part · What this does not do

It closes one channel. Four others are still open.

A private resolver is a real improvement and it is not a cloak. Anyone selling it as one is selling something. Here is the list of what remains visible after everything above is done, because knowing the shape of the remaining exposure is the difference between a defence and a comfort blanket.

The connection still happens. One millisecond after the lookup, your device opens a TCP connection to the address it just learned. Anyone watching your line sees that address. For a site on its own IP, the address is the site. Encrypted DNS removes the question; it does not remove the answer.

The TLS handshake usually names the site. Server Name Indication is sent in the clear at the start of nearly every HTTPS connection, so a passive observer reads the hostname anyway. Encrypted Client Hello fixes this and is deployed unevenly — where both the browser and the site support it the name is hidden, and where they do not it is not. Do not assume it is on.

The resolver operator is now you, and you are one person. Every authoritative server your resolver talks to sees a single IP address asking about their zone. Across many zones, that IP has a distinctive pattern, and the pattern is a household. This is the anonymity-set cost from the first chapter, and the only thing that blunts it is the IP not being traceable to a name — a KYC-free VPS paid in crypto — and, ideally, being the same IP your traffic already exits from.

The provider can see the packets, as always. The VPS runs on somebody's hardware in somebody's datacentre. NordBastion does not inspect customer traffic and the transparency report and warrant canary describe what would happen if asked — but the honest architectural statement is that a machine you rent is a machine somebody else can physically reach. What self-hosting changes is that there is no product-level query log, no analytics pipeline and no privacy policy that can be amended; there is only a process holding a cache in RAM, configured by you, in a jurisdiction you chose on purpose.

What you have actually bought, precisely: the single richest and cheapest source of behavioural data about you is no longer being collected by a party whose business model involves collecting it. That is a narrow claim, and it is a real one. Nothing on this page is a substitute for Tor when the threat model calls for Tor — and for that, the onion-service guide below is the honest next reading.

Sizing · What to order

DNS is small. Order for what else lives on the box.

Tier Monthly Resources What it comfortably runs
Sentinel$3.902 vCPU · 4 GB · 120 GBResolver + WireGuard for a household. The right answer.
Garrison$7.904 vCPU · 8 GB · 240 GBSame, plus a Vaultwarden, a SearXNG and a backup target
Ravelin$16.908 vCPU · 16 GB · 480 GBA small team, twenty-five tunnel peers, other services beside
A second Sentinel$3.90In another bastionThe fallback resolver, so an outage is not an internet outage

Nothing in this table is chosen for DNS. A resolver for thirty devices uses a few hundred megabytes of cache and a fraction of one core, and moves a few hundred megabytes of traffic a month. Pick the tier for the WireGuard peers, the other services and the disk — then let the resolver ride along for free.

Field notes · Six traps

Six ways this goes wrong. Five of them are silent.

Trap 01 · Loud

The open resolver, and the null route a week later

One firewall rule too many, or an access-control line left at allow. Scanners find the port in hours, the amplification traffic starts, the abuse report arrives, the IP goes dark. Refuse by default, allow only the tunnel, and test from off-network.

Trap 02 · Pointless

A private resolver forwarding to a public one

AdGuard Home installed, upstream left on the default list, everything working beautifully — and the query log still accumulating at a company whose name is in the settings box. Set one upstream, and make it your Unbound.

Trap 03 · Invisible

The tunnel is up and the device never asked you

wg-quick could not push the DNS setting because resolvconf is not installed, or a browser is doing its own DNS-over-HTTPS. Everything looks correct and every lookup still goes to the network you are sitting on. Verify from the resolver's side, not the client's settings screen.

Trap 04 · Confusing

The blocklist broke the bank app, three weeks later

Nothing announces itself as a DNS failure — a page half-loads, a button does nothing. Six aggressive lists enabled on day one make this untraceable. Start with one, add slowly, keep an allowlist, and keep enough query log to answer the question.

Trap 05 · Ironic

Ninety days of query log, kept by default

The whole build exists to stop someone keeping this list, and the filtering layer keeps it out of the box, on disk, for three months. Set retention on purpose — 24 hours while tuning, zero afterwards — and remember it covers everyone on the network, not only you.

Trap 06 · Sudden

One reboot, and the house has no internet

A resolver is infrastructure the moment other people rely on it. Enable both services at boot, set Restart=on-failure, reboot once on purpose to prove it, and decide the fallback before you need it rather than during.

FAQ · Private DNS

Questions, answered.

Eleven questions that come up while building this — including the two that decide whether it is worth building at all.

Is running my own resolver actually more private than using Quad9 or Cloudflare?

It is different, and it is better in the way that matters most — but the honest answer has two halves. What you gain: with a recursive resolver, no single party ever sees your full query stream. The root servers learn that someone asked about .com, the .com servers learn that someone asked about example.com, and only the operator of example.com learns the full name — and none of them see the next thousand names you look up. A public resolver, by contrast, sees every single one, in order, with timestamps, forever, and its privacy policy is the only thing standing between that list and someone else. What you lose: a personal resolver has an anonymity set of one. Queries leaving a big public resolver are mixed with millions of other people's; queries leaving yours are unambiguously yours. That trade is worth taking when the resolver's IP is not tied to your identity — which is exactly what a KYC-free VPS paid in crypto gives you, and exactly what a hyperscaler with your card on file does not. Put the resolver behind the same VPS your VPN already exits from and the anonymity set does not shrink at all, because that IP was already doing your browsing.

Will self-hosting DNS make my browsing faster?

Slightly slower on the first lookup of a name, noticeably faster on every repeat, and roughly a wash overall. A recursive resolver starting cold has to walk the delegation chain — root, TLD, authoritative — which is three or four round trips instead of the one round trip to a public resolver that already had the answer cached from someone else's query. Once your cache is warm, which for a household takes about a day, the common names are served from RAM in well under a millisecond and you are ahead. Turn on prefetch so popular records are refreshed before they expire and the cold-start case mostly disappears. The one place you can genuinely lose performance is CDN geolocation: large public resolvers pass a truncated version of your network location to the authoritative server (EDNS Client Subnet) so the CDN can pick a nearby edge. Unbound does not send it, by design. If your resolver sits in the same region as you, this changes nothing measurable. If the resolver is in Reykjavík and you are in Singapore, some CDNs will hand you a European edge and you will feel it.

Do I need a domain name for this?

Only for encrypted transport. Plain DNS over a WireGuard tunnel needs no domain, no certificate and no public exposure at all — the resolver listens on a private address like 10.8.0.1 and answers only peers on the tunnel. That configuration is both the simplest and the most private one in this guide. You need a domain the moment you want DNS-over-TLS or DNS-over-HTTPS, because both are authenticated by a certificate and a certificate is issued for a name. That is the configuration to pick when devices cannot always be on the VPN — a work laptop, a phone on a network that blocks UDP, a family member who will not maintain a tunnel. If you do register one, register it somewhere that does not require the same identity you were careful about elsewhere, and keep WHOIS privacy on.

Unbound or AdGuard Home — do I need both?

They solve different problems and the pairing is not redundant. Unbound is the resolver: it walks the DNS from the root, validates DNSSEC signatures and caches the results. AdGuard Home is not a resolver at all — it is a filtering front end with a web interface and encrypted listeners, and it forwards everything it does not block to an upstream. Run AdGuard Home alone and its upstream is a public resolver, which puts your whole query stream back in someone else's log. Run Unbound alone and you get a genuinely private resolver with no blocklists, no per-device rules and no interface. Chained — AdGuard on 53, Unbound on 127.0.0.1:5335 as its only upstream — you get filtering and encrypted transport in front of a resolver that answers from the root. If you want the smallest possible attack surface and do not care about ad blocking, Unbound alone is a completely respectable answer, and it is the half of the stack that actually delivers the privacy.

What is an open resolver, and why is it such a big deal?

An open resolver is a recursive resolver that answers queries from any source address on the internet. It is a big deal because DNS runs over UDP, UDP source addresses are trivially forged, and a small query can produce a large answer. An attacker sends your server a 60-byte query with a victim's address forged as the source; your server dutifully sends the victim a response dozens of times larger. Multiply by thousands of open resolvers and it is a reflection-and-amplification DDoS, and your server is one of the guns. In practice the sequence is fast and predictable: internet-wide scanners find a newly opened port 53 within hours, the traffic starts, the abuse complaints arrive, and the provider null-routes the IP to protect its network. This is not a hypothetical and it is not rare — it is the single most common way a first DNS server ends badly. The fix costs one firewall rule and one line of config: refuse by default, allow only your tunnel.

Does encrypted DNS hide which websites I visit from my ISP?

It hides the lookup, not the visit. DNS-over-TLS and DNS-over-HTTPS mean your ISP no longer sees a plaintext question that says example.com. What it still sees is the connection you make one millisecond later: the destination IP address, and — unless both ends support Encrypted Client Hello — the server name in the TLS handshake, in the clear. For a site on a dedicated IP the address alone identifies it; for a site behind a large CDN the address is shared and the SNI is the giveaway. So encrypted DNS raises the cost of passive logging and removes the easiest, cheapest surveillance surface there is, and it does not make your browsing invisible. If the threat model is your ISP or a hostile network seeing everything, the tool for that is the tunnel — encrypted DNS closes the leak that a VPN alone can still have, when the operating system keeps using the ISP resolver outside the tunnel.

How big does the VPS need to be?

Much smaller than people expect. DNS answers are hundreds of bytes and the work per query is a hash lookup, so the resolver for a household or a small team is idle almost all of the time. A Sentinel — 2 vCPU, 4 GB, $3.90 a month — will serve dozens of devices without the load average leaving zero, and most of that 4 GB is available to be spent on cache, which is the only resource that actually improves the experience. Give Unbound a few hundred megabytes of message and RRset cache and your hit rate will sit in the eighty-to-ninety per cent range. The reason to size up is never DNS: it is that the same box is also terminating your WireGuard tunnel, or running a Vaultwarden, or holding a backup repository. Bandwidth is a non-issue — a busy household resolver moves a few hundred megabytes a month.

What happens when the VPS reboots or the resolver dies?

Every device configured to use it stops resolving names, which the people using them will describe as "the internet is down". This is the real operational cost of self-hosting DNS and it deserves a decision rather than a discovery. Three defences, in increasing order of effort. Configure a secondary resolver on your clients — a public one such as Quad9 or dns0.eu — and accept that during an outage those queries leave your control; this is what most people should do. Or run a second instance on a second VPS in another bastion and list both, which keeps everything in-house at the price of a second $3.90 box. Or, on the tunnel, run a tiny local cache on each client so a short outage is invisible. Whatever you pick, set Restart=on-failure on the unit, enable it at boot, and test the reboot once on purpose — a resolver that does not come back by itself will always choose the worst moment to prove it.

Will the blocklists break things?

Yes, occasionally, and the way you handle that is the difference between a setup that lasts and one that gets ripped out in a fortnight. Blocklists are maintained by people making judgement calls, and every so often a call catches something you need: a bank's fraud-detection endpoint, a delivery-tracking widget, a login flow that redirects through an analytics domain. The failure is confusing because it does not look like DNS — the page half-loads, or a button does nothing. Three habits fix it. Start with one conservative list rather than six aggressive ones. Keep an allowlist and add to it without guilt. And keep the query log on long enough to answer "what did this device just ask for, and what did we refuse?" — which is the moment you discover that a filtering resolver is itself a device that logs everything, and that its retention setting is a privacy decision you are making for everyone on the network.

Can I share the resolver with family or friends?

Yes, and it is one of the best reasons to build it — one $3.90 box can clean up the DNS for an entire household or a handful of friends. Two rules make it safe. First, share it over the tunnel or over an authenticated encrypted endpoint, never by opening port 53 to the world; with DNS-over-HTTPS you can give each person a distinct path so access can be revoked individually, and AdGuard Home can then apply different filtering per client. Second, be clear with yourself about what you are taking on: you now hold the query log of everyone using it. Set retention to zero or to a day, tell them what the setting is, and remember that if it is ever your own privacy you are protecting, a resolver used by five people is also a resolver where any single query is harder to attribute to any one of them.

Does NordBastion mind if I run a DNS resolver on a VPS?

Not at all — a private, access-controlled resolver is a normal workload and the acceptable-use policy has nothing to say about it. What the policy does care about, and what every serious network cares about, is an open resolver being used as an amplification reflector against a third party, because the attack traffic leaves our network and lands on someone else's. Follow step 02 and that never happens: refuse by default, allow the tunnel, rate-limit the rest. If you want to run a deliberately public resolver as a service to others, that is a conversation worth having in advance rather than after the first abuse report — the technical requirements (response-rate limiting, no ANY, monitoring) are well understood, and running one accidentally is the problem, not running one on purpose.

Get the box

A resolver of your own, on an IP that is not your name. KYC-free, crypto-paid.

Sentinel — 2 vCPU and 4 GB for $3.90 a month, in Helsinki, Stockholm, Oslo or Reykjavík. Enough for the resolver, the WireGuard tunnel it should live behind, and whatever else you decide to stop renting. No email at signup, no identity document, and no product-level log of anything you look up.

Last reviewed · 2026-10-12 · Sources · Unbound and AdGuard Home documentation, RFC 9156 (QNAME minimisation), RFC 7858 (DNS-over-TLS), RFC 8484 (DNS-over-HTTPS), systemd-resolved(8) · Cadence · yearly

Private hosting, built in the North.