Skip to main content
Here's a recipe for your pie hole

VPN/DNS: Pihole - Wireguard - Unbound installation

original photo by Andrew Malone

Self-Hosted VPN/DNS: Pi-hole + WireGuard + Unbound (2025 Guide)

Introduction

This guide walks you through creating a self-hosted VPN server that combines network-level ad blocking, privacy-focused DNS resolution, and secure remote access. Unlike commercial VPN services (which often have questionable privacy practices), you'll have complete control over your infrastructure.

What You'll Build

A cloud-hosted server running:

  • Pi-hole: Network-wide ad and tracker blocking
  • Unbound: Recursive DNS resolver for enhanced privacy
  • WireGuard: Modern, fast VPN protocol

Once configured, your devices connect to this server 24/7, giving you:

  • Ad blocking across all apps and browsers
  • Protection on untrusted networks (coffee shops, airports, hotels)
  • Direct DNS queries to root nameservers (bypassing ISP and third-party DNS providers)
  • Encrypted connections between your devices

Why This Matters

Security: Public WiFi networks are often compromised. Your encrypted VPN tunnel protects all traffic from eavesdropping and man-in-the-middle attacks.
Privacy: By running your own recursive DNS resolver, you eliminate third-party DNS providers (Google, Cloudflare, your ISP) from seeing your browsing patterns. Your queries go directly to authoritative nameservers.
Ad Blocking: Pi-hole blocks ads, trackers, and malware domains at the network level using curated blocklists. This works across all devices and apps, not just web browsers.
Censorship Circumvention: For users in regions with internet restrictions, this provides a trusted pathway to the open internet.
Family Safety: Parents can monitor and control internet access for children's devices through Pi-hole's logging and blocking features.

Prerequisites

  • Basic Linux command line knowledge
  • A cloud hosting account (recommendations below)
  • Devices to connect (smartphones, laptops, tablets)
  • SSH client for your operating system
  • Approximately $5-10/month for hosting costs

Technical Requirements:

  • Server: Minimum 1GB RAM, 1 vCPU, 10GB storage
  • Recommended: 2GB RAM, 2 vCPU, 20GB storage (for 10+ devices)
  • Operating System: Ubuntu 24.04 LTS

Choosing Your Cloud Provider

Any VPS (Virtual Private Server) provider works, but here are current recommendations:

Important Note About Streaming Services

Most cloud hosting providers have their IP ranges blocked by streaming services (Netflix, HBO Max, Disney+, etc.). If streaming access is important:

  • Use a residential ISP connection (Raspberry Pi at home)
  • Research providers specifically advertising streaming-friendly IPs
  • Accept that streaming may not work through this VPN

For this guide, I assume you're primarily focused on security, privacy, and ad blocking rather than geo-unblocking streaming services.

Initial Server Setup

Create Your Server

  1. Sign up for your chosen cloud provider
  2. Create a new Ubuntu 24.04 LTS server
  3. Choose a data center location close to you for better latency
  4. Select at least 1GB RAM (2GB recommended)
  5. Add your SSH key during creation (highly recommended)

First Login and Updates

SSH into your server:

ssh root@your-server-ip

Update the system:

apt update && apt upgrade -y
apt autoremove -y
reboot

Wait 30 seconds, then reconnect via SSH.

Configure Firewall

Install and configure UFW (Uncomplicated Firewall):

apt install -y ufw

# Set default policies
ufw default deny incoming
ufw default allow outgoing

# Allow SSH (critical - don't lock yourself out)
ufw allow 22/tcp

# Allow WireGuard VPN
ufw allow 51820/udp

# Enable firewall
ufw enable

Verify the firewall is active:

ufw status verbose

Install Fail2Ban

Protect against brute-force SSH attacks:

apt install -y fail2ban
systemctl enable fail2ban
systemctl start fail2ban

Fail2Ban will automatically ban IP addresses after multiple failed SSH login attempts.

Configure Automatic Security Updates

Enable unattended upgrades for security patches:

apt install -y unattended-upgrades
dpkg-reconfigure -plow unattended-upgrades

Select "Yes" when prompted to enable automatic updates.

Create a Non-Root User (Optional but Recommended)

If you're logged in as root:

adduser username
usermod -aG sudo username

Copy your SSH keys to the new user:

rsync --archive --chown=username:username ~/.ssh /home/username

Test SSH login with the new user before disabling root login:

ssh username@your-server-ip

Installing Pi-hole

Pi-hole provides network-level ad blocking and a DNS server with a web interface for management.

Installation

