• Skip to primary navigation
  • Skip to main content
  • Skip to primary sidebar

Nerdgineer

One engineer's take on the world as a nerd

  • Home
  • About
  • Upcoming rides
You are here: Home / Technologist / Build Your Own Private VPN with Ad-Blocking DNS (Pi-hole + WireGuard)

Build Your Own Private VPN with Ad-Blocking DNS (Pi-hole + WireGuard)

Shared on 2026-06-15

If you’ve ever wanted your own personal “internet filter” that blocks ads and trackers no matter where you are — at home, on coffee shop Wi-Fi, or roaming on your phone — this guide is for you.

We’re going to set up a small Linux server that does two things:

  1. Pi-hole — blocks ads and trackers for any device that uses it as a DNS server.
  2. WireGuard (via the friendly installer PiVPN) — creates a private, encrypted tunnel from your devices back to this server, so you can use Pi-hole’s filtering from anywhere, and the server itself isn’t directly exposed to the internet for anything except the VPN.

By the end, your server will quietly sit there doing one job: only devices connected through your VPN can use it, and everything they look up gets filtered through Pi-hole.

This guide assumes you’re starting with a fresh installation of a Debian-based Linux distribution (such as Ubuntu Server or Raspberry Pi OS) and that you can access it over SSH or a terminal.

What you’ll need

  • A small always-on computer or server (a Raspberry Pi, an old laptop, or a cheap cloud VPS all work)
  • A fresh install of a Debian/Ubuntu-based OS
  • Basic comfort typing commands into a terminal (we’ll explain each one)
  • About 30–45 minutes

Step 1: Update everything

Before installing anything new, make sure your system’s software is fully up to date. This patches security holes and avoids weird compatibility issues later.

sudo apt update
sudo apt full-upgrade -y

apt update refreshes the list of available software versions, and full-upgrade actually installs the newest versions, including any that need to remove or replace older packages.


Step 2: Create a personal user account

Most Linux servers come with a root account that can do absolutely anything — including break absolutely anything. Best practice is to create a normal user account for yourself and use sudo (which temporarily grants admin rights) only when needed.

Create a new user, replacing <username> with whatever name you’d like:

sudo adduser <username>

You’ll be asked to set a password and optionally fill in some details (these can be left blank).

Now add that user to the sudo group, which allows it to run admin commands:

sudo usermod -aG sudo <username>

Switch to your new account and test it

su - <username>
sudo apt update

If sudo apt update runs without complaining about permissions (it will ask for your password the first time), everything’s working correctly. From now on, do all your work logged in as this user, not as root.

(Optional but recommended) Disable the root account

Since you now have a sudo-capable user, there’s no need to log in as root directly. Disabling direct root logins is a simple security improvement:

sudo passwd -l root

This “locks” the root account so nobody can log in with a root password. Your <username> account can still use sudo to perform admin tasks as needed.

If you ever need to re-enable it (for troubleshooting, for example), you can run:

sudo passwd -u root

Step 3: Install Pi-hole

Pi-hole is the piece that blocks ads and trackers. It works by acting as your network’s DNS server — when a device asks “what’s the address for adserver.example.com?”, Pi-hole checks it against block lists and refuses to answer if it’s a known ad or tracker domain.

Install it with the official one-line installer:

curl -sSL https://install.pi-hole.net | bash

This launches an interactive setup wizard. Walk through the prompts — the defaults are sensible for most people. Pay attention to two things near the end:

  • The admin password it generates (or lets you set) — you’ll need this to log into the Pi-hole web dashboard.
  • The upstream DNS provider you choose — this is the DNS server Pi-hole forwards allowed requests to. If you plan to follow the optional Unbound step below, don’t worry too much about this choice now; we’ll point Pi-hole at Unbound afterward.

If you’d prefer to read through the full official instructions first, they’re available on the Pi-hole website.

Once installed, you can reach the Pi-hole dashboard at http://<your-server-ip>/admin (only from devices on your VPN once we finish setting things up).


