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:
- Pi-hole — blocks ads and trackers for any device that uses it as a DNS server.
- 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:
- Open the Pi-hole dashboard (
http://<your-server-ip>/admin) and log in. - Go to Settings → DNS.
- Under Upstream DNS Servers, untick any pre-selected providers (like Google or Cloudflare).
- Tick Custom 1 (IPv4) and enter:
127.0.0.1#5335 - 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
- Connect to your VPN from a phone or laptop using the config or QR code from
pivpn -qr. - Once connected, visit
http://<your-server-ip>/admin— you should be able to reach the Pi-hole dashboard. - Try browsing to a known ad-heavy site and check the Pi-hole dashboard’s Query Log — you should see blocked requests rolling in.
- 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!