Pi-hole uses an automated installer:

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

Installation Wizard

The installer will ask several questions:

  1. Upstream DNS Provider: Choose any option (we'll replace this with Unbound later). For now, select Google or Cloudflare.
  2. Blocklists: Accept the default blocklists (you can customize later).
  3. Admin Interface: Select "Yes" to install the web interface.
  4. Web Server: Select "Yes" to install lighttpd.
  5. Logging: Enable query logging if you want to monitor DNS requests.
  6. Privacy Mode: Choose your preferred privacy level (0 = show everything, 3 = anonymous).

The installer will complete and display your admin password. Save this password.

Set a Custom Admin Password

Change the randomly generated password to something memorable:

pihole -a -p

Enter your desired password when prompted.

Access the Admin Interface

Open a web browser and navigate to:

http://your-server-ip/admin

Login with the password you just set.

Initial Pi-hole Configuration

In the web interface:

  1. Navigate to Settings > DNS
  2. Note the current upstream DNS settings (we'll change these after installing Unbound)
  3. Explore the Dashboard to familiarize yourself with the interface

Installing Unbound

Unbound is a recursive DNS resolver that queries root nameservers directly, eliminating third-party DNS providers and enhancing privacy.

Installation

apt install -y unbound

Configuration

Create a new configuration file for Pi-hole integration:

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

Paste the following configuration:

server:
    # Basic settings
    verbosity: 0
    interface: 127.0.0.1
    port: 5335
    do-ip4: yes
    do-udp: yes
    do-tcp: yes
    
    # IPv6 support (disable if not needed)
    do-ip6: no
    
    # Performance settings
    num-threads: 2
    msg-cache-slabs: 2
    rrset-cache-slabs: 2
    infra-cache-slabs: 2
    key-cache-slabs: 2
    
    # Cache sizes (adjust based on your RAM)
    rrset-cache-size: 256m
    msg-cache-size: 128m
    
    # Performance optimizations
    so-rcvbuf: 1m
    so-sndbuf: 1m
    
    # Prefetch popular entries
    prefetch: yes
    prefetch-key: yes
    
    # Aggressive NSEC caching for faster responses
    aggressive-nsec: yes
    
    # Privacy enhancements
    qname-minimisation: yes
    hide-identity: yes
    hide-version: yes
    
    # Security hardening
    harden-glue: yes
    harden-dnssec-stripped: yes
    harden-below-nxdomain: yes
    harden-referral-path: yes
    use-caps-for-id: yes
    
    # Access control
    access-control: 127.0.0.1/32 allow
    access-control: 0.0.0.0/0 refuse
    
    # Root hints
    root-hints: "/var/lib/unbound/root.hints"
    
    # Trust anchor for DNSSEC
    auto-trust-anchor-file: "/var/lib/unbound/root.key"
    
    # Logging (disable for production)
    logfile: ""

Save and exit (Ctrl+X, Y, Enter).

Download Root Hints

Download the current root hints file:

wget -O /var/lib/unbound/root.hints https://www.internic.net/domain/named.root

Restart Unbound

systemctl restart unbound
systemctl enable unbound

Test Unbound

Verify Unbound is working:

dig @127.0.0.1 -p 5335 google.com

You should see a successful DNS response. The first query will be slower as the cache is empty.

Configure Pi-hole to Use Unbound

  1. Open the Pi-hole admin interface
  2. Navigate to Settings > DNS
  3. Uncheck all upstream DNS servers
  4. In the Custom 1 (IPv4) field, enter: 127.0.0.1#5335
  5. Click Save at the bottom of the page

Verify the Configuration

In the Pi-hole admin interface, go to Tools > Query Log and make a test query:

nslookup example.com your-server-ip

You should see the query appear in Pi-hole's logs, and it should be resolved by your local Unbound instance.

Installing WireGuard with PiVPN

PiVPN automates WireGuard VPN setup and integrates seamlessly with Pi-hole.

Installation

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

Installation Wizard

The installer will guide you through configuration:

  1. Static IP Warning: Acknowledge that your cloud server has a static IP
  2. User Selection: If logged in as root, create a standard user account when prompted. Use a simple username like vpnuser. This user stores VPN configuration files.
  3. Unattended Upgrades: Select "Yes" to enable automatic security updates
  4. VPN Protocol: Select WireGuard
  5. Port: Use the default port 51820 (we already opened this in UFW)
  6. DNS Provider: Select Pi-hole (it should auto-detect your Pi-hole installation)
  7. Public IP: Confirm your server's public IP address
  8. Reboot: Allow the installer to reboot when finished

After Reboot

Reconnect via SSH and verify PiVPN is working:

pivpn list

This should show "No clients found" since we haven't created any profiles yet.

Creating Client Profiles

Each device needs its own WireGuard configuration profile.

Create a Profile

Generate a configuration for your first device:

pivpn add -n devicename

Replace devicename with a descriptive name (maximum 15 characters):

  • iphone (your iPhone)
  • laptop (your laptop)
  • tablet (your tablet)
  • work (work computer)

The configuration file is saved to /home/vpnuser/configs/devicename.conf

Create Multiple Profiles

Repeat for each device:

pivpn add -n iphone
pivpn add -n laptop
pivpn add -n tablet

List All Profiles

pivpn list

Generate QR Codes for Mobile Devices

For smartphones and tablets, generate a QR code:

pivpn -qr iphone

A QR code appears in the terminal that you can scan with the WireGuard mobile app.

Client Installation

iOS and Android

  1. Install the WireGuard app from the App Store or Google Play Store
  2. Open the app and tap the + button
  3. Select Create from QR code
  4. Scan the QR code generated by pivpn -qr devicename
  5. Give the connection a name (e.g., "Family VPN")
  6. Toggle the connection ON
  7. (Optional) Enable "Connect on Demand" in the profile settings for automatic connection

macOS

  1. Install WireGuard from the Mac App Store
  2. Download the configuration file from your server:

    scp vpnuser@your-server-ip:/home/vpnuser/configs/laptop.conf ~/Downloads/
    
  3. Open WireGuard and click Import tunnel(s) from file
  4. Select the downloaded .conf file
  5. Click Activate to connect

Windows

  1. Download WireGuard from wireguard.com
  2. Install the application
  3. Download the configuration file using WinSCP or similar:

    • Server: your-server-ip
    • Username: vpnuser
    • Path: /home/vpnuser/configs/laptop.conf
  4. Open WireGuard, click Import tunnel(s) from file
  5. Select the .conf file
  6. Click Activate

Linux (Ubuntu/Debian)

Install WireGuard:

apt install -y wireguard openresolv

Download the configuration file:

scp vpnuser@your-server-ip:/home/vpnuser/configs/laptop.conf ~/

Move it to the WireGuard directory:

sudo mv ~/laptop.conf /etc/wireguard/

Manual Connection

Connect to the VPN:

sudo wg-quick up laptop

Disconnect:

sudo wg-quick down laptop

Automatic Connection with NetworkManager

For automatic connection on boot and easier management:

sudo nmcli connection import type wireguard file /etc/wireguard/laptop.conf

Enable auto-connect:

nmcli connection modify laptop connection.autoconnect yes

List all connections:

nmcli connection show

Disable auto-connect if needed:

nmcli connection modify laptop connection.autoconnect no

Verify Your Connection

Once connected, verify your VPN is working:

  1. Check your IP address:

    curl ifconfig.me
    

    This should show your server's IP, not your local IP.

  2. Test DNS resolution:

    nslookup doubleclick.net
    

    This ad domain should resolve to 0.0.0.0 (blocked by Pi-hole).

  3. Check Pi-hole logs:

    • Open the Pi-hole admin interface
    • Navigate to Tools > Query Log
    • You should see queries from your device's WireGuard IP (usually 10.6.0.x or similar)

Security Hardening

Restrict Pi-hole Admin Access

By default, anyone who can reach your server's IP can access the Pi-hole admin interface. Restrict it to only VPN-connected devices.
Edit the lighttpd configuration:

nano /etc/lighttpd/external.conf

Add the following (replace IP addresses with your own):

# Restrict admin panel to specific IPs
# Update these IPs to match your WireGuard client IPs
$HTTP["remoteip"] !~ "10.6.0.2|10.6.0.3|10.6.0.4" {
    $HTTP["url"] =~ "^/admin/" {
        url.access-deny = ( "" )
    }
}

Find your WireGuard client IPs:

cat /etc/wireguard/wg0.conf

Look for the Peer sections and note the AllowedIPs values.
Restart lighttpd:

systemctl restart lighttpd

Harden SSH Access

If you haven't already, disable password authentication and root login:

nano /etc/ssh/sshd_config

Find and modify these lines:

PasswordAuthentication no
PermitRootLogin no

Restart SSH:

systemctl restart sshd

Warning: Only do this after confirming SSH key authentication works, or you'll lock yourself out.

Monitor Failed Login Attempts

Check Fail2Ban status:

fail2ban-client status sshd

This shows how many IPs have been banned for failed SSH attempts.

Keep Software Updated

Your unattended-upgrades configuration handles security updates automatically, but you should periodically check for major updates:

apt update
apt list --upgradable

Update Pi-hole:

pihole -up

Update PiVPN:

pivpn update

Maintenance and Monitoring

Regular Maintenance Tasks

Weekly:

  • Review Pi-hole query logs for unusual activity
  • Check blocked domain percentage (Dashboard)

Monthly:

  • Review and update blocklists
  • Check server disk space: df -h
  • Review Fail2Ban banned IPs: fail2ban-client status sshd

Quarterly:

  • Update Unbound root hints:
    wget -O /var/lib/unbound/root.hints https://www.internic.net/domain/named.root
    systemctl restart unbound
    

Managing Client Access

List all connected clients:

pivpn list

Show connection statistics:

pivpn -c

Remove a client:

pivpn remove

Select the profile to remove from the menu.
Temporarily disable a client by moving its config:

mv /home/vpnuser/configs/oldphone.conf /home/vpnuser/configs/oldphone.conf.disabled

Backup Your Configuration

Create regular backups of your critical configuration:

# Create backup directory
mkdir -p /root/backups

# Backup Pi-hole
pihole -a -t
mv /root/pi-hole-*.tar.gz /root/backups/

# Backup WireGuard configs
tar -czf /root/backups/wireguard-$(date +%Y%m%d).tar.gz /etc/wireguard/

# Backup Unbound config
cp -r /etc/unbound/ /root/backups/unbound-$(date +%Y%m%d)/

# Download backups to your local machine
scp -r vpnuser@your-server-ip:/root/backups/ ~/vpn-backups/

Monitor Server Resources

Check CPU and memory usage:

htop

(Install with apt install htop if needed)
Check disk space:

df -h

Monitor active WireGuard connections:

wg show

Pi-hole Maintenance

Update gravity (blocklists):

pihole -g

Clear query logs (if needed):

pihole flush

View live query log in terminal:

pihole -t

Troubleshooting

VPN Won't Connect

Check WireGuard status on server:

wg show
systemctl status wg-quick@wg0

Verify firewall allows WireGuard:

ufw status | grep 51820

Check client configuration:
Ensure the client .conf file has the correct server endpoint IP address.
Test connectivity:

ping your-server-ip

If this fails, your client can't reach the server at all (network/firewall issue).

DNS Not Resolving

Verify Unbound is running:

systemctl status unbound

Test Unbound directly:

dig @127.0.0.1 -p 5335 google.com

Check Pi-hole is using Unbound:
In the Pi-hole admin interface, go to Settings > DNS and verify 127.0.0.1#5335 is set as the upstream DNS.
Check Pi-hole status:

pihole status

Verify DNS on client:
From your connected VPN client:

nslookup google.com

This should show your WireGuard server IP as the DNS server.

Ads Still Appearing

Check device is using VPN:

curl ifconfig.me

This should show your server's IP, not your home IP.
Verify DNS queries going through Pi-hole:
Open the Pi-hole admin interface and go to Tools > Query Log. Make a test query and verify it appears in the logs.
Check blocklists are active:
Navigate to Group Management > Adlists and ensure lists are enabled and recently updated.
Update gravity:

pihole -g

Some ads can't be blocked:
YouTube ads, sponsored social media posts, and first-party ads cannot be blocked at the DNS level. These require browser extensions or app-level blocking.

Slow DNS Resolution

Check Unbound cache:
The first DNS query for a domain is always slower (recursive lookup). Subsequent queries should be nearly instant.
Increase cache sizes:
Edit /etc/unbound/unbound.conf.d/pi-hole.conf and increase these values:

rrset-cache-size: 512m
msg-cache-size: 256m

Restart Unbound:

systemctl restart unbound

Check server resources:

htop

If CPU or RAM is maxed out, upgrade your server instance.

Connection Drops Frequently

Mobile devices (iOS/Android):
Enable "Connect on Demand" or "Always-on VPN" in the WireGuard app settings. Some phones aggressively close VPN connections to save battery.
Check server load:

uptime

If the load average is consistently above the number of CPU cores, your server is overloaded.
Check for IP conflicts:
Ensure WireGuard client IPs don't conflict:

cat /etc/wireguard/wg0.conf

Each Peer section should have a unique AllowedIPs value.

Can't Access Pi-hole Admin Interface

From VPN client:
Verify you're connected to the VPN, then navigate to:

http://10.6.0.1/admin

(Use your server's WireGuard IP, found with wg show)
Check lighttpd restrictions:
If you configured IP restrictions, ensure your current WireGuard IP is whitelisted:

nano /etc/lighttpd/external.conf

Verify lighttpd is running:

systemctl status lighttpd

Pi-hole Not Blocking Anything

Check DNS settings in WireGuard config:
On the client device, verify the WireGuard profile has DNS = 10.6.0.1 (or your server's WireGuard IP).
Verify Pi-hole is enabled:

pihole status

If disabled:

pihole enable

Check query log:
If queries don't appear in Pi-hole's query log, DNS requests aren't reaching Pi-hole. Check your client's DNS configuration.

Advanced Configuration

Custom Blocklists

Add additional blocklists in the Pi-hole admin interface:

  1. Navigate to Group Management > Adlists
  2. Add list URLs (examples below)
  3. Click Add
  4. Run pihole -g to update

Recommended blocklists:

  • The Block List Project: https://blocklistproject.github.io/Lists/
  • OISD: https://big.oisd.nl/domainswild
  • Hagezi's lists: https://github.com/hagezi/dns-blocklists

Warning: Very large blocklists (millions of domains) can slow down DNS resolution. Start conservative and add more only if needed.

Whitelist Management

Some sites break when ads are blocked. Add them to the whitelist:

  1. Navigate to Whitelist in Pi-hole admin
  2. Add the domain
  3. Click Add

Common domains to whitelist:

  • s.youtube.com (YouTube history)
  • www.googleadservices.com (Google Shopping)
  • amazon-adsystem.com (Amazon product images)

Pre-made whitelist:

git clone https://github.com/anudeepND/whitelist.git
cd whitelist/scripts
sudo python3 whitelist.py

Group Management

Create different filtering levels for different devices:

  1. Navigate to Group Management > Groups
  2. Create groups (e.g., "Kids", "Adults", "Work")
  3. Assign clients to groups in Group Management > Clients
  4. Assign adlists to groups in Group Management > Adlists

This allows stricter filtering for children's devices while allowing work devices more access.

Split Tunneling

To route only specific traffic through the VPN (e.g., DNS only), edit your client .conf file:
Change:

AllowedIPs = 0.0.0.0/0, ::/0

To:

AllowedIPs = 10.6.0.0/24

This routes only VPN subnet traffic through the tunnel, letting other traffic use your local internet connection directly.

IPv6 Support

If your server and ISP support IPv6:

  1. Enable IPv6 in Unbound (do-ip6: yes)
  2. Add IPv6 configuration to WireGuard (Address = 10.6.0.2/24, fd42:42:42::2/64)
  3. Update Pi-hole to listen on IPv6
  4. Update UFW to allow IPv6 WireGuard traffic

Detailed IPv6 setup is beyond the scope of this guide, but the components support it.

Monitoring with Grafana (Optional)

For advanced monitoring:

  1. Install Prometheus and Grafana
  2. Configure Pi-hole exporter
  3. Create dashboards for DNS queries, blocked domains, and VPN connections

This is optional but provides excellent visibility into your system's performance.

Resources

Official Documentation

Video Tutorials

Blocklist Resources

Community Support

Additional Guides

Cost Analysis

Bandwidth considerations:
Average usage per device: 50-100MB/day (mostly DNS queries, minimal data transfer). A 1TB/month bandwidth limit handles 20+ devices easily.

Final Notes

This setup provides a robust, self-hosted VPN solution with comprehensive ad blocking and privacy-focused DNS resolution. The initial setup takes 1-2 hours, but ongoing maintenance is minimal (10-15 minutes per month).
Key advantages over commercial VPNs:

  • Complete control and visibility
  • No third-party logging concerns
  • Significantly lower cost for families
  • Customizable filtering and access controls
  • Learning opportunity for networking and Linux administration

The main limitations:

  • Requires basic technical knowledge
  • May not work with streaming services
  • Single point of failure (consider backup strategies)
  • Responsibility for security and maintenance

For most users, this solution provides better privacy, security, and value than commercial VPN services while offering flexibility that commercial services cannot match.

Last updated: October 2025
Tested on: Ubuntu 24.04 LTS, Pi-hole v5.18.3, WireGuard (kernel module), Unbound 1.19.3

Deployments with Ansible and Ansistrano

Deployment

Read more...

VideoJS Media block module has been released

old time movie projector

Read more...

Closed-source vs open-source website hosting

We use open-source software

Read more...

Fork an upstream GIT repository and make it your own

Fork an upstream GIT repository

Read more...
Click to contact us.