Step 4 (Optional): Add Unbound for fully private DNS lookups

By default, Pi-hole forwards the DNS lookups it doesn’t block to an upstream provider like Cloudflare or Google. That’s fine, but it means those providers can still see which sites you’re visiting (even if your ISP can’t).

Unbound is a small program that lets your server look up domain names directly from the source — talking straight to the authoritative DNS servers for each website, rather than going through a third-party DNS provider. This step is entirely optional, but it’s a nice privacy upgrade.

Install Unbound

sudo apt install unbound

Create a configuration file for Pi-hole to use

Create a new file at /etc/unbound/unbound.conf.d/pi-hole.conf:

sudo nano /etc/unbound/unbound.conf.d/pi-hole.conf

Paste in the following configuration, then save and exit (in nano, press Ctrl+O to save and Ctrl+X to exit):

server:
    # If no logfile is specified, syslog is used
    # logfile: "/var/log/unbound/unbound.log"
    verbosity: 0

    interface: 127.0.0.1
    port: 5335
    do-ip4: yes
    do-udp: yes
    do-tcp: yes

    # May be set to no if you don't have IPv6 connectivity
    do-ip6: yes

    # You want to leave this to no unless you have *native* IPv6. With 6to4 and
    # Teredo tunnels your web browser should favor IPv4 for the same reasons
    prefer-ip6: no

    # Use this only when you downloaded the list of primary root servers!
    # If you use the default dns-root-data package, unbound will find it automatically
    #root-hints: "/var/lib/unbound/root.hints"

    # Trust glue only if it is within the server's authority
    harden-glue: yes

    # Require DNSSEC data for trust-anchored zones, if such data is absent, the zone becomes BOGUS
    harden-dnssec-stripped: yes

    # Don't use Capitalization randomization as it is known to cause DNSSEC issues sometimes
    use-caps-for-id: no

    # Reduce EDNS reassembly buffer size to avoid issues with fragmented UDP packets
    edns-buffer-size: 1232

    # Perform prefetching of close-to-expired cache entries for frequently queried domains
    prefetch: yes

    # One thread is sufficient for most small setups
    num-threads: 1

    # Ensure the kernel buffer is large enough to avoid losing messages during traffic spikes
    so-rcvbuf: 1m

    # Keep local network addresses private
    private-address: 192.168.0.0/16
    private-address: 169.254.0.0/16
    private-address: 172.16.0.0/12
    private-address: 10.0.0.0/8
    private-address: fd00::/8
    private-address: fe80::/10

    # Prevent reverse lookups for non-public IP ranges (per RFC6303)
    private-address: 192.0.2.0/24
    private-address: 198.51.100.0/24
    private-address: 203.0.113.0/24
    private-address: 255.255.255.255/32
    private-address: 2001:db8::/32

Now restart Unbound so it picks up the new configuration:

sudo systemctl restart unbound

You can do a quick test to make sure it’s working:

dig pi-hole.net @127.0.0.1 -p 5335

If you get back a response with an ANSWER SECTION, Unbound is working correctly.

Point Pi-hole at Unbound

Now tell Pi-hole to send its lookups to Unbound instead of an external provider:

  1. Open the Pi-hole dashboard (http://<your-server-ip>/admin) and log in.
  2. Go to Settings → DNS.
  3. Under Upstream DNS Servers, untick any pre-selected providers (like Google or Cloudflare).
  4. Tick Custom 1 (IPv4) and enter: 127.0.0.1#5335
  5. Scroll down and click Save.

Pi-hole will now use Unbound for any lookups that aren’t blocked, keeping your DNS queries private end-to-end.


Step 5: Install WireGuard using PiVPN

WireGuard is a modern, fast, and secure VPN protocol. PiVPN is a friendly installer script that sets up WireGuard for you and handles generating client configurations — without it, configuring WireGuard by hand involves a fair bit of manual key management.

Install it with:

curl -L https://install.pivpn.io | bash

This also launches an interactive installer. A few notes on the prompts:

  • It will automatically detect your existing Pi-hole installation and offer to configure your VPN clients to use Pi-hole for DNS automatically — say yes to this.
  • You’ll be asked to confirm the public IP address or domain name your devices will use to reach this server. If your server doesn’t have a static IP (most home internet connections don’t), consider setting up a free dynamic DNS hostname (e.g. via DuckDNS or No-IP) so the address doesn’t change on you.
  • You’ll choose a port for WireGuard — the default (51820/UDP) is fine unless you have a reason to change it.

Once installation finishes, you can create a configuration file for each device (phone, laptop, etc.) with:

pivpn add

This generates a .conf file you can transfer to your device, or — even easier — generate a QR code for mobile devices with:

pivpn -qr

Scan the QR code with the WireGuard app on your phone, and it’s instantly configured.


Step 6: Lock down the firewall

This is the most important step from a security standpoint. The goal is:

  • Allow WireGuard VPN connections in from the internet (so you can connect from anywhere).
  • Allow SSH so you can keep managing the server (ideally only from trusted networks, or via your VPN once it’s set up).
  • Block everything else from the outside world — especially the DNS, HTTP, and HTTPS ports that Pi-hole and its dashboard use. These should only ever be reachable through the VPN tunnel, never directly from the internet.

We’ll use UFW (Uncomplicated Firewall), which is a much friendlier front-end for Linux’s firewall rules than editing raw iptables files.

Install UFW (if it isn’t already)

sudo apt install ufw

Allow the essentials

sudo ufw allow OpenSSH
sudo ufw allow 51820/udp

The first command keeps your SSH access working (so you don’t lock yourself out!). The second opens the WireGuard port (adjust the number if you chose a different port during the PiVPN setup) so your devices can connect to the VPN.

Confirm DNS, HTTP, and HTTPS are NOT open to the world

By default, UFW blocks everything that isn’t explicitly allowed, so as long as you haven’t added rules for ports 53 (DNS), 80 (HTTP), or 443 (HTTPS), they’re already inaccessible from the public internet. You can double check with:

sudo ufw status verbose

You should only see rules for SSH and your WireGuard port. If you see anything for ports 53, 80, or 443, remove those rules — Pi-hole’s DNS and dashboard should only be reachable from inside the VPN’s private network range (PiVPN sets this up automatically as part of the WireGuard interface).

Enable the firewall

sudo ufw enable

You’ll be warned that this might disrupt existing SSH connections — as long as you’ve allowed OpenSSH above, you’ll be fine. Type y to confirm.


Step 7: Test everything

  1. Connect to your VPN from a phone or laptop using the config or QR code from pivpn -qr.
  2. Once connected, visit http://<your-server-ip>/admin — you should be able to reach the Pi-hole dashboard.
  3. Try browsing to a known ad-heavy site and check the Pi-hole dashboard’s Query Log — you should see blocked requests rolling in.
  4. Disconnect from the VPN and confirm you can no longer reach the Pi-hole dashboard or resolve DNS through the server’s public IP — this confirms it’s properly locked down.

Wrapping up

You now have a private, ad-blocking DNS server that’s only reachable through an encrypted VPN tunnel. A few ideas for where to go next:

  • Set up dynamic DNS if your home IP address changes periodically, so your VPN connection details don’t go stale.
  • Explore Pi-hole’s block lists (Settings → Blocklists) to fine-tune how aggressive the filtering is.
  • Consider enabling automatic security updates (unattended-upgrades) so your server stays patched without manual effort.

Happy (and ad-free) browsing!

Filed Under: Technologist

Primary Sidebar

About the author

Hello everyone, my name is Andrew. I am a nerd and an engineer. So welcome to Nerdgineer, the blog where I share the details of my journey called life as I attempt to understand life as a whole.

Categories

Copyright © 2026 · Metro Pro on Genesis Framework · WordPress · Log in